From a880282b2026f8e75003cec3c8c4d4c344011d4f Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Sun, 22 Mar 2026 13:39:02 -0600 Subject: [PATCH 1/4] fix: Remove unnecessary asset prefix handling for texture paths --- .../lib/src/texture_packer_parser.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/flame_texturepacker/lib/src/texture_packer_parser.dart b/packages/flame_texturepacker/lib/src/texture_packer_parser.dart index c9fc42aab31..3230ab5f8f9 100644 --- a/packages/flame_texturepacker/lib/src/texture_packer_parser.dart +++ b/packages/flame_texturepacker/lib/src/texture_packer_parser.dart @@ -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; From a1135b0d33ecc27f1bcd9ab636428d1c905dcb30 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Sun, 22 Mar 2026 13:39:11 -0600 Subject: [PATCH 2/4] fix: Ensure assetsPrefix is not applied to image paths loaded via Images --- .../test/atlas_path_resolution_test.dart | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/flame_texturepacker/test/atlas_path_resolution_test.dart b/packages/flame_texturepacker/test/atlas_path_resolution_test.dart index bbab123140e..c5372edfc12 100644 --- a/packages/flame_texturepacker/test/atlas_path_resolution_test.dart +++ b/packages/flame_texturepacker/test/atlas_path_resolution_test.dart @@ -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); From 265a7ca7ccbf93c051380e9258dec45d2b41fca8 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Sun, 22 Mar 2026 15:04:05 -0600 Subject: [PATCH 3/4] fix: Refactor texture path handling and clean up index extraction in texture region parsing --- .../lib/src/texture_packer_parser.dart | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/packages/flame_texturepacker/lib/src/texture_packer_parser.dart b/packages/flame_texturepacker/lib/src/texture_packer_parser.dart index 3230ab5f8f9..4c37e12f7a9 100644 --- a/packages/flame_texturepacker/lib/src/texture_packer_parser.dart +++ b/packages/flame_texturepacker/lib/src/texture_packer_parser.dart @@ -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}'; @@ -183,7 +183,6 @@ abstract class TexturePackerParser { static Region _parseRegion(ListQueue lineQueue, Page page) { final originalName = lineQueue.removeFirst().trim(); var name = originalName; - var extractedIndex = -1; final extensionMatch = RegExp( r'\.(png|jpg|jpeg|bmp|tga|webp)$', @@ -193,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 = >{}; while (lineQueue.isNotEmpty) { @@ -230,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']; @@ -261,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), From 96e756e75b2daaa7bd8302a5500b9213657dd6f0 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Sun, 22 Mar 2026 15:05:06 -0600 Subject: [PATCH 4/4] feat: Update testing --- .../test/atlas_path_resolution_test.dart | 43 +++++++++++++++++-- .../test/naming_index_test.dart | 34 +++++++-------- .../test/separated_parsing_test.dart | 15 ++++--- 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/packages/flame_texturepacker/test/atlas_path_resolution_test.dart b/packages/flame_texturepacker/test/atlas_path_resolution_test.dart index c5372edfc12..269c97410ab 100644 --- a/packages/flame_texturepacker/test/atlas_path_resolution_test.dart +++ b/packages/flame_texturepacker/test/atlas_path_resolution_test.dart @@ -258,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 = ''' @@ -282,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); }, ); }); diff --git a/packages/flame_texturepacker/test/naming_index_test.dart b/packages/flame_texturepacker/test/naming_index_test.dart index 947d21cd7b0..7a724e46f7d 100644 --- a/packages/flame_texturepacker/test/naming_index_test.dart +++ b/packages/flame_texturepacker/test/naming_index_test.dart @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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); }); }); diff --git a/packages/flame_texturepacker/test/separated_parsing_test.dart b/packages/flame_texturepacker/test/separated_parsing_test.dart index dbee1eb8940..ae404d6b635 100644 --- a/packages/flame_texturepacker/test/separated_parsing_test.dart +++ b/packages/flame_texturepacker/test/separated_parsing_test.dart @@ -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 { @@ -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);