mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-10-31 08:56:01 +08:00 
			
		
		
		
	feat(flame): Add helper methods to create frame data on SpriteSheet (#2754)
				
					
				
			Add two methods to SpriteSheet to create frame data for SpriteAnimation
This commit is contained in:
		 Jochum van der Ploeg
					Jochum van der Ploeg
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							7313cd5352
						
					
				
				
					commit
					477221998a
				
			| @ -331,21 +331,37 @@ a very simple example of how to use it: | |||||||
| ```dart | ```dart | ||||||
| import 'package:flame/sprite.dart'; | import 'package:flame/sprite.dart'; | ||||||
|  |  | ||||||
| final spritesheet = SpriteSheet( | final spriteSheet = SpriteSheet( | ||||||
|   image: imageInstance, |   image: imageInstance, | ||||||
|   srcSize: Vector2.all(16.0), |   srcSize: Vector2.all(16.0), | ||||||
| ); | ); | ||||||
|  |  | ||||||
| final animation = spritesheet.createAnimation(0, stepTime: 0.1); | final animation = spriteSheet.createAnimation(0, stepTime: 0.1); | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Now you can use the animation directly or use it in an animation component. | Now you can use the animation directly or use it in an animation component. | ||||||
|  |  | ||||||
| You can also get a single frame of the sprite sheet using the `getSprite` method: | You can also create a custom animation by retrieving individual `SpriteAnimationFrameData` using | ||||||
|  | either `SpriteSheet.createFrameData` or `SpriteSheet.createFrameDataFromId`: | ||||||
|  |  | ||||||
| ```dart | ```dart | ||||||
| spritesheet.getSprite(0, 0) // row, column; | final animation = SpriteAnimation.fromFrameData( | ||||||
|  |   imageInstance,  | ||||||
|  |   SpriteAnimationData([ | ||||||
|  |     spriteSheet.createFrameDataFromId(1, stepTime: 0.1), // by id | ||||||
|  |     spriteSheet.createFrameData(2, 3, stepTime: 0.3), // row, column | ||||||
|  |     spriteSheet.createFrameDataFromId(4, stepTime: 0.1), // by id | ||||||
|  |   ]), | ||||||
|  | ); | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If you don't need any kind of animation and instead only want an instance of a `Sprite` on the | ||||||
|  | `SpriteSheet` you can use the `getSprite` or `getSpriteById` methods: | ||||||
|  |  | ||||||
|  | ```dart | ||||||
|  | spriteSheet.getSpriteById(2); // by id | ||||||
|  | spriteSheet.getSprite(0, 0); // row, column | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| You can see a full example of the `SpriteSheet` class | You can see a full example of the `SpriteSheet` class | ||||||
| [here](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/sprites/spritesheet_example.dart). | [here](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/sprites/sprite_sheet_example.dart). | ||||||
|  | |||||||
| @ -308,7 +308,7 @@ it's fully played during the `Particle` lifespan. It's possible to override this | |||||||
| `alignAnimationTime` argument. | `alignAnimationTime` argument. | ||||||
|  |  | ||||||
| ```dart | ```dart | ||||||
| final spritesheet = SpriteSheet( | final spriteSheet = SpriteSheet( | ||||||
|   image: yourSpriteSheetImage, |   image: yourSpriteSheetImage, | ||||||
|   srcSize: Vector2.all(16.0), |   srcSize: Vector2.all(16.0), | ||||||
| ); | ); | ||||||
| @ -316,7 +316,7 @@ final spritesheet = SpriteSheet( | |||||||
| game.add( | game.add( | ||||||
|   ParticleSystemComponent( |   ParticleSystemComponent( | ||||||
|     particle: SpriteAnimationParticle( |     particle: SpriteAnimationParticle( | ||||||
|       animation: spritesheet.createAnimation(0, stepTime: 0.1), |       animation: spriteSheet.createAnimation(0, stepTime: 0.1), | ||||||
|     ); |     ); | ||||||
|   ), |   ), | ||||||
| ); | ); | ||||||
|  | |||||||
| Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB | 
| @ -501,12 +501,12 @@ class ParticlesExample extends FlameGame { | |||||||
|     const rows = 8; |     const rows = 8; | ||||||
|     const frames = columns * rows; |     const frames = columns * rows; | ||||||
|     final spriteImage = images.fromCache('boom.png'); |     final spriteImage = images.fromCache('boom.png'); | ||||||
|     final spritesheet = SpriteSheet.fromColumnsAndRows( |     final spriteSheet = SpriteSheet.fromColumnsAndRows( | ||||||
|       image: spriteImage, |       image: spriteImage, | ||||||
|       columns: columns, |       columns: columns, | ||||||
|       rows: rows, |       rows: rows, | ||||||
|     ); |     ); | ||||||
|     final sprites = List<Sprite>.generate(frames, spritesheet.getSpriteById); |     final sprites = List<Sprite>.generate(frames, spriteSheet.getSpriteById); | ||||||
|     return SpriteAnimation.spriteList(sprites, stepTime: 0.1); |     return SpriteAnimation.spriteList(sprites, stepTime: 0.1); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ import 'package:flame/components.dart'; | |||||||
| import 'package:flame/game.dart'; | import 'package:flame/game.dart'; | ||||||
| import 'package:flame/sprite.dart'; | import 'package:flame/sprite.dart'; | ||||||
| 
 | 
 | ||||||
| class SpritesheetExample extends FlameGame { | class SpriteSheetExample extends FlameGame { | ||||||
|   static const String description = ''' |   static const String description = ''' | ||||||
|     In this example we show how to load images and how to create animations from |     In this example we show how to load images and how to create animations from | ||||||
|     sprite sheets. |     sprite sheets. | ||||||
| @ -11,7 +11,7 @@ class SpritesheetExample extends FlameGame { | |||||||
|   @override |   @override | ||||||
|   Future<void> onLoad() async { |   Future<void> onLoad() async { | ||||||
|     final spriteSheet = SpriteSheet( |     final spriteSheet = SpriteSheet( | ||||||
|       image: await images.load('spritesheet.png'), |       image: await images.load('sprite_sheet.png'), | ||||||
|       srcSize: Vector2(16.0, 18.0), |       srcSize: Vector2(16.0, 18.0), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
| @ -28,6 +28,19 @@ class SpritesheetExample extends FlameGame { | |||||||
|       stepTimes: [0.1, 0.1, 0.3, 0.3, 0.5, 0.3, 0.1], |       stepTimes: [0.1, 0.1, 0.3, 0.3, 0.5, 0.3, 0.1], | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     final customVampireAnimation = SpriteAnimation.fromFrameData( | ||||||
|  |       spriteSheet.image, | ||||||
|  |       SpriteAnimationData([ | ||||||
|  |         spriteSheet.createFrameData(0, 0, stepTime: 0.1), | ||||||
|  |         spriteSheet.createFrameData(0, 1, stepTime: 0.1), | ||||||
|  |         spriteSheet.createFrameData(0, 2, stepTime: 0.3), | ||||||
|  |         spriteSheet.createFrameDataFromId(4, stepTime: 0.3), | ||||||
|  |         spriteSheet.createFrameDataFromId(5, stepTime: 0.5), | ||||||
|  |         spriteSheet.createFrameDataFromId(6, stepTime: 0.3), | ||||||
|  |         spriteSheet.createFrameDataFromId(7, stepTime: 0.1), | ||||||
|  |       ]), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|     final spriteSize = Vector2(80.0, 90.0); |     final spriteSize = Vector2(80.0, 90.0); | ||||||
| 
 | 
 | ||||||
|     final vampireComponent = SpriteAnimationComponent( |     final vampireComponent = SpriteAnimationComponent( | ||||||
| @ -44,13 +57,20 @@ class SpritesheetExample extends FlameGame { | |||||||
| 
 | 
 | ||||||
|     final ghostAnimationVariableStepTimesComponent = SpriteAnimationComponent( |     final ghostAnimationVariableStepTimesComponent = SpriteAnimationComponent( | ||||||
|       animation: ghostAnimationVariableStepTimes, |       animation: ghostAnimationVariableStepTimes, | ||||||
|       position: Vector2(150, 340), |       position: Vector2(250, 220), | ||||||
|  |       size: spriteSize, | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     final customVampireComponent = SpriteAnimationComponent( | ||||||
|  |       animation: customVampireAnimation, | ||||||
|  |       position: Vector2(250, 100), | ||||||
|       size: spriteSize, |       size: spriteSize, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     add(vampireComponent); |     add(vampireComponent); | ||||||
|     add(ghostComponent); |     add(ghostComponent); | ||||||
|     add(ghostAnimationVariableStepTimesComponent); |     add(ghostAnimationVariableStepTimesComponent); | ||||||
|  |     add(customVampireComponent); | ||||||
| 
 | 
 | ||||||
|     // Some plain sprites |     // Some plain sprites | ||||||
|     final vampireSpriteComponent = SpriteComponent( |     final vampireSpriteComponent = SpriteComponent( | ||||||
| @ -65,14 +85,7 @@ class SpritesheetExample extends FlameGame { | |||||||
|       position: Vector2(50, 220), |       position: Vector2(50, 220), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     final ghostVariableSpriteComponent = SpriteComponent( |  | ||||||
|       sprite: spriteSheet.getSprite(1, 0), |  | ||||||
|       size: spriteSize, |  | ||||||
|       position: Vector2(50, 340), |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     add(vampireSpriteComponent); |     add(vampireSpriteComponent); | ||||||
|     add(ghostSpriteComponent); |     add(ghostSpriteComponent); | ||||||
|     add(ghostVariableSpriteComponent); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @ -5,7 +5,7 @@ import 'package:examples/stories/sprites/basic_sprite_example.dart'; | |||||||
| import 'package:examples/stories/sprites/sprite_batch_example.dart'; | import 'package:examples/stories/sprites/sprite_batch_example.dart'; | ||||||
| import 'package:examples/stories/sprites/sprite_batch_load_example.dart'; | import 'package:examples/stories/sprites/sprite_batch_load_example.dart'; | ||||||
| import 'package:examples/stories/sprites/sprite_group_example.dart'; | import 'package:examples/stories/sprites/sprite_group_example.dart'; | ||||||
| import 'package:examples/stories/sprites/spritesheet_example.dart'; | import 'package:examples/stories/sprites/sprite_sheet_example.dart'; | ||||||
| import 'package:flame/game.dart'; | import 'package:flame/game.dart'; | ||||||
|  |  | ||||||
| void addSpritesStories(Dashbook dashbook) { | void addSpritesStories(Dashbook dashbook) { | ||||||
| @ -23,10 +23,10 @@ void addSpritesStories(Dashbook dashbook) { | |||||||
|       info: Base64SpriteExample.description, |       info: Base64SpriteExample.description, | ||||||
|     ) |     ) | ||||||
|     ..add( |     ..add( | ||||||
|       'Spritesheet', |       'SpriteSheet', | ||||||
|       (_) => GameWidget(game: SpritesheetExample()), |       (_) => GameWidget(game: SpriteSheetExample()), | ||||||
|       codeLink: baseLink('sprites/spritesheet_example.dart'), |       codeLink: baseLink('sprites/sprite_sheet_example.dart'), | ||||||
|       info: SpritesheetExample.description, |       info: SpriteSheetExample.description, | ||||||
|     ) |     ) | ||||||
|     ..add( |     ..add( | ||||||
|       'SpriteBatch', |       'SpriteBatch', | ||||||
|  | |||||||
| @ -7,4 +7,4 @@ export 'src/sprite.dart'; | |||||||
| export 'src/sprite_animation.dart'; | export 'src/sprite_animation.dart'; | ||||||
| export 'src/sprite_animation_ticker.dart'; | export 'src/sprite_animation_ticker.dart'; | ||||||
| export 'src/sprite_batch.dart' hide FlippedAtlasStatus; | export 'src/sprite_batch.dart' hide FlippedAtlasStatus; | ||||||
| export 'src/spritesheet.dart'; | export 'src/sprite_sheet.dart'; | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import 'dart:ui'; | import 'dart:ui'; | ||||||
|  |  | ||||||
| import 'package:flame/components.dart'; | import 'package:flame/components.dart'; | ||||||
| import 'package:flame/src/spritesheet.dart'; | import 'package:flame/src/sprite_sheet.dart'; | ||||||
| import 'package:meta/meta.dart'; | import 'package:meta/meta.dart'; | ||||||
|  |  | ||||||
| /// This is just a pair of <int, int>. | /// This is just a pair of <int, int>. | ||||||
|  | |||||||
| @ -68,6 +68,34 @@ class SpriteSheet { | |||||||
|     return _spriteCache[spriteId] ??= _computeSprite(spriteId); |     return _spriteCache[spriteId] ??= _computeSprite(spriteId); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Create a [SpriteAnimationFrameData] for the sprite in the position | ||||||
|  |   /// (row, column) on the sprite sheet grid. | ||||||
|  |   SpriteAnimationFrameData createFrameData( | ||||||
|  |     int row, | ||||||
|  |     int column, { | ||||||
|  |     required double stepTime, | ||||||
|  |   }) { | ||||||
|  |     return createFrameDataFromId(row * columns + column, stepTime: stepTime); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Create a [SpriteAnimationFrameData] for the sprite with id [spriteId] | ||||||
|  |   /// from the grid. | ||||||
|  |   /// | ||||||
|  |   /// The ids are defined as starting at 0 on the top left and going | ||||||
|  |   /// sequentially on each row. | ||||||
|  |   SpriteAnimationFrameData createFrameDataFromId( | ||||||
|  |     int spriteId, { | ||||||
|  |     required double stepTime, | ||||||
|  |   }) { | ||||||
|  |     final i = spriteId % columns; | ||||||
|  |     final j = spriteId ~/ columns; | ||||||
|  |     return SpriteAnimationFrameData( | ||||||
|  |       srcPosition: Vector2Extension.fromInts(i, j)..multiply(srcSize), | ||||||
|  |       srcSize: srcSize, | ||||||
|  |       stepTime: stepTime, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   Sprite _computeSprite(int spriteId) { |   Sprite _computeSprite(int spriteId) { | ||||||
|     final i = spriteId % columns; |     final i = spriteId % columns; | ||||||
|     final j = spriteId ~/ columns; |     final j = spriteId ~/ columns; | ||||||
| @ -5,7 +5,7 @@ import 'package:flame/input.dart'; | |||||||
| import 'package:flame/src/anchor.dart'; | import 'package:flame/src/anchor.dart'; | ||||||
| import 'package:flame/src/components/sprite_group_component.dart'; | import 'package:flame/src/components/sprite_group_component.dart'; | ||||||
| import 'package:flame/src/events/flame_game_mixins/multi_tap_dispatcher.dart'; | import 'package:flame/src/events/flame_game_mixins/multi_tap_dispatcher.dart'; | ||||||
| import 'package:flame/src/spritesheet.dart'; | import 'package:flame/src/sprite_sheet.dart'; | ||||||
| import 'package:flame_test/flame_test.dart'; | import 'package:flame_test/flame_test.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_test/flutter_test.dart'; | import 'package:flutter_test/flutter_test.dart'; | ||||||
|  | |||||||
| @ -54,5 +54,69 @@ void main() { | |||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     test('return sprite based on row and column', () { | ||||||
|  |       final spriteSheet = SpriteSheet( | ||||||
|  |         image: image, | ||||||
|  |         srcSize: Vector2(50, 50), | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       expect( | ||||||
|  |         spriteSheet.getSprite(1, 1), | ||||||
|  |         isA<Sprite>().having( | ||||||
|  |           (sprite) => sprite.srcPosition, | ||||||
|  |           'srcPosition', | ||||||
|  |           equals(Vector2(50, 50)), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test('return sprite based on id', () { | ||||||
|  |       final spriteSheet = SpriteSheet( | ||||||
|  |         image: image, | ||||||
|  |         srcSize: Vector2(50, 50), | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       expect( | ||||||
|  |         spriteSheet.getSpriteById(3), | ||||||
|  |         isA<Sprite>().having( | ||||||
|  |           (sprite) => sprite.srcPosition, | ||||||
|  |           'srcPosition', | ||||||
|  |           equals(Vector2(50, 50)), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test('create sprite animation frame data based on row and column', () { | ||||||
|  |       final spriteSheet = SpriteSheet( | ||||||
|  |         image: image, | ||||||
|  |         srcSize: Vector2(50, 50), | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       expect( | ||||||
|  |         spriteSheet.createFrameData(1, 1, stepTime: 0.1), | ||||||
|  |         isA<SpriteAnimationFrameData>().having( | ||||||
|  |           (frame) => frame.srcPosition, | ||||||
|  |           'srcPosition', | ||||||
|  |           equals(Vector2(50, 50)), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     test('create sprite animation frame data based on id', () { | ||||||
|  |       final spriteSheet = SpriteSheet( | ||||||
|  |         image: image, | ||||||
|  |         srcSize: Vector2(50, 50), | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       expect( | ||||||
|  |         spriteSheet.createFrameDataFromId(3, stepTime: 0.1), | ||||||
|  |         isA<SpriteAnimationFrameData>().having( | ||||||
|  |           (frame) => frame.srcPosition, | ||||||
|  |           'srcPosition', | ||||||
|  |           equals(Vector2(50, 50)), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user