mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 01:18:38 +08:00
docs: Added the T-Rex example (#1602)
This commit is contained in:
BIN
examples/assets/images/trex.png
Normal file
BIN
examples/assets/images/trex.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
@ -8,6 +8,7 @@ import 'stories/collision_detection/collision_detection.dart';
|
||||
import 'stories/components/components.dart';
|
||||
import 'stories/effects/effects.dart';
|
||||
import 'stories/experimental/experimental.dart';
|
||||
import 'stories/games/games.dart';
|
||||
import 'stories/input/input.dart';
|
||||
import 'stories/parallax/parallax.dart';
|
||||
import 'stories/rendering/rendering.dart';
|
||||
@ -23,6 +24,10 @@ void main() async {
|
||||
theme: ThemeData.dark(),
|
||||
);
|
||||
|
||||
// Some small sample games
|
||||
addGameStories(dashbook);
|
||||
|
||||
// Feature examples
|
||||
addAnimationStories(dashbook);
|
||||
addCameraAndViewportStories(dashbook);
|
||||
addCollisionDetectionStories(dashbook);
|
||||
|
||||
26
examples/lib/stories/games/games.dart
Normal file
26
examples/lib/stories/games/games.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'package:dashbook/dashbook.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../commons/commons.dart';
|
||||
import 'trex/trex_game.dart';
|
||||
|
||||
void addGameStories(Dashbook dashbook) {
|
||||
dashbook.storiesOf('Sample Games').add(
|
||||
'T-Rex',
|
||||
(_) => Container(
|
||||
color: Colors.black,
|
||||
margin: const EdgeInsets.all(45),
|
||||
child: ClipRect(
|
||||
child: GameWidget(
|
||||
game: TRexGame(),
|
||||
loadingBuilder: (_) => const Center(
|
||||
child: Text('Loading'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
codeLink: baseLink('games/trex'),
|
||||
info: TRexGame.description,
|
||||
);
|
||||
}
|
||||
62
examples/lib/stories/games/trex/background/cloud.dart
Normal file
62
examples/lib/stories/games/trex/background/cloud.dart
Normal file
@ -0,0 +1,62 @@
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
import '../random_extension.dart';
|
||||
import '../trex_game.dart';
|
||||
import 'cloud_manager.dart';
|
||||
|
||||
class Cloud extends SpriteComponent
|
||||
with ParentIsA<CloudManager>, HasGameRef<TRexGame> {
|
||||
Cloud({required Vector2 position})
|
||||
: cloudGap = random.fromRange(
|
||||
minCloudGap,
|
||||
maxCloudGap,
|
||||
),
|
||||
super(
|
||||
position: position,
|
||||
size: initialSize,
|
||||
);
|
||||
|
||||
static Vector2 initialSize = Vector2(92.0, 28.0);
|
||||
|
||||
static const double maxCloudGap = 400.0;
|
||||
static const double minCloudGap = 100.0;
|
||||
|
||||
static const double maxSkyLevel = 71.0;
|
||||
static const double minSkyLevel = 30.0;
|
||||
|
||||
final double cloudGap;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
sprite = Sprite(
|
||||
gameRef.spriteImage,
|
||||
srcPosition: Vector2(166.0, 2.0),
|
||||
srcSize: initialSize,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
if (shouldRemove) {
|
||||
return;
|
||||
}
|
||||
x -= parent.cloudSpeed.ceil() * 50 * dt;
|
||||
|
||||
if (!isVisible) {
|
||||
removeFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
bool get isVisible {
|
||||
return x + width > 0;
|
||||
}
|
||||
|
||||
@override
|
||||
void onGameResize(Vector2 gameSize) {
|
||||
super.onGameResize(gameSize);
|
||||
y = ((absolutePosition.y / 2 - (maxSkyLevel - minSkyLevel)) +
|
||||
random.fromRange(minSkyLevel, maxSkyLevel)) -
|
||||
absolutePositionOf(absoluteTopLeftPosition).y;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
import '../random_extension.dart';
|
||||
import '../trex_game.dart';
|
||||
import 'cloud.dart';
|
||||
|
||||
class CloudManager extends PositionComponent with HasGameRef<TRexGame> {
|
||||
final double cloudFrequency = 0.5;
|
||||
final int maxClouds = 20;
|
||||
final double bgCloudSpeed = 0.2;
|
||||
|
||||
void addCloud() {
|
||||
final cloudPosition = Vector2(
|
||||
gameRef.size.x + Cloud.initialSize.x + 10,
|
||||
((absolutePosition.y / 2 - (Cloud.maxSkyLevel - Cloud.minSkyLevel)) +
|
||||
random.fromRange(Cloud.minSkyLevel, Cloud.maxSkyLevel)) -
|
||||
absolutePosition.y,
|
||||
);
|
||||
add(Cloud(position: cloudPosition));
|
||||
}
|
||||
|
||||
double get cloudSpeed => bgCloudSpeed / 1000 * gameRef.currentSpeed;
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
final numClouds = children.length;
|
||||
if (numClouds > 0) {
|
||||
final lastCloud = children.last as Cloud;
|
||||
if (numClouds < maxClouds &&
|
||||
(gameRef.size.x / 2 - lastCloud.x) > lastCloud.cloudGap) {
|
||||
addCloud();
|
||||
}
|
||||
} else {
|
||||
addCloud();
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
removeAll(children);
|
||||
}
|
||||
}
|
||||
82
examples/lib/stories/games/trex/background/horizon.dart
Normal file
82
examples/lib/stories/games/trex/background/horizon.dart
Normal file
@ -0,0 +1,82 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
import '../obstacle/obstacle_manager.dart';
|
||||
import '../trex_game.dart';
|
||||
import 'cloud_manager.dart';
|
||||
|
||||
class Horizon extends PositionComponent with HasGameRef<TRexGame> {
|
||||
Horizon() : super();
|
||||
|
||||
static final Vector2 lineSize = Vector2(1200, 24);
|
||||
final Queue<SpriteComponent> groundLayers = Queue();
|
||||
late final CloudManager cloudManager = CloudManager();
|
||||
late final ObstacleManager obstacleManager = ObstacleManager();
|
||||
|
||||
late final _softSprite = Sprite(
|
||||
gameRef.spriteImage,
|
||||
srcPosition: Vector2(2.0, 104.0),
|
||||
srcSize: lineSize,
|
||||
);
|
||||
|
||||
late final _bumpySprite = Sprite(
|
||||
gameRef.spriteImage,
|
||||
srcPosition: Vector2(gameRef.spriteImage.width / 2, 104.0),
|
||||
srcSize: lineSize,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
add(cloudManager);
|
||||
add(obstacleManager);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
final increment = gameRef.currentSpeed * dt;
|
||||
for (final line in groundLayers) {
|
||||
line.x -= increment;
|
||||
}
|
||||
|
||||
final firstLine = groundLayers.first;
|
||||
if (firstLine.x <= -firstLine.width) {
|
||||
firstLine.x = groundLayers.last.x + groundLayers.last.width;
|
||||
groundLayers.remove(firstLine);
|
||||
groundLayers.add(firstLine);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onGameResize(Vector2 gameSize) {
|
||||
super.onGameResize(gameSize);
|
||||
final newLines = _generateLines();
|
||||
groundLayers.addAll(newLines);
|
||||
addAll(newLines);
|
||||
y = (gameSize.y / 2) + 21.0;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
cloudManager.reset();
|
||||
obstacleManager.reset();
|
||||
groundLayers.forEachIndexed((i, line) => line.x = i * lineSize.x);
|
||||
}
|
||||
|
||||
List<SpriteComponent> _generateLines() {
|
||||
final number =
|
||||
1 + (gameRef.size.x / lineSize.x).ceil() - groundLayers.length;
|
||||
final lastX = (groundLayers.lastOrNull?.x ?? 0) +
|
||||
(groundLayers.lastOrNull?.width ?? 0);
|
||||
return List.generate(
|
||||
max(number, 0),
|
||||
(i) => SpriteComponent(
|
||||
sprite: (i + groundLayers.length).isEven ? _softSprite : _bumpySprite,
|
||||
size: lineSize,
|
||||
)..x = lastX + lineSize.x * i,
|
||||
growable: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
62
examples/lib/stories/games/trex/game_over.dart
Normal file
62
examples/lib/stories/games/trex/game_over.dart
Normal file
@ -0,0 +1,62 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
import 'trex_game.dart';
|
||||
|
||||
class GameOverPanel extends Component {
|
||||
bool visible = false;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
add(GameOverText());
|
||||
add(GameOverRestart());
|
||||
}
|
||||
|
||||
@override
|
||||
void renderTree(Canvas canvas) {
|
||||
if (visible) {
|
||||
super.renderTree(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GameOverText extends SpriteComponent with HasGameRef<TRexGame> {
|
||||
GameOverText() : super(size: Vector2(382, 25), anchor: Anchor.center);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
sprite = Sprite(
|
||||
gameRef.spriteImage,
|
||||
srcPosition: Vector2(955.0, 26.0),
|
||||
srcSize: size,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onGameResize(Vector2 gameSize) {
|
||||
super.onGameResize(gameSize);
|
||||
x = gameSize.x / 2;
|
||||
y = gameSize.y * .25;
|
||||
}
|
||||
}
|
||||
|
||||
class GameOverRestart extends SpriteComponent with HasGameRef<TRexGame> {
|
||||
GameOverRestart() : super(size: Vector2(72, 64), anchor: Anchor.center);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
sprite = Sprite(
|
||||
gameRef.spriteImage,
|
||||
srcPosition: Vector2.all(2.0),
|
||||
srcSize: size,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onGameResize(Vector2 gameSize) {
|
||||
super.onGameResize(gameSize);
|
||||
x = gameSize.x / 2;
|
||||
y = gameSize.y * .75;
|
||||
}
|
||||
}
|
||||
48
examples/lib/stories/games/trex/obstacle/obstacle.dart
Normal file
48
examples/lib/stories/games/trex/obstacle/obstacle.dart
Normal file
@ -0,0 +1,48 @@
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
import '../random_extension.dart';
|
||||
import '../trex_game.dart';
|
||||
import 'obstacle_type.dart';
|
||||
|
||||
class Obstacle extends SpriteComponent with HasGameRef<TRexGame> {
|
||||
Obstacle({
|
||||
required this.settings,
|
||||
required this.groupIndex,
|
||||
}) : super(size: settings.size);
|
||||
|
||||
final double _gapCoefficient = 0.6;
|
||||
final double _maxGapCoefficient = 1.5;
|
||||
|
||||
bool followingObstacleCreated = false;
|
||||
late double gap;
|
||||
final ObstacleTypeSettings settings;
|
||||
final int groupIndex;
|
||||
|
||||
bool get isVisible => (x + width) > 0;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
sprite = settings.sprite(gameRef.spriteImage);
|
||||
x = gameRef.size.x + width * groupIndex;
|
||||
y = settings.y;
|
||||
gap = computeGap(_gapCoefficient, gameRef.currentSpeed);
|
||||
addAll(settings.generateHitboxes());
|
||||
}
|
||||
|
||||
double computeGap(double gapCoefficient, double speed) {
|
||||
final minGap =
|
||||
(width * speed * settings.minGap * gapCoefficient).roundToDouble();
|
||||
final maxGap = (minGap * _maxGapCoefficient).roundToDouble();
|
||||
return random.fromRange(minGap, maxGap);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
x -= gameRef.currentSpeed * dt;
|
||||
|
||||
if (!isVisible) {
|
||||
removeFromParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
import '../random_extension.dart';
|
||||
import '../trex_game.dart';
|
||||
import 'obstacle.dart';
|
||||
import 'obstacle_type.dart';
|
||||
|
||||
class ObstacleManager extends Component with HasGameRef<TRexGame> {
|
||||
ObstacleManager();
|
||||
|
||||
ListQueue<ObstacleType> history = ListQueue();
|
||||
static const int maxObstacleDuplication = 2;
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
final obstacles = children.query<Obstacle>();
|
||||
|
||||
if (obstacles.isNotEmpty) {
|
||||
final lastObstacle = children.last as Obstacle?;
|
||||
|
||||
if (lastObstacle != null &&
|
||||
!lastObstacle.followingObstacleCreated &&
|
||||
lastObstacle.isVisible &&
|
||||
(lastObstacle.x + lastObstacle.width + lastObstacle.gap) <
|
||||
gameRef.size.x) {
|
||||
addNewObstacle();
|
||||
lastObstacle.followingObstacleCreated = true;
|
||||
}
|
||||
} else {
|
||||
addNewObstacle();
|
||||
}
|
||||
}
|
||||
|
||||
void addNewObstacle() {
|
||||
final speed = gameRef.currentSpeed;
|
||||
if (speed == 0) {
|
||||
return;
|
||||
}
|
||||
var settings = random.nextBool()
|
||||
? ObstacleTypeSettings.cactusSmall
|
||||
: ObstacleTypeSettings.cactusLarge;
|
||||
if (duplicateObstacleCheck(settings.type) || speed < settings.allowedAt) {
|
||||
settings = ObstacleTypeSettings.cactusSmall;
|
||||
}
|
||||
|
||||
final groupSize = _groupSize(settings);
|
||||
for (var i = 0; i < groupSize; i++) {
|
||||
add(Obstacle(settings: settings, groupIndex: i));
|
||||
gameRef.score++;
|
||||
}
|
||||
|
||||
history.addFirst(settings.type);
|
||||
while (history.length > maxObstacleDuplication) {
|
||||
history.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
bool duplicateObstacleCheck(ObstacleType nextType) {
|
||||
var duplicateCount = 0;
|
||||
|
||||
for (final type in history) {
|
||||
duplicateCount += type == nextType ? 1 : 0;
|
||||
}
|
||||
return duplicateCount >= maxObstacleDuplication;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
removeAll(children);
|
||||
history.clear();
|
||||
}
|
||||
|
||||
int _groupSize(ObstacleTypeSettings settings) {
|
||||
if (gameRef.currentSpeed > settings.multipleAt) {
|
||||
return random.fromRange(1.0, ObstacleTypeSettings.maxGroupSize).floor();
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
examples/lib/stories/games/trex/obstacle/obstacle_type.dart
Normal file
105
examples/lib/stories/games/trex/obstacle/obstacle_type.dart
Normal file
@ -0,0 +1,105 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/collisions.dart';
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
enum ObstacleType {
|
||||
cactusSmall,
|
||||
cactusLarge,
|
||||
}
|
||||
|
||||
class ObstacleTypeSettings {
|
||||
const ObstacleTypeSettings._internal(
|
||||
this.type, {
|
||||
required this.size,
|
||||
required this.y,
|
||||
required this.allowedAt,
|
||||
required this.multipleAt,
|
||||
required this.minGap,
|
||||
required this.minSpeed,
|
||||
this.numFrames,
|
||||
this.frameRate,
|
||||
this.speedOffset,
|
||||
required this.generateHitboxes,
|
||||
});
|
||||
|
||||
final ObstacleType type;
|
||||
final Vector2 size;
|
||||
final double y;
|
||||
final int allowedAt;
|
||||
final int multipleAt;
|
||||
final double minGap;
|
||||
final double minSpeed;
|
||||
final int? numFrames;
|
||||
final double? frameRate;
|
||||
final double? speedOffset;
|
||||
|
||||
static const maxGroupSize = 3.0;
|
||||
|
||||
final List<ShapeHitbox> Function() generateHitboxes;
|
||||
|
||||
static final cactusSmall = ObstacleTypeSettings._internal(
|
||||
ObstacleType.cactusSmall,
|
||||
size: Vector2(34.0, 70.0),
|
||||
y: -55.0,
|
||||
allowedAt: 0,
|
||||
multipleAt: 1000,
|
||||
minGap: 120.0,
|
||||
minSpeed: 0.0,
|
||||
generateHitboxes: () => <ShapeHitbox>[
|
||||
RectangleHitbox(
|
||||
position: Vector2(5.0, 7.0),
|
||||
size: Vector2(10.0, 54.0),
|
||||
),
|
||||
RectangleHitbox(
|
||||
position: Vector2(5.0, 7.0),
|
||||
size: Vector2(12.0, 68.0),
|
||||
),
|
||||
RectangleHitbox(
|
||||
position: Vector2(15.0, 4.0),
|
||||
size: Vector2(14.0, 28.0),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
static final cactusLarge = ObstacleTypeSettings._internal(
|
||||
ObstacleType.cactusLarge,
|
||||
size: Vector2(50.0, 100.0),
|
||||
y: -74.0,
|
||||
allowedAt: 800,
|
||||
multipleAt: 1500,
|
||||
minGap: 120.0,
|
||||
minSpeed: 0.0,
|
||||
generateHitboxes: () => <ShapeHitbox>[
|
||||
RectangleHitbox(
|
||||
position: Vector2(0.0, 26.0),
|
||||
size: Vector2(14.0, 40.0),
|
||||
),
|
||||
RectangleHitbox(
|
||||
position: Vector2(16.0, 0.0),
|
||||
size: Vector2(14.0, 98.0),
|
||||
),
|
||||
RectangleHitbox(
|
||||
position: Vector2(28.0, 22.0),
|
||||
size: Vector2(20.0, 40.0),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Sprite sprite(Image spriteImage) {
|
||||
switch (type) {
|
||||
case ObstacleType.cactusSmall:
|
||||
return Sprite(
|
||||
spriteImage,
|
||||
srcPosition: Vector2(446.0, 2.0),
|
||||
srcSize: size,
|
||||
);
|
||||
case ObstacleType.cactusLarge:
|
||||
return Sprite(
|
||||
spriteImage,
|
||||
srcPosition: Vector2(652.0, 2.0),
|
||||
srcSize: size,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
examples/lib/stories/games/trex/player.dart
Normal file
130
examples/lib/stories/games/trex/player.dart
Normal file
@ -0,0 +1,130 @@
|
||||
import 'package:flame/collisions.dart';
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
import 'trex_game.dart';
|
||||
|
||||
enum PlayerState { crashed, jumping, running, waiting }
|
||||
|
||||
class Player extends SpriteAnimationGroupComponent<PlayerState>
|
||||
with HasGameRef<TRexGame>, CollisionCallbacks {
|
||||
Player() : super(size: Vector2(90, 88));
|
||||
|
||||
final double gravity = 1;
|
||||
|
||||
final double initialJumpVelocity = -15.0;
|
||||
final double introDuration = 1500.0;
|
||||
final double startXPosition = 50;
|
||||
|
||||
double _jumpVelocity = 0.0;
|
||||
|
||||
double get groundYPos {
|
||||
return (gameRef.size.y / 2) - height / 2;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
// Body hitbox
|
||||
add(
|
||||
RectangleHitbox.relative(
|
||||
Vector2(0.7, 0.6),
|
||||
position: Vector2(0, height / 3),
|
||||
parentSize: size,
|
||||
),
|
||||
);
|
||||
// Head hitbox
|
||||
add(
|
||||
RectangleHitbox.relative(
|
||||
Vector2(0.45, 0.35),
|
||||
position: Vector2(width / 2, 0),
|
||||
parentSize: size,
|
||||
),
|
||||
);
|
||||
animations = {
|
||||
PlayerState.running: _getAnimation(
|
||||
size: Vector2(88.0, 90.0),
|
||||
frames: [Vector2(1514.0, 4.0), Vector2(1602.0, 4.0)],
|
||||
stepTime: 0.2,
|
||||
),
|
||||
PlayerState.waiting: _getAnimation(
|
||||
size: Vector2(88.0, 90.0),
|
||||
frames: [Vector2(76.0, 6.0)],
|
||||
),
|
||||
PlayerState.jumping: _getAnimation(
|
||||
size: Vector2(88.0, 90.0),
|
||||
frames: [Vector2(1339.0, 6.0)],
|
||||
),
|
||||
PlayerState.crashed: _getAnimation(
|
||||
size: Vector2(88.0, 90.0),
|
||||
frames: [Vector2(1782.0, 6.0)],
|
||||
),
|
||||
};
|
||||
current = PlayerState.waiting;
|
||||
}
|
||||
|
||||
void jump(double speed) {
|
||||
if (current == PlayerState.jumping) {
|
||||
return;
|
||||
}
|
||||
|
||||
current = PlayerState.jumping;
|
||||
_jumpVelocity = initialJumpVelocity - (speed / 500);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
y = groundYPos;
|
||||
_jumpVelocity = 0.0;
|
||||
current = PlayerState.running;
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
if (current == PlayerState.jumping) {
|
||||
y += _jumpVelocity;
|
||||
_jumpVelocity += gravity;
|
||||
if (y > groundYPos) {
|
||||
reset();
|
||||
}
|
||||
} else {
|
||||
y = groundYPos;
|
||||
}
|
||||
|
||||
if (gameRef.isIntro && x < startXPosition) {
|
||||
x += (startXPosition / introDuration) * dt * 5000;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onGameResize(Vector2 size) {
|
||||
super.onGameResize(size);
|
||||
y = groundYPos;
|
||||
}
|
||||
|
||||
@override
|
||||
void onCollisionStart(
|
||||
Set<Vector2> intersectionPoints,
|
||||
PositionComponent other,
|
||||
) {
|
||||
super.onCollisionStart(intersectionPoints, other);
|
||||
gameRef.gameOver();
|
||||
}
|
||||
|
||||
SpriteAnimation _getAnimation({
|
||||
required Vector2 size,
|
||||
required List<Vector2> frames,
|
||||
double stepTime = double.infinity,
|
||||
}) {
|
||||
return SpriteAnimation.spriteList(
|
||||
frames
|
||||
.map(
|
||||
(vector) => Sprite(
|
||||
gameRef.spriteImage,
|
||||
srcSize: size,
|
||||
srcPosition: vector,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
stepTime: stepTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
8
examples/lib/stories/games/trex/random_extension.dart
Normal file
8
examples/lib/stories/games/trex/random_extension.dart
Normal file
@ -0,0 +1,8 @@
|
||||
import 'dart:math';
|
||||
|
||||
Random random = Random();
|
||||
|
||||
extension RandomExtension on Random {
|
||||
double fromRange(double min, double max) =>
|
||||
(nextDouble() * (max - min + 1)).floor() + min;
|
||||
}
|
||||
133
examples/lib/stories/games/trex/trex_game.dart
Normal file
133
examples/lib/stories/games/trex/trex_game.dart
Normal file
@ -0,0 +1,133 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/flame.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flutter/material.dart' hide Image;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'background/horizon.dart';
|
||||
import 'game_over.dart';
|
||||
import 'player.dart';
|
||||
|
||||
enum GameState { playing, intro, gameOver }
|
||||
|
||||
class TRexGame extends FlameGame
|
||||
with KeyboardEvents, TapDetector, HasCollisionDetection {
|
||||
static const String description = '''
|
||||
A game similar to the game in chrome that you get to play while offline.
|
||||
Press space or tap/click the screen to jump, the more obstacles you manage
|
||||
to survive, the more points you get.
|
||||
''';
|
||||
|
||||
late final Image spriteImage;
|
||||
|
||||
@override
|
||||
Color backgroundColor() => const Color(0xFFFFFFFF);
|
||||
|
||||
late final player = Player();
|
||||
late final horizon = Horizon();
|
||||
late final gameOverPanel = GameOverPanel();
|
||||
late final TextComponent scoreText;
|
||||
|
||||
late int _score;
|
||||
int get score => _score;
|
||||
set score(int newScore) {
|
||||
_score = newScore;
|
||||
scoreText.text = 'Score:$score';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
spriteImage = await Flame.images.load('trex.png');
|
||||
add(horizon);
|
||||
add(player);
|
||||
add(gameOverPanel);
|
||||
|
||||
final textStyle = GoogleFonts.pressStart2p(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey.shade700,
|
||||
);
|
||||
final textPaint = TextPaint(style: textStyle);
|
||||
add(
|
||||
scoreText = TextComponent(
|
||||
position: Vector2(20, 20),
|
||||
textRenderer: textPaint,
|
||||
)..positionType = PositionType.viewport,
|
||||
);
|
||||
score = 0;
|
||||
}
|
||||
|
||||
GameState state = GameState.intro;
|
||||
double currentSpeed = 0.0;
|
||||
double timePlaying = 0.0;
|
||||
|
||||
final double acceleration = 10;
|
||||
final double maxSpeed = 2500.0;
|
||||
final double startSpeed = 600;
|
||||
|
||||
bool get isPlaying => state == GameState.playing;
|
||||
bool get isGameOver => state == GameState.gameOver;
|
||||
bool get isIntro => state == GameState.intro;
|
||||
|
||||
@override
|
||||
KeyEventResult onKeyEvent(
|
||||
RawKeyEvent event,
|
||||
Set<LogicalKeyboardKey> keysPressed,
|
||||
) {
|
||||
if (keysPressed.contains(LogicalKeyboardKey.enter) ||
|
||||
keysPressed.contains(LogicalKeyboardKey.space)) {
|
||||
onAction();
|
||||
}
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapDown(TapDownInfo info) {
|
||||
onAction();
|
||||
}
|
||||
|
||||
void onAction() {
|
||||
if (isGameOver || isIntro) {
|
||||
restart();
|
||||
return;
|
||||
}
|
||||
player.jump(currentSpeed);
|
||||
}
|
||||
|
||||
void gameOver() {
|
||||
gameOverPanel.visible = true;
|
||||
state = GameState.gameOver;
|
||||
player.current = PlayerState.crashed;
|
||||
currentSpeed = 0.0;
|
||||
}
|
||||
|
||||
void restart() {
|
||||
state = GameState.playing;
|
||||
player.reset();
|
||||
horizon.reset();
|
||||
currentSpeed = startSpeed;
|
||||
gameOverPanel.visible = false;
|
||||
timePlaying = 0.0;
|
||||
score = 0;
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
if (isGameOver) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
timePlaying += dt;
|
||||
|
||||
if (currentSpeed < maxSpeed) {
|
||||
currentSpeed += acceleration * dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,7 @@ dependencies:
|
||||
flame_svg: ^1.2.0
|
||||
flame_forge2d: ^0.11.0
|
||||
dashbook: 0.1.6
|
||||
google_fonts: 2.2.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
Reference in New Issue
Block a user