Skip to content

Commit 17fac08

Browse files
authored
refactor: Asset path resolution/loading in TexturePackerAtlas to correctly handle prefixes and different asset types
1 parent b170299 commit 17fac08

File tree

2 files changed

+181
-10
lines changed

2 files changed

+181
-10
lines changed

packages/flame_texturepacker/lib/src/texture_packer_atlas.dart

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ Future<TextureAtlasData> _fromAssets(
188188
);
189189
} on Exception catch (e, stack) {
190190
Error.throwWithStackTrace(
191-
Exception('Error loading $assetsPrefix$path from assets: $e'),
191+
Exception('Error loading $path (prefix: $assetsPrefix) from assets: $e'),
192192
stack,
193193
);
194194
}
@@ -240,12 +240,33 @@ Future<TextureAtlasData> _parse(
240240
final regions = <Region>[];
241241
var hasIndexes = false;
242242

243-
final fileContent = fromStorage
244-
? await XFile(path).readAsString()
245-
: await (assets ?? Flame.assets).readFile(
246-
'${assetsPrefix!}/$path',
247-
package: package,
248-
);
243+
final String fileContent;
244+
if (fromStorage) {
245+
fileContent = await XFile(path).readAsString();
246+
} else {
247+
final prefix = (assetsPrefix ?? '').trim();
248+
var cleanPath = path.trim();
249+
250+
if (cleanPath.startsWith('/')) {
251+
cleanPath = cleanPath.substring(1);
252+
}
253+
254+
String fullPath;
255+
if (prefix.isEmpty) {
256+
fullPath = cleanPath;
257+
} else {
258+
if (prefix.endsWith('/')) {
259+
fullPath = '$prefix$cleanPath';
260+
} else {
261+
fullPath = '$prefix/$cleanPath';
262+
}
263+
}
264+
265+
fileContent = await (assets ?? Flame.assets).readFile(
266+
fullPath,
267+
package: package,
268+
);
269+
}
249270

250271
final lines = LineSplitter.split(
251272
fileContent,
@@ -261,6 +282,8 @@ Future<TextureAtlasData> _parse(
261282
fromStorage,
262283
images,
263284
package,
285+
assetsPrefix: assetsPrefix,
286+
assets: assets,
264287
);
265288
pages.add(page);
266289

@@ -329,8 +352,10 @@ Future<Page> _parsePage(
329352
String path,
330353
bool fromStorage,
331354
Images images,
332-
String? package,
333-
) async {
355+
String? package, {
356+
String? assetsPrefix,
357+
AssetsCache? assets,
358+
}) async {
334359
final page = Page();
335360
page.textureFile = lineQueue.removeFirst();
336361

@@ -345,7 +370,25 @@ Future<Page> _parsePage(
345370
images.add(texturePath, image);
346371
page.texture = images.fromCache(texturePath);
347372
} else {
348-
page.texture = await images.load(texturePath, package: package);
373+
final prefix = (assetsPrefix ?? '').trim();
374+
var cleanPath = texturePath.trim();
375+
376+
if (cleanPath.startsWith('/')) {
377+
cleanPath = cleanPath.substring(1);
378+
}
379+
380+
String fullTexturePath;
381+
if (prefix.isEmpty) {
382+
fullTexturePath = cleanPath;
383+
} else {
384+
if (prefix.endsWith('/')) {
385+
fullTexturePath = '$prefix$cleanPath';
386+
} else {
387+
fullTexturePath = '$prefix/$cleanPath';
388+
}
389+
}
390+
391+
page.texture = await images.load(fullTexturePath, package: package);
349392
}
350393

351394
_parsePageProperties(lineQueue, page);
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import 'dart:ui' as ui;
2+
import 'package:flame/cache.dart';
3+
import 'package:flame_texturepacker/flame_texturepacker.dart';
4+
import 'package:flutter/services.dart';
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:mocktail/mocktail.dart';
7+
8+
class _MockAssetBundle extends Mock implements AssetBundle {}
9+
10+
class _MockImages extends Mock implements Images {}
11+
12+
class FakeImage extends Mock implements ui.Image {}
13+
14+
void main() {
15+
TestWidgetsFlutterBinding.ensureInitialized();
16+
17+
group('TexturePackerAtlas Path Resolution', () {
18+
late _MockAssetBundle bundle;
19+
late _MockImages images;
20+
late String atlasContent;
21+
22+
setUpAll(() {
23+
registerFallbackValue(const Symbol('package'));
24+
});
25+
26+
setUp(() {
27+
bundle = _MockAssetBundle();
28+
images = _MockImages();
29+
atlasContent = '''
30+
test.png
31+
size: 64, 64
32+
filter: Nearest, Nearest
33+
repeat: none
34+
sprite1
35+
bounds: 0, 0, 32, 32
36+
''';
37+
38+
// Mock loading the atlas file
39+
when(
40+
() => bundle.loadString(any(), cache: any(named: 'cache')),
41+
).thenAnswer((_) async => atlasContent);
42+
43+
// Mock loading an image
44+
when(
45+
() => images.load(any(), package: any(named: 'package')),
46+
).thenAnswer((_) async => FakeImage());
47+
});
48+
49+
test('should resolve paths correctly with leading slashes', () async {
50+
final assets = AssetsCache(bundle: bundle);
51+
52+
await TexturePackerAtlas.load(
53+
'/path/to/atlas_name.atlas',
54+
assets: assets,
55+
images: images,
56+
);
57+
58+
// Verify it tried to load 'images/path/to/atlas.atlas'
59+
// The leading slash in /path/to/atlas.atlas should be removed.
60+
verify(
61+
() => bundle.loadString(
62+
'assets/images/path/to/atlas_name.atlas',
63+
cache: any(named: 'cache'),
64+
),
65+
).called(1);
66+
});
67+
68+
test('should handle assetsPrefix WITH trailing slash', () async {
69+
final assets = AssetsCache(bundle: bundle);
70+
71+
await TexturePackerAtlas.load(
72+
'atlas_name.atlas',
73+
assetsPrefix: 'custom/',
74+
assets: assets,
75+
images: images,
76+
);
77+
78+
verify(
79+
() => bundle.loadString(
80+
'assets/custom/atlas_name.atlas',
81+
cache: any(named: 'cache'),
82+
),
83+
).called(1);
84+
});
85+
86+
test('should handle assetsPrefix WITHOUT trailing slash', () async {
87+
final assets = AssetsCache(bundle: bundle);
88+
89+
await TexturePackerAtlas.load(
90+
'atlas_name.atlas',
91+
assetsPrefix: 'custom',
92+
assets: assets,
93+
images: images,
94+
);
95+
96+
verify(
97+
() => bundle.loadString(
98+
'assets/custom/atlas_name.atlas',
99+
cache: any(named: 'cache'),
100+
),
101+
).called(1);
102+
});
103+
104+
test('should pass package parameter to AssetsCache and Images', () async {
105+
final assets = AssetsCache(bundle: bundle);
106+
107+
await TexturePackerAtlas.load(
108+
'atlas_name.atlas',
109+
assets: assets,
110+
images: images,
111+
package: 'my_package',
112+
);
113+
114+
// Verify bundle call includes the package-prefixed path
115+
verify(
116+
() => bundle.loadString(
117+
'packages/my_package/assets/images/atlas_name.atlas',
118+
cache: any(named: 'cache'),
119+
),
120+
).called(1);
121+
122+
// Verify images.load call also includes the package
123+
verify(
124+
() => images.load('images/test.png', package: 'my_package'),
125+
).called(1);
126+
});
127+
});
128+
}

0 commit comments

Comments
 (0)