Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an "Ecosystem" category to the Samples menu #2835

Merged
merged 1 commit into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading