mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-10-31 17:06:50 +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/components/components.dart'; | ||||||
| import 'stories/effects/effects.dart'; | import 'stories/effects/effects.dart'; | ||||||
| import 'stories/experimental/experimental.dart'; | import 'stories/experimental/experimental.dart'; | ||||||
|  | import 'stories/games/games.dart'; | ||||||
| import 'stories/input/input.dart'; | import 'stories/input/input.dart'; | ||||||
| import 'stories/parallax/parallax.dart'; | import 'stories/parallax/parallax.dart'; | ||||||
| import 'stories/rendering/rendering.dart'; | import 'stories/rendering/rendering.dart'; | ||||||
| @ -23,6 +24,10 @@ void main() async { | |||||||
|     theme: ThemeData.dark(), |     theme: ThemeData.dark(), | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   // Some small sample games | ||||||
|  |   addGameStories(dashbook); | ||||||
|  |  | ||||||
|  |   // Feature examples | ||||||
|   addAnimationStories(dashbook); |   addAnimationStories(dashbook); | ||||||
|   addCameraAndViewportStories(dashbook); |   addCameraAndViewportStories(dashbook); | ||||||
|   addCollisionDetectionStories(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_svg: ^1.2.0 | ||||||
|   flame_forge2d: ^0.11.0 |   flame_forge2d: ^0.11.0 | ||||||
|   dashbook: 0.1.6 |   dashbook: 0.1.6 | ||||||
|  |   google_fonts: 2.2.0 | ||||||
|   flutter: |   flutter: | ||||||
|     sdk: flutter |     sdk: flutter | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Lukas Klingsbo
					Lukas Klingsbo