From b16e3b910db89892fa3a7c0fcab4db19e42edd73 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Sun, 20 Aug 2023 10:40:43 -0600 Subject: [PATCH 01/10] feat: load atlas and sprite sheet from storage --- lib/atlas/texture_atlas.dart | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/atlas/texture_atlas.dart b/lib/atlas/texture_atlas.dart index 5add38b..00478b9 100644 --- a/lib/atlas/texture_atlas.dart +++ b/lib/atlas/texture_atlas.dart @@ -1,6 +1,7 @@ library flame_texturepacker; import 'dart:convert'; +import 'dart:io'; import 'package:flame/cache.dart'; import 'package:flame/flame.dart'; @@ -41,7 +42,16 @@ class TextureAtlas { } Future load(String path) async { - final atlasData = await _TextureAtlasData()._load(path); + final atlasData = await _TextureAtlasData()._fromAssets(path); + + for (final region in atlasData.regions) { + sprites.add(AtlasSprite(region)); + } + return this; + } + + Future loadFromStorage(String path) async { + final atlasData = await _TextureAtlasData()._fromStorage(path); for (final region in atlasData.regions) { sprites.add(AtlasSprite(region)); @@ -54,9 +64,27 @@ class _TextureAtlasData { final pages = []; final regions = []; - Future<_TextureAtlasData> _load(String path) async { + Future<_TextureAtlasData> _fromAssets(String path) async { final fileAsString = await Flame.assets.readFile(path); + await _parse(fileAsString); + return this; + } + + Future<_TextureAtlasData> _fromStorage(String path) async { + File file = File(path); + + try { + final fileAsString = await file.readAsString(); + await _parse(fileAsString); + } catch (e) { + throw Exception("Error loading from storage: ${e.toString()}"); + } + + return this; + } + + Future _parse(String fileAsString) async { final iterator = LineSplitter.split(fileAsString).iterator; var line = iterator.moveNextAndGet(); var hasIndexes = false; @@ -159,7 +187,6 @@ class _TextureAtlasData { return i1 - i2; }); } - return this; } ({int count, List entry}) _readEntry(String line) { From c44281a5f6ce84a40cf3f1aa8b6ed2dfecedd706 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Sun, 20 Aug 2023 11:15:22 -0600 Subject: [PATCH 02/10] cache sprite sheet image --- lib/atlas/texture_atlas.dart | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/atlas/texture_atlas.dart b/lib/atlas/texture_atlas.dart index 00478b9..5e01752 100644 --- a/lib/atlas/texture_atlas.dart +++ b/lib/atlas/texture_atlas.dart @@ -9,6 +9,7 @@ import 'package:flame_texturepacker/atlas/model/page.dart'; import 'package:flame_texturepacker/atlas/model/region.dart'; import 'package:flame_texturepacker/atlas/model/atlas_sprite.dart'; import 'package:collection/collection.dart'; +import 'package:flutter/painting.dart'; final _images = Images(prefix: 'assets/'); @@ -67,7 +68,7 @@ class _TextureAtlasData { Future<_TextureAtlasData> _fromAssets(String path) async { final fileAsString = await Flame.assets.readFile(path); - await _parse(fileAsString); + await _parse(fileAsString, path, fromStorage: false); return this; } @@ -76,7 +77,7 @@ class _TextureAtlasData { try { final fileAsString = await file.readAsString(); - await _parse(fileAsString); + await _parse(fileAsString, path, fromStorage: true); } catch (e) { throw Exception("Error loading from storage: ${e.toString()}"); } @@ -84,7 +85,8 @@ class _TextureAtlasData { return this; } - Future _parse(String fileAsString) async { + Future _parse(String fileAsString, String path, + {required bool fromStorage}) async { final iterator = LineSplitter.split(fileAsString).iterator; var line = iterator.moveNextAndGet(); var hasIndexes = false; @@ -103,7 +105,22 @@ class _TextureAtlasData { page.textureFile = line; final parentPath = (path.split('/')..removeLast()).join('/'); final texturePath = '$parentPath/$line'; - page.texture = await _images.load(texturePath); + + if (fromStorage) { + try { + File file = File(path); + final bytes = await file.readAsBytes(); + final decodedBytes = await decodeImageFromList(bytes); + Flame.images.add(path, decodedBytes); + page.texture = Flame.images.fromCache(path); + } catch (e) { + throw Exception( + "Could not add storage file to Flame cache. ${e.toString()}"); + } + } else { + page.texture = await _images.load(texturePath); + } + while (true) { line = iterator.moveNextAndGet(); if (line == null) break; From 726d76ca9c7f9342fc54bff5a34c0051faa09ad3 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Sun, 20 Aug 2023 11:27:32 -0600 Subject: [PATCH 03/10] make createSprite method public --- lib/flame_texturepacker.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/flame_texturepacker.dart b/lib/flame_texturepacker.dart index 7c1ddf7..6536d38 100644 --- a/lib/flame_texturepacker.dart +++ b/lib/flame_texturepacker.dart @@ -56,7 +56,7 @@ extension TexturepackerLoader on Game { return json['frames']; } - Sprite _createSprite(dynamic value, Image image) { + Sprite createSprite(dynamic value, Image image) { final frameData = value['frame']; final int x = frameData['x']; final int y = frameData['y']; From 21e82509378302ca460a0add7e50003a669d1021 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Sun, 20 Aug 2023 16:09:43 -0600 Subject: [PATCH 04/10] put createSprite method back to private and added keyword sprite search --- lib/atlas/texture_atlas.dart | 16 ++++++++++++++++ lib/flame_texturepacker.dart | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/atlas/texture_atlas.dart b/lib/atlas/texture_atlas.dart index 5e01752..bed18b5 100644 --- a/lib/atlas/texture_atlas.dart +++ b/lib/atlas/texture_atlas.dart @@ -21,6 +21,11 @@ class TextureAtlas { AtlasSprite? findSpriteByName(String name) => sprites.firstWhereOrNull((e) => e.name == name); + /// Returns first region found where name matches keyword. This method uses string comparison to find + /// the region, so the result should be cached rather than calling this method multiple times. + AtlasSprite? findSpriteByKeyword(String keyword) => + sprites.firstWhereOrNull((e) => e.name.contains(keyword)); + /// Returns the first region found with the specified name and index. This method uses string /// comparison to find the region, so the result should be cached rather than calling this /// method multiple times. @@ -42,6 +47,17 @@ class TextureAtlas { return matched; } + /// Returns all regions that match a keyword, ordered by smallest to largest index. This method uses + /// string comparison to find the regions, so the result should be cached rather than calling + /// this method multiple times. + List findSpritesByKeyword(String name) { + final matched = []; + for (final sprite in sprites) { + if (sprite.name.contains(name)) matched.add(sprite); + } + return matched; + } + Future load(String path) async { final atlasData = await _TextureAtlasData()._fromAssets(path); diff --git a/lib/flame_texturepacker.dart b/lib/flame_texturepacker.dart index 6536d38..7c1ddf7 100644 --- a/lib/flame_texturepacker.dart +++ b/lib/flame_texturepacker.dart @@ -56,7 +56,7 @@ extension TexturepackerLoader on Game { return json['frames']; } - Sprite createSprite(dynamic value, Image image) { + Sprite _createSprite(dynamic value, Image image) { final frameData = value['frame']; final int x = frameData['x']; final int y = frameData['y']; From 7bef386a79c5f5d83d9d0c2a98fe56012f339be9 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Sun, 20 Aug 2023 19:49:09 -0600 Subject: [PATCH 05/10] fix loadFromStorage --- lib/flame_texturepacker.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/flame_texturepacker.dart b/lib/flame_texturepacker.dart index 7c1ddf7..6287019 100644 --- a/lib/flame_texturepacker.dart +++ b/lib/flame_texturepacker.dart @@ -12,8 +12,11 @@ export 'package:flame_texturepacker/atlas/model/atlas_sprite.dart'; extension TexturepackerLoader on Game { /// Loads the specified pack file, using the parent directory of the pack file to find the page images. - Future fromAtlas(String assetsPath) async => - TextureAtlas().load(assetsPath); + Future fromAtlas(String assetsPath, + {bool fromStorage = false}) async => + fromStorage + ? await TextureAtlas().loadFromStorage(assetsPath) + : await TextureAtlas().load(assetsPath); @Deprecated('Please use fromAtlas() and files with extension .atlas') Future> fromJSONAtlas(String imagePath, String dataPath) async { From 0595549b88dfd40dadd998aec498eb46ab331300 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Mon, 21 Aug 2023 10:28:27 -0600 Subject: [PATCH 06/10] debugging read file issue --- lib/atlas/texture_atlas.dart | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/atlas/texture_atlas.dart b/lib/atlas/texture_atlas.dart index bed18b5..c6af0cf 100644 --- a/lib/atlas/texture_atlas.dart +++ b/lib/atlas/texture_atlas.dart @@ -91,12 +91,12 @@ class _TextureAtlasData { Future<_TextureAtlasData> _fromStorage(String path) async { File file = File(path); - try { - final fileAsString = await file.readAsString(); - await _parse(fileAsString, path, fromStorage: true); - } catch (e) { - throw Exception("Error loading from storage: ${e.toString()}"); - } + // try { + final fileAsString = await file.readAsString(); + await _parse(fileAsString, path, fromStorage: true); + // } catch (e) { + // throw Exception("Error loading from storage: ${e.toString()}"); + // } return this; } @@ -123,16 +123,16 @@ class _TextureAtlasData { final texturePath = '$parentPath/$line'; if (fromStorage) { - try { - File file = File(path); - final bytes = await file.readAsBytes(); - final decodedBytes = await decodeImageFromList(bytes); - Flame.images.add(path, decodedBytes); - page.texture = Flame.images.fromCache(path); - } catch (e) { - throw Exception( - "Could not add storage file to Flame cache. ${e.toString()}"); - } + // try { + File file = File(path); + final bytes = await file.readAsBytes(); + final decodedBytes = await decodeImageFromList(bytes); + Flame.images.add(path, decodedBytes); + page.texture = Flame.images.fromCache(path); + // } catch (e) { + // throw Exception( + // "Could not add storage file to Flame cache. ${e.toString()}"); + // } } else { page.texture = await _images.load(texturePath); } From e65d176f912d7555993c91159c26622bfb301c89 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Mon, 21 Aug 2023 10:35:50 -0600 Subject: [PATCH 07/10] debugging read file issue --- lib/atlas/texture_atlas.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/atlas/texture_atlas.dart b/lib/atlas/texture_atlas.dart index c6af0cf..29a695e 100644 --- a/lib/atlas/texture_atlas.dart +++ b/lib/atlas/texture_atlas.dart @@ -124,8 +124,8 @@ class _TextureAtlasData { if (fromStorage) { // try { - File file = File(path); - final bytes = await file.readAsBytes(); + File textureFile = File(path); + final bytes = await textureFile.readAsBytes(); final decodedBytes = await decodeImageFromList(bytes); Flame.images.add(path, decodedBytes); page.texture = Flame.images.fromCache(path); From 9c3ccc63d676ccd032d514c521245348e1b34699 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Mon, 21 Aug 2023 10:39:07 -0600 Subject: [PATCH 08/10] debugging read file issue --- lib/atlas/texture_atlas.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/atlas/texture_atlas.dart b/lib/atlas/texture_atlas.dart index 29a695e..7854c40 100644 --- a/lib/atlas/texture_atlas.dart +++ b/lib/atlas/texture_atlas.dart @@ -124,11 +124,11 @@ class _TextureAtlasData { if (fromStorage) { // try { - File textureFile = File(path); + File textureFile = File(texturePath); final bytes = await textureFile.readAsBytes(); final decodedBytes = await decodeImageFromList(bytes); - Flame.images.add(path, decodedBytes); - page.texture = Flame.images.fromCache(path); + Flame.images.add(texturePath, decodedBytes); + page.texture = Flame.images.fromCache(texturePath); // } catch (e) { // throw Exception( // "Could not add storage file to Flame cache. ${e.toString()}"); From 00cafcd5a017d30de1a14d74e086a7c3fda467a6 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Mon, 21 Aug 2023 10:40:10 -0600 Subject: [PATCH 09/10] fixed read file issue --- lib/atlas/texture_atlas.dart | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/atlas/texture_atlas.dart b/lib/atlas/texture_atlas.dart index 7854c40..876c389 100644 --- a/lib/atlas/texture_atlas.dart +++ b/lib/atlas/texture_atlas.dart @@ -91,12 +91,12 @@ class _TextureAtlasData { Future<_TextureAtlasData> _fromStorage(String path) async { File file = File(path); - // try { - final fileAsString = await file.readAsString(); - await _parse(fileAsString, path, fromStorage: true); - // } catch (e) { - // throw Exception("Error loading from storage: ${e.toString()}"); - // } + try { + final fileAsString = await file.readAsString(); + await _parse(fileAsString, path, fromStorage: true); + } catch (e) { + throw Exception("Error loading from storage: ${e.toString()}"); + } return this; } @@ -123,16 +123,16 @@ class _TextureAtlasData { final texturePath = '$parentPath/$line'; if (fromStorage) { - // try { - File textureFile = File(texturePath); - final bytes = await textureFile.readAsBytes(); - final decodedBytes = await decodeImageFromList(bytes); - Flame.images.add(texturePath, decodedBytes); - page.texture = Flame.images.fromCache(texturePath); - // } catch (e) { - // throw Exception( - // "Could not add storage file to Flame cache. ${e.toString()}"); - // } + try { + File textureFile = File(texturePath); + final bytes = await textureFile.readAsBytes(); + final decodedBytes = await decodeImageFromList(bytes); + Flame.images.add(texturePath, decodedBytes); + page.texture = Flame.images.fromCache(texturePath); + } catch (e) { + throw Exception( + "Could not add storage file to Flame cache. ${e.toString()}"); + } } else { page.texture = await _images.load(texturePath); } From b2853058ff0fc06efc632720ef778f5d7f0b97a2 Mon Sep 17 00:00:00 2001 From: Grayson Erhard Date: Mon, 21 Aug 2023 15:22:58 -0600 Subject: [PATCH 10/10] docs: added readme instructions for new features --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b92146..576ae3e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ dependencies: ``` ## Usage - + +### Asset Storage + Drop generated atlas file and sprite sheet images into the `assets/` and link the files in your `pubspec.yaml` file: ```yaml @@ -31,6 +33,16 @@ Load the TextureAtlas passing the path of the sprite sheet atlas file: final atlas = await fromAtlas('FlameAtlasMap.atlas'); ``` +### File Storage + +If you are using file storage, grab your atlas file like this: + +```Dart +final atlas = await fromAtlas("${(await getApplicationDocumentsDirectory()).path}/FlamAtlasMap.atlas", fromStorage: true); +``` + +### Getting Sprites + Get a list of sprites ordered by their index, you can use the list to generate an animation: ```Dart @@ -51,6 +63,13 @@ final fallSprite = atlas.findSpriteByName('robot_fall')!; final idleSprite = atlas.findSpriteByName('robot_idle')!; ``` +Get a single sprite or list of sprites by keyword: + +```Dart +final jumpSprites = atlas.findSpritesByKeyword('robot_jump')!; +final fallSprite = atlas.findSpriteByKeyword('robot_fall')!; +``` + Full working example can be found in [example folder][3]. Note: Sprites used in this example can be found OpenGameArt [here][4].