Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 3 additions & 38 deletions packages/flame_texturepacker/lib/src/texture_packer_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ abstract class TexturePackerParser {
final img = images ?? Flame.images;
for (final page in atlasData.pages) {
final parentPath = (path.split('/')..removeLast()).join('/');
var texturePath = parentPath.isEmpty
final texturePath = parentPath.isEmpty
? page.textureFile
: '$parentPath/${page.textureFile}';

Expand All @@ -128,16 +128,6 @@ abstract class TexturePackerParser {
img.add(texturePath, image);
page.texture = img.fromCache(texturePath);
} else {
final prefix = (assetsPrefix ?? '').trim();
if (prefix.isNotEmpty &&
!texturePath.contains('packages/') &&
!texturePath.startsWith('assets/')) {
final effectivePrefix = prefix.endsWith('/') ? prefix : '$prefix/';
if (!texturePath.startsWith(effectivePrefix)) {
texturePath = '$effectivePrefix$texturePath';
}
}

final resolved = resolvePath(texturePath, package);
final assetsCachePrefix = (assets ?? Flame.assets).prefix;

Expand Down Expand Up @@ -193,7 +183,6 @@ abstract class TexturePackerParser {
static Region _parseRegion(ListQueue<String> lineQueue, Page page) {
final originalName = lineQueue.removeFirst().trim();
var name = originalName;
var extractedIndex = -1;

final extensionMatch = RegExp(
r'\.(png|jpg|jpeg|bmp|tga|webp)$',
Expand All @@ -203,24 +192,6 @@ abstract class TexturePackerParser {
name = name.substring(0, extensionMatch.start);
}

final nameBeforeIndex = name;

final indexMatch = RegExp(r'(_?)(\d+)$').firstMatch(name);
if (indexMatch != null) {
try {
extractedIndex = int.parse(indexMatch.group(2)!);
name = name.substring(0, indexMatch.start);
} on FormatException catch (e, stack) {
Error.throwWithStackTrace(
FormatException(
'Failed to parse index from sprite name "$name". '
'Ensure the numeric suffix fits within an integer range.',
),
stack,
);
}
}

final values = <String, List<String>>{};

while (lineQueue.isNotEmpty) {
Expand All @@ -240,11 +211,6 @@ abstract class TexturePackerParser {
lineQueue.removeFirst();
}

final indexValue = values['index'];
if (indexValue != null) {
extractedIndex = int.parse(indexValue[0]);
}

final xy = values['xy'];
final size = values['size'];
final bounds = values['bounds'];
Expand All @@ -271,12 +237,11 @@ abstract class TexturePackerParser {

final finalOriginalHeight = originalHeight == 0.0 ? null : originalHeight;

final finalIndex = index != null ? int.parse(index[0]) : extractedIndex;
final finalName = finalIndex == -1 ? nameBeforeIndex : name;
final finalIndex = index != null ? int.parse(index[0]) : -1;

return Region(
page: page,
name: finalName,
name: name,
left: bounds != null
? double.parse(bounds[0])
: (xy != null ? double.parse(xy[0]) : 0.0),
Expand Down
96 changes: 93 additions & 3 deletions packages/flame_texturepacker/test/atlas_path_resolution_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,59 @@ sprite1
).called(1);
});

test('should not apply assetsPrefix to image paths loaded via Images',
() async {
final assets = AssetsCache(bundle: bundle);

await TexturePackerAtlas.load(
'atlas_name.atlas',
assets: assets,
images: images,
);

// assetsPrefix is for Flame.assets (prefix 'assets/'), NOT for
// Flame.images (prefix 'assets/images/'). The image path must be
// relative to Flame.images's prefix, without assetsPrefix applied.
verify(
() => images.load('test.png', package: any(named: 'package')),
).called(1);
});

test(
'should not double-prefix image paths in subdirectories',
() async {
final assets = AssetsCache(bundle: bundle);
const subDirAtlasContent = '''
textures.png
size: 64, 64
filter: Nearest, Nearest
repeat: none
sprite1
bounds: 0, 0, 32, 32
''';

when(
() => bundle.loadString(any(), cache: any(named: 'cache')),
).thenAnswer((_) async => subDirAtlasContent);

await TexturePackerAtlas.load(
'packs/atlas_name.atlas',
assets: assets,
images: images,
);

// The image path should be 'packs/textures.png' (parent dir + filename),
// NOT 'images/packs/textures.png' which would cause Flame.images to
// resolve 'assets/images/images/packs/textures.png'.
verify(
() => images.load(
'packs/textures.png',
package: any(named: 'package'),
),
).called(1);
},
);

test('should handle assetsPrefix WITHOUT trailing slash', () async {
final assets = AssetsCache(bundle: bundle);

Expand Down Expand Up @@ -205,7 +258,7 @@ sprite1
);

test(
'should correctly parse region names with .png and extracted indexes',
'should preserve region names with trailing digits (no index stripping)',
() async {
final assets = AssetsCache(bundle: bundle);
const complexAtlasContent = '''
Expand All @@ -229,11 +282,48 @@ knight_walk_02.png
images: images,
);

// Names should be preserved as-is (minus .png extension).
// Index only comes from the explicit 'index:' field in the atlas.
expect(atlas.sprites.length, 2);
expect(atlas.sprites[0].region.name, 'knight_walk_01');
expect(atlas.sprites[0].region.index, -1);
expect(atlas.sprites[1].region.name, 'knight_walk_02');
expect(atlas.sprites[1].region.index, -1);
},
);

test(
'should use explicit index field from atlas',
() async {
final assets = AssetsCache(bundle: bundle);
const indexedAtlasContent = '''
knight.png
size: 64, 64
filter: Nearest, Nearest
repeat: none
knight_walk
bounds: 0, 0, 32, 32
index: 0
knight_walk
bounds: 32, 0, 32, 32
index: 1
''';

when(
() => bundle.loadString(any(), cache: any(named: 'cache')),
).thenAnswer((_) async => indexedAtlasContent);

final atlas = await TexturePackerAtlas.load(
'knight.atlas',
assets: assets,
images: images,
);

expect(atlas.sprites.length, 2);
expect(atlas.sprites[0].region.name, 'knight_walk');
expect(atlas.sprites[0].region.index, 1);
expect(atlas.sprites[0].region.index, 0);
expect(atlas.sprites[1].region.name, 'knight_walk');
expect(atlas.sprites[1].region.index, 2);
expect(atlas.sprites[1].region.index, 1);
},
);
});
Expand Down
34 changes: 17 additions & 17 deletions packages/flame_texturepacker/test/naming_index_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void main() {
return atlasData.regions;
}

test('Pattern image1', () async {
test('Name with trailing digits is preserved as-is', () async {
final regions = await createAndLoadRegions('''
test.png
size: 64, 64
Expand All @@ -50,11 +50,11 @@ image1
orig: 32, 32
offset: 0, 0
''');
expect(regions.first.name, 'image');
expect(regions.first.index, 1);
expect(regions.first.name, 'image1');
expect(regions.first.index, -1);
});

test('Pattern image01', () async {
test('Name with zero-padded trailing digits is preserved', () async {
final regions = await createAndLoadRegions('''
test.png
size: 64, 64
Expand All @@ -68,11 +68,11 @@ image01
orig: 32, 32
offset: 0, 0
''');
expect(regions.first.name, 'image');
expect(regions.first.index, 1);
expect(regions.first.name, 'image01');
expect(regions.first.index, -1);
});

test('Pattern image_1', () async {
test('Name with underscore and digit is preserved', () async {
final regions = await createAndLoadRegions('''
test.png
size: 64, 64
Expand All @@ -86,11 +86,11 @@ image_1
orig: 32, 32
offset: 0, 0
''');
expect(regions.first.name, 'image');
expect(regions.first.index, 1);
expect(regions.first.name, 'image_1');
expect(regions.first.index, -1);
});

test('Pattern image_01', () async {
test('Name with underscore and zero-padded digit is preserved', () async {
final regions = await createAndLoadRegions('''
test.png
size: 64, 64
Expand All @@ -104,11 +104,11 @@ image_01
orig: 32, 32
offset: 0, 0
''');
expect(regions.first.name, 'image');
expect(regions.first.index, 1);
expect(regions.first.name, 'image_01');
expect(regions.first.index, -1);
});

test('Pattern image001', () async {
test('Name with triple-zero-padded digit is preserved', () async {
final regions = await createAndLoadRegions('''
test.png
size: 64, 64
Expand All @@ -122,11 +122,11 @@ image001
orig: 32, 32
offset: 0, 0
''');
expect(regions.first.name, 'image');
expect(regions.first.index, 1);
expect(regions.first.name, 'image001');
expect(regions.first.index, -1);
});

test('Index field overrides name-based index if positive', () async {
test('Explicit index field is used when present', () async {
final regions = await createAndLoadRegions('''
test.png
size: 64, 64
Expand All @@ -141,7 +141,7 @@ image_01
offset: 0, 0
index: 5
''');
expect(regions.first.name, 'image');
expect(regions.first.name, 'image_01');
expect(regions.first.index, 5);
});
});
Expand Down
15 changes: 9 additions & 6 deletions packages/flame_texturepacker/test/separated_parsing_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ sprite1
expect(atlasData.pages.first.textureFile, 'test.png');
expect(atlasData.pages.first.texture, isNull);
expect(atlasData.regions, hasLength(1));
// 'sprite1' becomes 'sprite' with index 1
expect(atlasData.regions.first.name, 'sprite');
expect(atlasData.regions.first.index, 1);
// Name is preserved as-is; index only comes from explicit 'index:' field
expect(atlasData.regions.first.name, 'sprite1');
expect(atlasData.regions.first.index, -1);
});

test('should load images later for already parsed atlas data', () async {
Expand Down Expand Up @@ -83,28 +83,31 @@ sprite1
// 3. Create atlas from data
final atlas = TexturePackerAtlas.fromAtlas(atlasData);
expect(atlas.sprites, hasLength(1));
expect(atlas.sprites.first.region.name, 'sprite');
expect(atlas.sprites.first.region.name, 'sprite1');
});

test('getAnimation provides sequences easily', () async {
// Animations require explicit index: fields in the atlas
const animationAtlas = '''
anim.png
size: 64, 64
format: RGBA8888
filter: Linear,Linear
repeat: none
walk_0
walk
rotate: false
xy: 0, 0
size: 32, 32
orig: 32, 32
offset: 0, 0
walk_1
index: 0
walk
rotate: false
xy: 32, 0
size: 32, 32
orig: 32, 32
offset: 0, 0
index: 1
''';
final atlasFile = File('${tempDir.path}/anim.atlas');
await atlasFile.writeAsString(animationAtlas);
Expand Down
Loading