Skip to content

Commit

Permalink
feat: Enhance TexturePackerSprite (#3224)
Browse files Browse the repository at this point in the history
- Add feature to enable to use original size instead always to use
packed size
- Support trimmed/stripped whitespace with `useOriginalSize = true`
- Fix issue when rendering rotated image (wrong size and position)
  • Loading branch information
ani3llyon authored Jul 18, 2024
1 parent 9404241 commit 0b0a6c1
Show file tree
Hide file tree
Showing 17 changed files with 721 additions and 32 deletions.
15 changes: 13 additions & 2 deletions packages/flame_texturepacker/lib/flame_texturepacker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,25 @@ extension TexturepackerLoader on Game {
Future<TexturePackerAtlas> atlasFromAssets(
String assetsPath, {
Images? images,
bool useOriginalSize = true,
}) async =>
TexturePackerAtlas.load(assetsPath, images: images);
TexturePackerAtlas.load(
assetsPath,
images: images,
useOriginalSize: useOriginalSize,
);

/// Loads the specified pack file from storage
/// Uses the parent directory of the pack file to find the page images.
Future<TexturePackerAtlas> atlasFromStorage(
String storagePath, {
Images? images,
bool useOriginalSize = true,
}) async =>
TexturePackerAtlas.load(storagePath, fromStorage: true, images: images);
TexturePackerAtlas.load(
storagePath,
fromStorage: true,
images: images,
useOriginalSize: useOriginalSize,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ class TexturePackerAtlas {
/// returns a new [TexturePackerAtlas].
/// If [fromStorage] is true, the atlas will be loaded from the device's
/// storage instead of the assets folder.
///
/// If [useOriginalSize] is true, the sprites loaded will be use original size
/// instead of the packed size. For animation sprites, load with origin size
/// is recommended for smooth result.
static Future<TexturePackerAtlas> load(
String path, {
bool fromStorage = false,
Images? images,
bool useOriginalSize = true,
}) async {
final _TextureAtlasData atlasData;

Expand All @@ -35,7 +40,9 @@ class TexturePackerAtlas {
}

return TexturePackerAtlas(
atlasData.regions.map(TexturePackerSprite.new).toList(),
atlasData.regions
.map((e) => TexturePackerSprite(e, useOriginalSize: useOriginalSize))
.toList(),
);
}

Expand Down
128 changes: 103 additions & 25 deletions packages/flame_texturepacker/lib/src/texture_packer_sprite.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import 'package:flame_texturepacker/src/model/region.dart';
/// {@endtemplate}
class TexturePackerSprite extends Sprite {
/// {@macro _texture_packer_sprite}
TexturePackerSprite(Region region)
: name = region.name,
TexturePackerSprite(Region region, {this.useOriginalSize = true})
: _region = region,
name = region.name,
index = region.index,
offsetX = region.offsetX,
offsetY = region.offsetY,
packedWidth = region.rotate ? region.height : region.width,
packedHeight = region.rotate ? region.width : region.height,
packedWidth = region.width,
packedHeight = region.height,
originalWidth = region.originalWidth,
originalHeight = region.originalHeight,
rotate = region.rotate,
Expand All @@ -26,8 +27,8 @@ class TexturePackerSprite extends Sprite {
region.page.texture,
srcPosition: Vector2(region.left, region.top),
srcSize: Vector2(
region.rotate ? region.height : region.width,
region.rotate ? region.width : region.height,
useOriginalSize ? region.originalWidth : region.width,
useOriginalSize ? region.originalHeight : region.height,
),
) {
_decorator = Transform2DDecorator(_transform);
Expand All @@ -36,6 +37,13 @@ class TexturePackerSprite extends Sprite {
}
}

/// Region object for [clone] function, don't modify this object properties.
final Region _region;

/// If true, use [originalWidth] and [originalHeight] as size; otherwise use
/// [packedWidth] and [packedHeight] as size.
final bool useOriginalSize;

/// The number at the end of the original image file name, or -1 if none.
///
/// When sprites are packed, if the original file name ends with a number, it
Expand Down Expand Up @@ -82,12 +90,57 @@ class TexturePackerSprite extends Sprite {
/// The [degrees] field (angle) represented as radians.
double get angle => radians(degrees.toDouble());

/// Clone current object with new value for argument [useOriginalSize].
TexturePackerSprite clone({bool useOriginalSize = true}) =>
TexturePackerSprite(_region, useOriginalSize: useOriginalSize);

Vector2 get _srcSize => Vector2(src.width, src.height);

Vector2 get _srcSizeRotated => Vector2(src.height, src.width);

Vector2 get _srcSizeRender => rotate ? _srcSizeRotated : _srcSize;

Vector2 get _offset => Vector2(offsetX, offsetY);

Vector2 get _packedSize => Vector2(packedWidth, packedHeight);

Vector2 get _originalSize => Vector2(originalWidth, originalHeight);

Vector2 get offset => useOriginalSize ? _offset : Vector2.zero();

@override
Vector2 get originalSize => useOriginalSize ? _originalSize : _packedSize;

@override
Vector2 get srcSize => _srcSizeRender
..divide(_packedSize)
..multiply(originalSize);

@override
set srcSize(Vector2? size) {
final actualSize = Vector2.copy(size ?? originalSize)
..divide(originalSize)
..multiply(_packedSize);
if (rotate) {
actualSize.setValues(actualSize.y, actualSize.x);
}
src = srcPosition.toPositionedRect(actualSize);
}

@override
set srcPosition(Vector2? position) {
src = (position ?? Vector2.zero()).toPositionedRect(_srcSize);
}

late final Decorator _decorator;
final Transform2D _transform = Transform2D();

// Used to avoid the creation of new Vector2 objects in render.
static final _tmpRenderPosition = Vector2.zero();
static final _tmpRenderSize = Vector2.zero();
static final _tmpRenderScale = Vector2.zero();
static final _tmpRenderImageSize = Vector2.zero();
static final _tmpRenderOffset = Vector2.zero();

@override
void render(
Expand All @@ -97,42 +150,67 @@ class TexturePackerSprite extends Sprite {
Anchor anchor = Anchor.topLeft,
Paint? overridePaint,
}) {
if (!rotate) {
return super.render(
canvas,
position: position,
size: size,
anchor: anchor,
overridePaint: overridePaint,
);
}
if (position != null) {
_tmpRenderPosition.setFrom(position);
} else {
_tmpRenderPosition.setZero();
}

// If the sprite is rotated on the sprite sheet un-rotate it
// and adjust the size
final auxAnchor = rotate ? Anchor.bottomLeft : anchor;
// Get sprite size
_tmpRenderSize.setFrom(size ?? srcSize);

// Calculate topLeft position
_tmpRenderPosition.setValues(
_tmpRenderPosition.x - (anchor.x * _tmpRenderSize.x),
_tmpRenderPosition.y - (anchor.y * _tmpRenderSize.y),
);

// Calculate multiplier value
_tmpRenderScale
..setFrom(_tmpRenderSize)
..divide(originalSize);

// Calculate image size rendered based on packedSize
_tmpRenderImageSize
..setFrom(_packedSize)
..multiply(_tmpRenderScale);

final tempSize = size ?? srcSize;
final tempWidth = rotate ? tempSize.y : tempSize.x;
final tempHeight = rotate ? tempSize.x : tempSize.y;
// Calculate image rendered offset from topLeft position
_tmpRenderOffset
..setFrom(offset)
..multiply(_tmpRenderScale)
..add(_tmpRenderPosition);

_tmpRenderSize.setValues(tempWidth, tempHeight);
if (!rotate) {
// Calculate and render for non-rotated image, must call function render
// from super class with anchor = Anchor.topLeft, because we already
// calculated size and position based on Anchor.topLeft to rendered by
// super class function.
_tmpRenderSize.setFrom(_tmpRenderImageSize);
_tmpRenderPosition.setFrom(_tmpRenderOffset);
return super.render(
canvas,
position: _tmpRenderPosition,
size: _tmpRenderSize,
overridePaint: overridePaint,
);
}

// Calculate and render for rotated image, must call function render
// from super class with anchor = Anchor.topLeft, because we already
// calculated size and position based on Anchor.topLeft to rendered by super
// class function.
_tmpRenderSize.setValues(_tmpRenderImageSize.y, _tmpRenderImageSize.x);
_tmpRenderPosition.setValues(
_tmpRenderPosition.x - (auxAnchor.x * _tmpRenderSize.x),
_tmpRenderPosition.y - (auxAnchor.y * _tmpRenderSize.y),
_tmpRenderOffset.y - 0,
-_tmpRenderOffset.x - _tmpRenderImageSize.x,
);

_decorator.applyChain(
(applyCanvas) => super.render(
applyCanvas,
position: _tmpRenderPosition,
size: _tmpRenderSize,
anchor: anchor,
overridePaint: overridePaint,
),
canvas,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@

MultipleTrimmedPageAtlasMap.png
size: 189, 502
format: RGBA8888
filter: Linear, Linear
repeat: none
robot_fall
rotate: true
xy: 0, 317
size: 185, 189
orig: 192, 256
offset: 6, 2
index: -1
robot_jump
rotate: true
xy: 0, 152
size: 163, 189
orig: 192, 256
offset: 17, 1
index: -1
robot_walk
rotate: true
xy: 0, 0
size: 150, 183
orig: 192, 256
offset: 14, 2
index: 0

MultipleTrimmedPageAtlasMap2.png
size: 249, 469
format: RGBA8888
filter: Linear, Linear
repeat: none
robot_walk
rotate: true
xy: 0, 318
size: 151, 181
orig: 192, 256
offset: 13, 0
index: 4
robot_walk
rotate: true
xy: 0, 187
size: 129, 185
orig: 192, 256
offset: 23, 0
index: 7
robot_walk
rotate: false
xy: 0, 0
size: 129, 185
orig: 192, 256
offset: 23, 0
index: 3
robot_walk
rotate: false
xy: 131, 4
size: 118, 181
orig: 192, 256
offset: 34, 0
index: 2

MultipleTrimmedPageAtlasMap3.png
size: 250, 492
format: RGBA8888
filter: Linear, Linear
repeat: none
robot_duck
rotate: false
xy: 132, 159
size: 116, 150
orig: 192, 256
offset: 34, 0
index: -1
robot_idle
rotate: false
xy: 0, 310
size: 130, 182
orig: 192, 256
offset: 31, 0
index: -1
robot_walk
rotate: false
xy: 0, 131
size: 130, 177
orig: 192, 256
offset: 23, 0
index: 5
robot_walk
rotate: false
xy: 132, 311
size: 118, 181
orig: 192, 256
offset: 34, 0
index: 6
robot_walk
rotate: true
xy: 0, 0
size: 129, 177
orig: 192, 256
offset: 24, 0
index: 1
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 0b0a6c1

Please sign in to comment.