Skip to content

Commit

Permalink
Sample menu exploration (#2835)
Browse files Browse the repository at this point in the history
  • Loading branch information
parlough authored Feb 15, 2024
1 parent 9d8e450 commit cff1de0
Show file tree
Hide file tree
Showing 18 changed files with 1,444 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"glob": "2.1.2",
"go_router": "13.2.0",
"google_fonts": "6.1.0",
"google_generative_ai": "0.1.0",
"google_generative_ai": "0.2.0",
"hooks_riverpod": "2.4.10",
"html": "0.15.4",
"http": "1.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"glob": "2.1.2",
"go_router": "13.2.0",
"google_fonts": "6.1.0",
"google_generative_ai": "0.1.0",
"google_generative_ai": "0.2.0",
"hooks_riverpod": "2.4.10",
"html": "0.15.4",
"http": "1.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"glob": "2.1.2",
"go_router": "13.2.0",
"google_fonts": "6.1.0",
"google_generative_ai": "0.1.0",
"google_generative_ai": "0.2.0",
"hooks_riverpod": "2.4.10",
"html": "0.15.4",
"http": "1.2.0",
Expand Down
2 changes: 2 additions & 0 deletions pkgs/samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Sample code snippets for DartPad.
| --- | --- | --- | --- |
| Dart | Fibonacci | [fibonacci.dart](lib/fibonacci.dart) | `fibonacci` |
| Dart | Hello world | [hello_world.dart](lib/hello_world.dart) | `hello-world` |
| Ecosystem | Flame game | [brick_breaker.dart](lib/brick_breaker.dart) | `flame-game` |
| Ecosystem | Google AI SDK | [google_ai.dart](lib/google_ai.dart) | `google-ai-sdk` |
| Flutter | Counter | [main.dart](lib/main.dart) | `counter` |
| Flutter | Sunflower | [sunflower.dart](lib/sunflower.dart) | `sunflower` |
<!-- samples -->
Expand Down
326 changes: 326 additions & 0 deletions pkgs/samples/lib/brick_breaker.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
// Copyright 2024 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

/// A simplified brick-breaker game,
/// built using the Flame game engine for Flutter.
///
/// To learn how to build a more complete version of this game yourself,
/// check out the codelab at https://docs.flutter.dev/brick-breaker.
library;

import 'dart:async';
import 'dart:math' as math;

import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
runApp(const GameApp());
}

class GameApp extends StatefulWidget {
const GameApp({super.key});

@override
State<GameApp> createState() => _GameAppState();
}

class _GameAppState extends State<GameApp> {
late final BrickBreaker game;

@override
void initState() {
super.initState();
game = BrickBreaker();
}

@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xffa9d6e5),
Color(0xfff2e8cf),
],
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: FittedBox(
child: SizedBox(
width: gameWidth,
height: gameHeight,
child: GameWidget(
game: game,
),
),
),
),
),
),
),
),
);
}
}

class BrickBreaker extends FlameGame
with HasCollisionDetection, KeyboardEvents, TapDetector {
BrickBreaker()
: super(
camera: CameraComponent.withFixedResolution(
width: gameWidth, height: gameHeight));

final rand = math.Random();
double get width => size.x;
double get height => size.y;

@override
FutureOr<void> onLoad() async {
super.onLoad();
camera.viewfinder.anchor = Anchor.topLeft;
world.add(PlayArea());
startGame();
}

void startGame() {
world.removeAll(world.children.query<Ball>());
world.removeAll(world.children.query<Bat>());
world.removeAll(world.children.query<Brick>());

world.add(Ball(
difficultyModifier: difficultyModifier,
radius: ballRadius,
position: size / 2,
velocity:
Vector2((rand.nextDouble() - 0.5) * width, height * 0.2).normalized()
..scale(height / 4),
));

world.add(Bat(
size: Vector2(batWidth, batHeight),
cornerRadius: const Radius.circular(ballRadius / 2),
position: Vector2(width / 2, height * 0.95),
));

world.addAll([
for (var i = 0; i < brickColors.length; i++)
for (var j = 1; j <= 5; j++)
Brick(
Vector2(
(i + 0.5) * brickWidth + (i + 1) * brickGutter,
(j + 2.0) * brickHeight + j * brickGutter,
),
brickColors[i],
),
]);
}

@override
void onTap() {
super.onTap();
startGame();
}

@override
KeyEventResult onKeyEvent(
RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
super.onKeyEvent(event, keysPressed);
switch (event.logicalKey) {
case LogicalKeyboardKey.arrowLeft:
case LogicalKeyboardKey.keyA:
world.children.query<Bat>().first.moveBy(-batStep);
case LogicalKeyboardKey.keyD:
case LogicalKeyboardKey.arrowRight:
world.children.query<Bat>().first.moveBy(batStep);
case LogicalKeyboardKey.space:
case LogicalKeyboardKey.enter:
startGame();
}
return KeyEventResult.handled;
}

@override
Color backgroundColor() => const Color(0xfff2e8cf);
}

class Ball extends CircleComponent
with CollisionCallbacks, HasGameReference<BrickBreaker> {
Ball({
required this.velocity,
required super.position,
required double radius,
required this.difficultyModifier,
}) : super(
radius: radius,
anchor: Anchor.center,
paint: Paint()
..color = const Color(0xff1e6091)
..style = PaintingStyle.fill,
children: [CircleHitbox()]);

final Vector2 velocity;
final double difficultyModifier;

@override
void update(double dt) {
super.update(dt);
position += velocity * dt;
}

@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
if (other is PlayArea) {
if (intersectionPoints.first.y <= 0) {
velocity.y = -velocity.y;
} else if (intersectionPoints.first.x <= 0) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.x >= game.width) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.y >= game.height) {
add(RemoveEffect(
delay: 0.35,
onComplete: () {
game.startGame();
},
));
}
} else if (other is Bat) {
velocity.y = -velocity.y;
velocity.x = velocity.x +
(position.x - other.position.x) / other.size.x * game.width * 0.3;
} else if (other is Brick) {
if (position.y < other.position.y - other.size.y / 2) {
velocity.y = -velocity.y;
} else if (position.y > other.position.y + other.size.y / 2) {
velocity.y = -velocity.y;
} else if (position.x < other.position.x) {
velocity.x = -velocity.x;
} else if (position.x > other.position.x) {
velocity.x = -velocity.x;
}
velocity.setFrom(velocity * difficultyModifier);
}
}
}

class Bat extends PositionComponent
with DragCallbacks, HasGameReference<BrickBreaker> {
Bat({
required this.cornerRadius,
required super.position,
required super.size,
}) : super(anchor: Anchor.center, children: [RectangleHitbox()]);

final Radius cornerRadius;

final _paint = Paint()
..color = const Color(0xff1e6091)
..style = PaintingStyle.fill;

@override
void render(Canvas canvas) {
super.render(canvas);
canvas.drawRRect(
RRect.fromRectAndRadius(
Offset.zero & size.toSize(),
cornerRadius,
),
_paint,
);
}

@override
void onDragUpdate(DragUpdateEvent event) {
if (isRemoved) return;
super.onDragUpdate(event);
position.x = (position.x + event.localDelta.x)
.clamp(width / 2, game.width - width / 2);
}

void moveBy(double dx) {
add(MoveToEffect(
Vector2(
(position.x + dx).clamp(width / 2, game.width - width / 2),
position.y,
),
EffectController(duration: 0.1),
));
}
}

class Brick extends RectangleComponent
with CollisionCallbacks, HasGameReference<BrickBreaker> {
Brick(Vector2 position, Color color)
: super(
position: position,
size: Vector2(brickWidth, brickHeight),
anchor: Anchor.center,
paint: Paint()
..color = color
..style = PaintingStyle.fill,
children: [RectangleHitbox()],
);

@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
removeFromParent();

if (game.world.children.query<Brick>().length == 1) {
game.startGame();
}
}
}

class PlayArea extends RectangleComponent with HasGameReference<BrickBreaker> {
PlayArea() : super(children: [RectangleHitbox()]);

@override
Future<void> onLoad() async {
super.onLoad();
size = Vector2(game.width, game.height);
}
}

const brickColors = [
Color(0xfff94144),
Color(0xfff3722c),
Color(0xfff8961e),
Color(0xfff9844a),
Color(0xfff9c74f),
Color(0xff90be6d),
Color(0xff43aa8b),
Color(0xff4d908e),
Color(0xff277da1),
Color(0xff577590),
];

const gameWidth = 820.0;
const gameHeight = 1600.0;
const ballRadius = gameWidth * 0.02;
const batWidth = gameWidth * 0.2;
const batHeight = ballRadius * 2;
const batStep = gameWidth * 0.05;
const brickGutter = gameWidth * 0.015;
final brickWidth =
(gameWidth - (brickGutter * (brickColors.length + 1))) / brickColors.length;
const brickHeight = gameHeight * 0.03;
const difficultyModifier = 1.05;
Loading

0 comments on commit cff1de0

Please sign in to comment.