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
2023-09-21 11:34:02 +02:00
committed by GitHub
parent 7313cd5352
commit 477221998a
16 changed files with 160 additions and 39 deletions

View File

@ -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).

View File

@ -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),
); );
), ),
); );

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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);
} }
} }

View File

@ -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);
} }
} }

View File

@ -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',

View File

@ -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';

View File

@ -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>.

View File

@ -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;

View File

@ -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';

View File

@ -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)),
),
);
});
}); });
} }