mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-11-01 01:18:38 +08:00 
			
		
		
		
	docs: Update platformer tutorial to latest Flame (#2904)
Updates the platformer tutorial to Flame v1.11.0
This commit is contained in:
		
							
								
								
									
										1
									
								
								.github/.cspell/gamedev_dictionary.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/.cspell/gamedev_dictionary.txt
									
									
									
									
										vendored
									
									
								
							| @ -45,7 +45,6 @@ raycasts # plural of raycast | ||||
| raytrace # act of raytracing | ||||
| raytracing # rendering techniques that calculates light rays as straight lines | ||||
| rects # plural of rect | ||||
| refactorings # plural of refactoring | ||||
| respawned # past tense of respawn | ||||
| respawn # when the player character dies and is brought back after some time and penalties | ||||
| RGBA # red green blue alpha | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/.cspell/words_dictionary.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/.cspell/words_dictionary.txt
									
									
									
									
										vendored
									
									
								
							| @ -2,8 +2,6 @@ | ||||
| bloodlust | ||||
| collidable | ||||
| collidables | ||||
| composability | ||||
| discoverability | ||||
| draggables | ||||
| focusable | ||||
| gamepad | ||||
|  | ||||
| @ -23,7 +23,7 @@ void main() { | ||||
|  | ||||
| Like in the [klondike](../klondike/klondike.md) tutorial, starting a new game can feel overwhelming. | ||||
| I like to first decide what platform I am trying to target. Will this be a mobile game, a desktop | ||||
| game, or maybe a web game, with Flutter and Flame, these are all possible.  For this game though, I | ||||
| game, or maybe a web game, with Flutter and Flame, these are all possible. For this game though, I | ||||
| am going to focus on a web game. This means my users will interact with the game using their | ||||
| keyboards. | ||||
|  | ||||
| @ -45,16 +45,16 @@ All of these will be brought together in `EmberQuestGame` derived from `FlameGam | ||||
|  | ||||
| ## Assets | ||||
|  | ||||
| Every game needs assets.  Assets are images, sprites, animations, sounds, etc. Now, I am not an | ||||
| Every game needs assets. Assets are images, sprites, animations, sounds, etc. Now, I am not an | ||||
| artist, but because I am basing this game on Ember, the flame mascot, and Ember is already designed, | ||||
| it sets the tone that this will be a pixel art game.  There are numerous sites available that | ||||
| it sets the tone that this will be a pixel art game. There are numerous sites available that | ||||
| provide free pixel art that can be used in games, but please check and comply with the licensing and | ||||
| always provide valid creator attribution.  For this game though, I am going to take a chance and | ||||
| make my artwork using an online pixel art tool.  If you decide to use this tool, multiple online | ||||
| tutorials will assist you with the basic operations as well as exporting the assets.  Now normally, | ||||
| most games will utilize sprite sheets.  These combine many images into one larger image that can be | ||||
| sectioned and used as individual images.  For this tutorial though, I specifically will save the | ||||
| images individually as I want to demonstrate the Flame engine's caching abilities.  Ember and the | ||||
| always provide valid creator attribution. For this game though, I am going to take a chance and | ||||
| make my artwork using an online pixel art tool. If you decide to use this tool, multiple online | ||||
| tutorials will assist you with the basic operations as well as exporting the assets. Now normally, | ||||
| most games will utilize sprite sheets. These combine many images into one larger image that can be | ||||
| sectioned and used as individual images. For this tutorial though, I specifically will save the | ||||
| images individually as I want to demonstrate the Flame engine's caching abilities. Ember and the | ||||
| water enemy are sprite sheets though as they contain multiple images to create animations. | ||||
|  | ||||
| Right-click the images below, choose "Save as...", and store them in the `assets/images` folder of the | ||||
| @ -88,8 +88,8 @@ emberquest/ | ||||
| You may ask, why are the images different sizes? | ||||
|  | ||||
| As I was using the online tool to make the assets, I had trouble getting the | ||||
| detail I desired for the game in a 16x16 block.  The heart worked out in 32x32  | ||||
| and the ground as well as the star were 64x64.  Regardless, the asset size does | ||||
| detail I desired for the game in a 16x16 block. The heart worked out in 32x32  | ||||
| and the ground as well as the star were 64x64. Regardless, the asset size does | ||||
| not matter for the game as we will resize as needed. | ||||
| ``` | ||||
|  | ||||
|  | ||||
| @ -91,20 +91,15 @@ You can run this file and you should just have a blank screen now. Let's get Emb | ||||
|  | ||||
| ## CameraComponent and World | ||||
|  | ||||
| Since `FlameGame.camera` is deprecated, we want to add a `CameraComponent` that we can move around, | ||||
| and a world that we can add all our components to and move around our player in. | ||||
| (The `CameraComponent` will be built-in in Flame v2). | ||||
| To move around in the world we are going the use the built-in `CameraComponent` and `World` that | ||||
| exists on the `FlameGame` class. | ||||
| We are going to add all our components to the `world` and follow the player with the `camera`. | ||||
|  | ||||
| ```dart | ||||
| import 'package:flame/components.dart'; | ||||
| import 'package:flame/game.dart'; | ||||
|  | ||||
| class EmberQuestGame extends FlameGame { | ||||
|   EmberQuestGame(); | ||||
|    | ||||
|   final world = World(); | ||||
|   late final CameraComponent cameraComponent; | ||||
|  | ||||
|   @override | ||||
|   Future<void> onLoad() async { | ||||
|     await images.loadAll([ | ||||
| @ -117,12 +112,10 @@ class EmberQuestGame extends FlameGame { | ||||
|       'water_enemy.png', | ||||
|     ]); | ||||
|  | ||||
|     cameraComponent = CameraComponent(world: world); | ||||
|     // Everything in this tutorial assumes that the position | ||||
|     // of the `CameraComponent`s viewfinder (where the camera is looking) | ||||
|     // is in the top left corner, that's why we set the anchor here. | ||||
|     cameraComponent.viewfinder.anchor = Anchor.topLeft; | ||||
|     addAll([cameraComponent, world]); | ||||
|     camera.viewfinder.anchor = Anchor.topLeft; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| @ -140,7 +133,7 @@ import 'package:flame/components.dart'; | ||||
| import '../ember_quest.dart'; | ||||
|  | ||||
| class EmberPlayer extends SpriteAnimationComponent | ||||
|     with HasGameRef<EmberQuestGame> { | ||||
|     with HasGameReference<EmberQuestGame> { | ||||
|   EmberPlayer({ | ||||
|     required super.position, | ||||
|   }) : super(size: Vector2.all(64), anchor: Anchor.center); | ||||
| @ -181,13 +174,8 @@ import 'package:flame/game.dart'; | ||||
| import 'actors/ember.dart'; | ||||
|  | ||||
| class EmberQuestGame extends FlameGame { | ||||
|   EmberQuestGame(); | ||||
|  | ||||
|   late EmberPlayer _ember; | ||||
|    | ||||
|   final world = World(); | ||||
|   late final CameraComponent cameraComponent; | ||||
|  | ||||
|   @override | ||||
|   Future<void> onLoad() async { | ||||
|     await images.loadAll([ | ||||
| @ -200,9 +188,7 @@ class EmberQuestGame extends FlameGame { | ||||
|       'water_enemy.png', | ||||
|     ]); | ||||
|  | ||||
|     cameraComponent = CameraComponent(world: world); | ||||
|     cameraComponent.viewfinder.anchor = Anchor.topLeft; | ||||
|     addAll([cameraComponent, world]); | ||||
|     camera.viewfinder.anchor = Anchor.topLeft; | ||||
|      | ||||
|     _ember = EmberPlayer( | ||||
|       position: Vector2(128, canvasSize.y - 70), | ||||
|  | ||||
| @ -26,17 +26,17 @@ so first create a new folder called `lib/objects`. In that folder, create 3 file | ||||
| boilerplate code for the class, so create the following in their respective files: | ||||
|  | ||||
| ```dart | ||||
| class GroundBlock{} | ||||
| class GroundBlock {} | ||||
|  | ||||
| class PlatformBlock{} | ||||
| class PlatformBlock {} | ||||
|  | ||||
| class Star{} | ||||
| class Star {} | ||||
| ``` | ||||
|  | ||||
| Also, create `water_enemy.dart` in the `lib/actors` folder using this boilerplate code: | ||||
|  | ||||
| ```dart | ||||
| class WaterEnemy{} | ||||
| class WaterEnemy {} | ||||
| ``` | ||||
|  | ||||
| Now we can create a file called `segment_manager.dart` which will be placed in a new folder called | ||||
| @ -300,10 +300,7 @@ as: | ||||
|       'water_enemy.png', | ||||
|     ]); | ||||
|      | ||||
|     cameraComponent = CameraComponent(world: world); | ||||
|     cameraComponent.viewfinder.anchor = Anchor.topLeft; | ||||
|     addAll([cameraComponent, world]); | ||||
|  | ||||
|     camera.viewfinder.anchor = Anchor.topLeft; | ||||
|     initializeGame(); | ||||
|   } | ||||
| ``` | ||||
| @ -329,7 +326,7 @@ import 'package:flame/components.dart'; | ||||
| import '../ember_quest.dart'; | ||||
|  | ||||
| class PlatformBlock extends SpriteComponent | ||||
|     with HasGameRef<EmberQuestGame> { | ||||
|     with HasGameReference<EmberQuestGame> { | ||||
|   final Vector2 gridPosition; | ||||
|   double xOffset; | ||||
|  | ||||
| @ -433,11 +430,11 @@ the block in a `Vector2`. So add the following to your `loadGameSegments` method | ||||
|  | ||||
| ```dart | ||||
| case PlatformBlock: | ||||
|     add(PlatformBlock( | ||||
|       gridPosition: block.gridPosition, | ||||
|       xOffset: xPositionOffset, | ||||
|     )); | ||||
|     break; | ||||
|   add(PlatformBlock( | ||||
|     gridPosition: block.gridPosition, | ||||
|     xOffset: xPositionOffset, | ||||
|   )); | ||||
|   break; | ||||
| ``` | ||||
|  | ||||
| If you run your code, you should now see: | ||||
| @ -453,7 +450,7 @@ import 'package:flutter/material.dart'; | ||||
|  | ||||
| @override | ||||
| Color backgroundColor() { | ||||
|     return const Color.fromARGB(255, 173, 223, 247); | ||||
|   return const Color.fromARGB(255, 173, 223, 247); | ||||
| } | ||||
| ``` | ||||
|  | ||||
|  | ||||
| @ -17,7 +17,7 @@ import 'package:flutter/material.dart'; | ||||
| import '../ember_quest.dart'; | ||||
|  | ||||
| class Star extends SpriteComponent | ||||
|     with HasGameRef<EmberQuestGame> { | ||||
|     with HasGameReference<EmberQuestGame> { | ||||
|   final Vector2 gridPosition; | ||||
|   double xOffset; | ||||
|  | ||||
| @ -65,7 +65,7 @@ So the only change between the Star and the Platform beyond the anchor is simply | ||||
| ```dart | ||||
| add( | ||||
|   SizeEffect.by( | ||||
|   Vector2(-24, -24), | ||||
|     Vector2(-24, -24), | ||||
|     EffectController( | ||||
|       duration: .75, | ||||
|       reverseDuration: .5, | ||||
| @ -76,24 +76,24 @@ add( | ||||
| ); | ||||
| ``` | ||||
|  | ||||
| The `SizeEffect` is best explained by going to their [help | ||||
| docs](../../flame/effects.md#sizeeffectby). In short, we simply reduce the size of the star | ||||
| The `SizeEffect` is best explained by going to their | ||||
| [docs](../../flame/effects.md#sizeeffectby). In short, we simply reduce the size of the star | ||||
| by -24 pixels in both directions and we make it pulse infinitely using the `EffectController`. | ||||
|  | ||||
| Don't forget to add the star to your `lib/ember_quest.dart` file by doing: | ||||
|  | ||||
| ```dart | ||||
| case Star: | ||||
|     add( | ||||
|       Star( | ||||
|         gridPosition: block.gridPosition, | ||||
|         xOffset: xPositionOffset, | ||||
|       ), | ||||
|     ); | ||||
|     break; | ||||
|   world.add( | ||||
|     Star( | ||||
|       gridPosition: block.gridPosition, | ||||
|       xOffset: xPositionOffset, | ||||
|     ), | ||||
|   ); | ||||
|   break; | ||||
| ``` | ||||
|  | ||||
| If you run your game, you should now see pulsing stars! | ||||
| If you run your game, you should now see pulsating stars! | ||||
|  | ||||
|  | ||||
| ## Water Enemy | ||||
| @ -109,7 +109,7 @@ import 'package:flame/effects.dart'; | ||||
| import '../ember_quest.dart'; | ||||
|  | ||||
| class WaterEnemy extends SpriteAnimationComponent | ||||
|     with HasGameRef<EmberQuestGame> { | ||||
|     with HasGameReference<EmberQuestGame> { | ||||
|   final Vector2 gridPosition; | ||||
|   double xOffset; | ||||
|  | ||||
| @ -171,7 +171,7 @@ Don't forget to add the water enemy to your `lib/ember_quest.dart` file by doing | ||||
|  | ||||
| ```dart | ||||
| case WaterEnemy: | ||||
|     add( | ||||
|     world.add( | ||||
|       WaterEnemy( | ||||
|        gridPosition: block.gridPosition, | ||||
|        xOffset: xPositionOffset, | ||||
| @ -204,7 +204,7 @@ import 'package:flutter/material.dart'; | ||||
|  | ||||
| import '../ember_quest.dart'; | ||||
|  | ||||
| class GroundBlock extends SpriteComponent with HasGameRef<EmberQuestGame> { | ||||
| class GroundBlock extends SpriteComponent with HasGameReference<EmberQuestGame> { | ||||
|   final Vector2 gridPosition; | ||||
|   double xOffset; | ||||
|  | ||||
| @ -253,8 +253,8 @@ Now in your Ground Block's `onLoad` method, add the following at the end of the | ||||
|  | ||||
| ```dart | ||||
| if (gridPosition.x == 9 && position.x > game.lastBlockXPosition) { | ||||
|     game.lastBlockKey = _blockKey; | ||||
|     game.lastBlockXPosition = position.x + size.x; | ||||
|   game.lastBlockKey = _blockKey; | ||||
|   game.lastBlockXPosition = position.x + size.x; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| @ -334,7 +334,7 @@ import 'package:flutter/material.dart'; | ||||
| import '../ember_quest.dart'; | ||||
| import '../managers/segment_manager.dart'; | ||||
|  | ||||
| class GroundBlock extends SpriteComponent with HasGameRef<EmberQuestGame> { | ||||
| class GroundBlock extends SpriteComponent with HasGameReference<EmberQuestGame> { | ||||
|   final Vector2 gridPosition; | ||||
|   double xOffset; | ||||
|    | ||||
| @ -391,13 +391,13 @@ Finally, don't forget to add your Ground Block to `lib/ember_quest.dart` by addi | ||||
|  | ||||
| ```dart | ||||
| case GroundBlock: | ||||
|     add( | ||||
|       GroundBlock( | ||||
|         gridPosition: block.gridPosition, | ||||
|         xOffset: xPositionOffset, | ||||
|       ), | ||||
|     ); | ||||
|     break; | ||||
|   world.add( | ||||
|     GroundBlock( | ||||
|       gridPosition: block.gridPosition, | ||||
|       xOffset: xPositionOffset, | ||||
|     ), | ||||
|   ); | ||||
|   break; | ||||
| ``` | ||||
|  | ||||
| If you run your code, your game should now look like this: | ||||
|  | ||||
| @ -20,7 +20,7 @@ class EmberQuestGame extends FlameGame with HasKeyboardHandlerComponents { | ||||
|  | ||||
| ```dart | ||||
| class EmberPlayer extends SpriteAnimationComponent | ||||
|     with KeyboardHandler, HasGameRef<EmberQuestGame> { | ||||
|     with KeyboardHandler, HasGameReference<EmberQuestGame> { | ||||
| ``` | ||||
|  | ||||
| Now we can add a new method: | ||||
| @ -118,7 +118,7 @@ Next, add the `CollisionCallbacks` mixin to `lib/actors/ember.dart` like: | ||||
|  | ||||
| ```dart | ||||
| class EmberPlayer extends SpriteAnimationComponent | ||||
|     with KeyboardHandler, CollisionCallbacks, HasGameRef<EmberQuestGame> { | ||||
|     with KeyboardHandler, CollisionCallbacks, HasGameReference<EmberQuestGame> { | ||||
| ``` | ||||
|  | ||||
| If it did not auto-import, you will need the following: | ||||
| @ -176,9 +176,7 @@ For the collisions to be activated for Ember, we need to add a `CircleHitbox`, s | ||||
| method, add the following: | ||||
|  | ||||
| ```dart | ||||
| add( | ||||
|   CircleHitbox(), | ||||
| ); | ||||
| add(CircleHitbox()); | ||||
| ``` | ||||
|  | ||||
| Now that we have the basic collisions created, we can add gravity so Ember exists in a game world | ||||
|  | ||||
| @ -26,7 +26,7 @@ enum HeartState { | ||||
| } | ||||
|  | ||||
| class HeartHealthComponent extends SpriteGroupComponent<HeartState> | ||||
|     with HasGameRef<EmberQuestGame> { | ||||
|     with HasGameReference<EmberQuestGame> { | ||||
|   final int heartNumber; | ||||
|  | ||||
|   HeartHealthComponent({ | ||||
| @ -88,7 +88,7 @@ import 'package:flutter/material.dart'; | ||||
| import '../ember_quest.dart'; | ||||
| import 'heart.dart'; | ||||
|  | ||||
| class Hud extends PositionComponent with HasGameRef<EmberQuestGame> { | ||||
| class Hud extends PositionComponent with HasGameReference<EmberQuestGame> { | ||||
|   Hud({ | ||||
|     super.position, | ||||
|     super.size, | ||||
| @ -152,7 +152,7 @@ the number of hearts necessary. The last step is to add the hud to the game. | ||||
| Go to `lib/ember_quest.dart` and add the following code in the `initializeGame` method: | ||||
|  | ||||
| ```dart | ||||
| cameraComponent.viewport.add(Hud()); | ||||
| camera.viewport.add(Hud()); | ||||
| ``` | ||||
|  | ||||
| If the auto-import did not occur, you will need to add: | ||||
|  | ||||
| @ -185,10 +185,8 @@ Future<void> onLoad() async { | ||||
|       'star.png', | ||||
|       'water_enemy.png', | ||||
|   ]); | ||||
|   cameraComponent = CameraComponent(world: world); | ||||
|   cameraComponent.viewfinder.anchor = Anchor.topLeft; | ||||
|   addAll([cameraComponent, world]); | ||||
|  | ||||
|    | ||||
|   camera.viewfinder.anchor = Anchor.topLeft; | ||||
|   initializeGame(true); | ||||
| } | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Lukas Klingsbo
					Lukas Klingsbo