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
import 'package:flame/sprite.dart';
final spritesheet = SpriteSheet(
final spriteSheet = SpriteSheet(
image: imageInstance,
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.
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
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
[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.
```dart
final spritesheet = SpriteSheet(
final spriteSheet = SpriteSheet(
image: yourSpriteSheetImage,
srcSize: Vector2.all(16.0),
);
@ -316,7 +316,7 @@ final spritesheet = SpriteSheet(
game.add(
ParticleSystemComponent(
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 frames = columns * rows;
final spriteImage = images.fromCache('boom.png');
final spritesheet = SpriteSheet.fromColumnsAndRows(
final spriteSheet = SpriteSheet.fromColumnsAndRows(
image: spriteImage,
columns: columns,
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);
}
}

View File

@ -2,7 +2,7 @@ import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/sprite.dart';
class SpritesheetExample extends FlameGame {
class SpriteSheetExample extends FlameGame {
static const String description = '''
In this example we show how to load images and how to create animations from
sprite sheets.
@ -11,7 +11,7 @@ class SpritesheetExample extends FlameGame {
@override
Future<void> onLoad() async {
final spriteSheet = SpriteSheet(
image: await images.load('spritesheet.png'),
image: await images.load('sprite_sheet.png'),
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],
);
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 vampireComponent = SpriteAnimationComponent(
@ -44,13 +57,20 @@ class SpritesheetExample extends FlameGame {
final ghostAnimationVariableStepTimesComponent = SpriteAnimationComponent(
animation: ghostAnimationVariableStepTimes,
position: Vector2(150, 340),
position: Vector2(250, 220),
size: spriteSize,
);
final customVampireComponent = SpriteAnimationComponent(
animation: customVampireAnimation,
position: Vector2(250, 100),
size: spriteSize,
);
add(vampireComponent);
add(ghostComponent);
add(ghostAnimationVariableStepTimesComponent);
add(customVampireComponent);
// Some plain sprites
final vampireSpriteComponent = SpriteComponent(
@ -65,14 +85,7 @@ class SpritesheetExample extends FlameGame {
position: Vector2(50, 220),
);
final ghostVariableSpriteComponent = SpriteComponent(
sprite: spriteSheet.getSprite(1, 0),
size: spriteSize,
position: Vector2(50, 340),
);
add(vampireSpriteComponent);
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_load_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';
void addSpritesStories(Dashbook dashbook) {
@ -23,10 +23,10 @@ void addSpritesStories(Dashbook dashbook) {
info: Base64SpriteExample.description,
)
..add(
'Spritesheet',
(_) => GameWidget(game: SpritesheetExample()),
codeLink: baseLink('sprites/spritesheet_example.dart'),
info: SpritesheetExample.description,
'SpriteSheet',
(_) => GameWidget(game: SpriteSheetExample()),
codeLink: baseLink('sprites/sprite_sheet_example.dart'),
info: SpriteSheetExample.description,
)
..add(
'SpriteBatch',

View File

@ -7,4 +7,4 @@ export 'src/sprite.dart';
export 'src/sprite_animation.dart';
export 'src/sprite_animation_ticker.dart';
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 'package:flame/components.dart';
import 'package:flame/src/spritesheet.dart';
import 'package:flame/src/sprite_sheet.dart';
import 'package:meta/meta.dart';
/// This is just a pair of <int, int>.

View File

@ -68,6 +68,34 @@ class SpriteSheet {
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) {
final i = 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/components/sprite_group_component.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:flutter/material.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)),
),
);
});
});
}