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]. diff --git a/lib/atlas/texture_atlas.dart b/lib/atlas/texture_atlas.dart index 5add38b..876c389 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'; @@ -8,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/'); @@ -19,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. @@ -40,8 +47,28 @@ 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()._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 +81,28 @@ 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, path, fromStorage: false); + return this; + } + + 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()}"); + } + + return this; + } + + Future _parse(String fileAsString, String path, + {required bool fromStorage}) async { final iterator = LineSplitter.split(fileAsString).iterator; var line = iterator.moveNextAndGet(); var hasIndexes = false; @@ -75,7 +121,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 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); + } + while (true) { line = iterator.moveNextAndGet(); if (line == null) break; @@ -159,7 +220,6 @@ class _TextureAtlasData { return i1 - i2; }); } - return this; } ({int count, List entry}) _readEntry(String line) { 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 {