@@ -13,8 +13,8 @@ extension SpriteBatchExtension on Game {
1313 /// its options.
1414 Future <SpriteBatch > loadSpriteBatch (
1515 String path, {
16- Color ? defaultColor,
17- BlendMode ? defaultBlendMode,
16+ Color defaultColor = const Color ( 0x00000000 ) ,
17+ BlendMode defaultBlendMode = BlendMode .srcOver ,
1818 RSTransform ? defaultTransform,
1919 Images ? imageCache,
2020 bool useAtlas = true ,
@@ -122,10 +122,10 @@ enum FlippedAtlasStatus {
122122class SpriteBatch {
123123 SpriteBatch (
124124 this .atlas, {
125+ this .defaultColor = const Color (0x00000000 ),
126+ this .defaultBlendMode = BlendMode .srcOver,
125127 this .defaultTransform,
126128 this .useAtlas = true ,
127- this .defaultColor,
128- this .defaultBlendMode,
129129 Images ? imageCache,
130130 String ? imageKey,
131131 }) : _imageCache = imageCache,
@@ -136,10 +136,10 @@ class SpriteBatch {
136136 /// When the [images] is omitted, the global [Flame.images] is used.
137137 static Future <SpriteBatch > load (
138138 String path, {
139+ Color defaultColor = const Color (0x00000000 ),
140+ BlendMode defaultBlendMode = BlendMode .srcOver,
139141 RSTransform ? defaultTransform,
140142 Images ? images,
141- Color ? defaultColor,
142- BlendMode ? defaultBlendMode,
143143 bool useAtlas = true ,
144144 }) async {
145145 final imagesCache = images ?? Flame .images;
@@ -156,37 +156,38 @@ class SpriteBatch {
156156
157157 FlippedAtlasStatus _flippedAtlasStatus = FlippedAtlasStatus .none;
158158
159- /// List of all the existing batch items .
160- final _batchItems = < BatchItem > [] ;
159+ /// Stack of available (freed) indices using ListQueue as a stack .
160+ final Queue < int > _freeIndices = Queue < int >() ;
161161
162- /// The sources to use on the [atlas] .
163- final _sources = < Rect > [] ;
162+ /// Returns the total number of indices that have been allocated .
163+ int get allocatedCount => _nextIndex ;
164164
165- /// The sources list shouldn't be modified directly, that is why an
166- /// [UnmodifiableListView] is used. If you want to add sources use the
167- /// [add] or [addTransform] method.
168- UnmodifiableListView <Rect > get sources {
169- return UnmodifiableListView <Rect >(_sources);
170- }
165+ /// Returns the number of currently free indices.
166+ int get freeCount => _freeIndices.length;
171167
172- /// The transforms that should be applied on the [_sources] .
173- final _transforms = < RSTransform > [] ;
168+ /// The next index to allocate if no free indices are available .
169+ int _nextIndex = 0 ;
174170
175- /// The transforms list shouldn't be modified directly, that is why an
176- /// [UnmodifiableListView] is used. If you want to add transforms use the
177- /// [add] or [addTransform] method.
178- UnmodifiableListView <RSTransform > get transforms {
179- return UnmodifiableListView <RSTransform >(_transforms);
180- }
171+ /// Sparse array of batch items, indexed by allocated indices.
172+ final Map <int , BatchItem > _batchItems = {};
173+
174+ /// Returns the number of active batch items.
175+ int get length => _batchItems.length;
181176
182- /// The background color for the [_sources] .
183- final _colors = < Color > [];
177+ /// Returns the number of indices currently in use.
178+ int get usedCount => _nextIndex - _freeIndices.length;
179+
180+ /// Allocates a new index, reusing freed indices when possible.
181+ int _allocateIndex () {
182+ if (_freeIndices.isNotEmpty) {
183+ return _freeIndices.removeFirst ();
184+ }
185+ return _nextIndex++ ;
186+ }
184187
185- /// The colors list shouldn't be modified directly, that is why an
186- /// [UnmodifiableListView] is used. If you want to add colors use the
187- /// [add] or [addTransform] method.
188- UnmodifiableListView <Color > get colors {
189- return UnmodifiableListView <Color >(_colors);
188+ /// Frees an index to be reused later.
189+ void _freeIndex (int index) {
190+ _freeIndices.addFirst (index);
190191 }
191192
192193 /// The atlas used by the [SpriteBatch] .
@@ -234,6 +235,12 @@ class SpriteBatch {
234235 /// Does this batch contain any operations?
235236 bool get isEmpty => _batchItems.isEmpty;
236237
238+ // Used to not create new Paint objects in [render] and
239+ // [generateFlippedAtlas].
240+ final _emptyPaint = Paint ();
241+
242+ static const _defaultColor = Color (0x00000000 );
243+
237244 Future <void > _makeFlippedAtlas () async {
238245 _flippedAtlasStatus = FlippedAtlasStatus .generating;
239246 final key = '$imageKey #with-flips' ;
@@ -255,12 +262,10 @@ class SpriteBatch {
255262 return picture.toImageSafe (image.width * 2 , image.height);
256263 }
257264
258- int get length => _sources.length;
259-
260265 /// Replace provided values of a batch item at the [index] , when a parameter
261266 /// is not provided, the original value of the batch item will be used.
262267 ///
263- /// Throws an [ArgumentError] if the [index] is out of bounds .
268+ /// Throws an [ArgumentError] if the [index] doesn't exist .
264269 /// At least one of the parameters must be different from null.
265270 void replace (
266271 int index, {
@@ -273,11 +278,11 @@ class SpriteBatch {
273278 'At least one of the parameters must be different from null.' ,
274279 );
275280
276- if (index < 0 || index >= length ) {
277- throw ArgumentError ('Index out of bounds : $index ' );
281+ if (! _batchItems. containsKey ( index) ) {
282+ throw ArgumentError ('Index does not exist : $index ' );
278283 }
279284
280- final currentBatchItem = _batchItems[index];
285+ final currentBatchItem = _batchItems[index]! ;
281286 final newBatchItem = BatchItem (
282287 source: source ?? currentBatchItem.source,
283288 transform: transform ?? currentBatchItem.transform,
@@ -286,10 +291,14 @@ class SpriteBatch {
286291 );
287292
288293 _batchItems[index] = newBatchItem;
294+ }
289295
290- _sources[index] = newBatchItem.source;
291- _transforms[index] = newBatchItem.transform;
292- _colors[index] = color ?? _defaultColor;
296+ /// Returns the [BatchItem] at the given [index] .
297+ BatchItem getBatchItem (int index) {
298+ if (! _batchItems.containsKey (index)) {
299+ throw ArgumentError ('Index does not exist: $index ' );
300+ }
301+ return _batchItems[index]! ;
293302 }
294303
295304 /// Add a new batch item using a RSTransform.
@@ -307,26 +316,15 @@ class SpriteBatch {
307316 /// cosine of the rotation so that they can be reused over multiple calls to
308317 /// this constructor, it may be more efficient to directly use this method
309318 /// instead.
310- void addTransform ({
319+ int addTransform ({
311320 required Rect source,
312321 RSTransform ? transform,
313322 bool flip = false ,
314323 Color ? color,
315324 }) {
325+ final index = _allocateIndex ();
316326 final batchItem = BatchItem (
317- source: source,
318- transform: transform ?? = defaultTransform ?? RSTransform (1 , 0 , 0 , 0 ),
319- flip: flip,
320- color: color ?? defaultColor,
321- );
322-
323- if (flip && useAtlas && _flippedAtlasStatus.isNone) {
324- _makeFlippedAtlas ();
325- }
326-
327- _batchItems.add (batchItem);
328- _sources.add (
329- flip
327+ source: flip
330328 ? Rect .fromLTWH (
331329 // The atlas is twice as wide when the flipped atlas is generated.
332330 (atlas.width * (_flippedAtlasStatus.isGenerated ? 1 : 2 )) -
@@ -335,10 +333,19 @@ class SpriteBatch {
335333 source.width,
336334 source.height,
337335 )
338- : batchItem.source,
336+ : source,
337+ transform: transform ?? = defaultTransform ?? RSTransform (1 , 0 , 0 , 0 ),
338+ flip: flip,
339+ color: color ?? defaultColor,
339340 );
340- _transforms.add (batchItem.transform);
341- _colors.add (color ?? _defaultColor);
341+
342+ if (flip && useAtlas && _flippedAtlasStatus.isNone) {
343+ _makeFlippedAtlas ();
344+ }
345+
346+ _batchItems[index] = batchItem;
347+
348+ return index;
342349 }
343350
344351 /// Add a new batch item.
@@ -359,7 +366,7 @@ class SpriteBatch {
359366 /// multiple [RSTransform] objects,
360367 /// it may be more efficient to directly use the more direct [addTransform]
361368 /// method instead.
362- void add ({
369+ int add ({
363370 required Rect source,
364371 double scale = 1.0 ,
365372 Vector2 ? anchor,
@@ -389,26 +396,31 @@ class SpriteBatch {
389396 );
390397 }
391398
392- addTransform (
399+ return addTransform (
393400 source: source,
394401 transform: transform,
395402 flip: flip,
396403 color: color,
397404 );
398405 }
399406
407+ /// Removes a batch item at the given [index] .
408+ void removeAt (int index) {
409+ if (! _batchItems.containsKey (index)) {
410+ throw ArgumentError ('Index does not exist: $index ' );
411+ }
412+
413+ _batchItems.remove (index);
414+ _freeIndex (index);
415+ }
416+
400417 /// Clear the SpriteBatch so it can be reused.
401418 void clear () {
402- _sources.clear ();
403- _transforms.clear ();
404- _colors.clear ();
405419 _batchItems.clear ();
420+ _freeIndices.clear ();
421+ _nextIndex = 0 ;
406422 }
407423
408- // Used to not create new Paint objects in [render] and
409- // [generateFlippedAtlas].
410- final _emptyPaint = Paint ();
411-
412424 void render (
413425 Canvas canvas, {
414426 BlendMode ? blendMode,
@@ -419,27 +431,38 @@ class SpriteBatch {
419431 return ;
420432 }
421433
422- final renderPaint = paint ?? _emptyPaint;
423-
424- final hasNoColors = _colors.every ((c) => c == _defaultColor);
425- final actualBlendMode = blendMode ?? defaultBlendMode;
426- if (! hasNoColors && actualBlendMode == null ) {
427- throw 'When setting any colors, a blend mode must be provided.' ;
428- }
434+ paint ?? = _emptyPaint;
429435
430436 if (useAtlas && ! _flippedAtlasStatus.isGenerating) {
437+ final transforms = _batchItems.values
438+ .map ((e) => e.transform)
439+ .toList (growable: false );
440+ final sources = _batchItems.values
441+ .map ((e) => e.source)
442+ .toList (growable: false );
443+ final colors = _batchItems.values
444+ .map ((e) => e.paint.color)
445+ .toList (growable: false );
446+
447+ final hasNoColors = colors.every ((c) => c == _defaultColor);
448+ final actualBlendMode = blendMode ?? defaultBlendMode;
449+ if (! hasNoColors && actualBlendMode == null ) {
450+ throw 'When setting any colors, a blend mode must be provided.' ;
451+ }
452+
431453 canvas.drawAtlas (
432454 atlas,
433- _transforms ,
434- _sources ,
435- hasNoColors ? null : _colors ,
455+ transforms ,
456+ sources ,
457+ hasNoColors ? null : colors ,
436458 actualBlendMode,
437459 cullRect,
438- renderPaint ,
460+ paint ,
439461 );
440462 } else {
441- for (final batchItem in _batchItems) {
442- renderPaint.blendMode = blendMode ?? renderPaint.blendMode;
463+ for (final index in _batchItems.keys) {
464+ final batchItem = _batchItems[index]! ;
465+ paint.blendMode = blendMode ?? paint.blendMode;
443466
444467 canvas
445468 ..save ()
@@ -449,12 +472,10 @@ class SpriteBatch {
449472 atlas,
450473 batchItem.source,
451474 batchItem.destination,
452- renderPaint ,
475+ paint ,
453476 )
454477 ..restore ();
455478 }
456479 }
457480 }
458-
459- static const _defaultColor = Color (0x00000000 );
460481}
0 commit comments