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
	 Lukas Klingsbo
					Lukas Klingsbo