-
Notifications
You must be signed in to change notification settings - Fork 560
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
1,444 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.