Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Notifier for changing current sprite/animation in group components #2956

Merged
merged 3 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import 'package:flame/components.dart';
import 'package:flame/src/effects/provider_interfaces.dart';
import 'package:flame/src/sprite_animation_ticker.dart';
import 'package:flutter/foundation.dart';
import 'package:meta/meta.dart';

Check notice on line 7 in packages/flame/lib/src/components/sprite_animation_group_component.dart

View workflow job for this annotation

GitHub Actions / analyze

The import of 'package:meta/meta.dart' is unnecessary because all of the used elements are also provided by the import of 'package:flutter/foundation.dart'.

Try removing the import directive. See https://dart.dev/diagnostics/unnecessary_import to learn more about this problem.

Check notice on line 7 in packages/flame/lib/src/components/sprite_animation_group_component.dart

View workflow job for this annotation

GitHub Actions / analyze-latest

The import of 'package:meta/meta.dart' is unnecessary because all of the used elements are also provided by the import of 'package:flutter/foundation.dart'.

Try removing the import directive. See https://dart.dev/diagnostics/unnecessary_import to learn more about this problem.

spydon marked this conversation as resolved.
Show resolved Hide resolved
export '../sprite_animation.dart';

Expand All @@ -13,6 +14,12 @@
/// Key with the current playing animation
T? _current;

ValueNotifier<T?>? _currentAnimationNotifier;

/// A [ValueNotifier] that notifies when the current animation changes.
ValueNotifier<T?> get currentAnimationNotifier =>
_currentAnimationNotifier ??= ValueNotifier<T?>(_current);

/// Map with the mapping each state to the flag removeOnFinish
final Map<T, bool> removeOnFinish;

Expand Down Expand Up @@ -136,8 +143,11 @@
_current = value;
_resizeToSprite();

if (changed && autoResetTicker) {
animationTicker?.reset();
if (changed) {
if (autoResetTicker) {
animationTicker?.reset();
}
_currentAnimationNotifier?.value = value;
}
}

Expand Down
17 changes: 14 additions & 3 deletions packages/flame/lib/src/components/sprite_group_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import 'package:flame/components.dart';
import 'package:flame/src/effects/provider_interfaces.dart';
import 'package:flutter/foundation.dart';
import 'package:meta/meta.dart';

Check notice on line 6 in packages/flame/lib/src/components/sprite_group_component.dart

View workflow job for this annotation

GitHub Actions / analyze

The import of 'package:meta/meta.dart' is unnecessary because all of the used elements are also provided by the import of 'package:flutter/foundation.dart'.

Try removing the import directive. See https://dart.dev/diagnostics/unnecessary_import to learn more about this problem.

Check notice on line 6 in packages/flame/lib/src/components/sprite_group_component.dart

View workflow job for this annotation

GitHub Actions / analyze-latest

The import of 'package:meta/meta.dart' is unnecessary because all of the used elements are also provided by the import of 'package:flutter/foundation.dart'.

Try removing the import directive. See https://dart.dev/diagnostics/unnecessary_import to learn more about this problem.

export '../sprite_animation.dart';

Expand All @@ -11,17 +12,23 @@
class SpriteGroupComponent<T> extends PositionComponent
with HasPaint
implements SizeProvider {
/// Key with the current playing animation
/// Key for the current sprite.
T? _current;

/// Map with the available states for this sprite group
ValueNotifier<T?>? _currentSpriteNotifier;

/// A [ValueNotifier] that notifies when the current sprite changes.
ValueNotifier<T?> get currentSpriteNotifier =>
_currentSpriteNotifier ??= ValueNotifier<T?>(_current);

/// Map with the available states for this sprite group.
Map<T, Sprite>? _sprites;

/// When set to true, the component is auto-resized to match the
/// size of current sprite.
bool _autoResize;

/// Creates a component with an empty animation which can be set later
/// Creates a component with an empty animation which can be set later.
SpriteGroupComponent({
Map<T, Sprite>? sprites,
T? current,
Expand Down Expand Up @@ -62,8 +69,12 @@
///
/// Will update [size] if [autoResize] is true.
set current(T? value) {
final changed = _current != value;
_current = value;
_resizeToSprite();
if (changed) {
_currentSpriteNotifier?.value = value;
}
}

/// Returns current value of auto resize flag.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,122 +199,8 @@ Future<void> main() async {
});
});

group('SpriteAnimationGroupComponent.autoResize', () {
test('mutual exclusive with size while construction', () {
expect(
() => SpriteAnimationGroupComponent<_AnimationState>(
autoResize: true,
size: Vector2.all(2),
),
throwsAssertionError,
);

expect(
() => SpriteAnimationGroupComponent<_AnimationState>(autoResize: false),
throwsAssertionError,
);
});

test('default value set correctly when not provided explicitly', () {
final component1 = SpriteAnimationGroupComponent<_AnimationState>();
final component2 = SpriteAnimationGroupComponent<_AnimationState>(
size: Vector2.all(2),
);

expect(component1.autoResize, true);
expect(component2.autoResize, false);
});

test('resizes on current state change', () {
final sprite1 = Sprite(image, srcSize: Vector2.all(76));
final sprite2 = Sprite(image, srcSize: Vector2.all(15));
final animation1 = SpriteAnimation.spriteList(
List.filled(5, sprite1),
stepTime: 0.1,
loop: false,
);
final animation2 = SpriteAnimation.spriteList(
List.filled(5, sprite2),
stepTime: 0.1,
loop: false,
);

final component = SpriteAnimationGroupComponent<_AnimationState>(
animations: {
_AnimationState.idle: animation1,
_AnimationState.running: animation2,
},
current: _AnimationState.idle,
);
expect(component.size, sprite1.srcSize);

component.current = _AnimationState.running;
expect(component.size, sprite2.srcSize);
});

test('resizes only when true', () {
final sprite1 = Sprite(image, srcSize: Vector2.all(76));
final sprite2 = Sprite(image, srcSize: Vector2.all(15));
final animation1 = SpriteAnimation.spriteList(
List.filled(5, sprite1),
stepTime: 0.1,
loop: false,
);
final animation2 = SpriteAnimation.spriteList(
List.filled(5, sprite2),
stepTime: 0.1,
loop: false,
);

final component = SpriteAnimationGroupComponent<_AnimationState>(
animations: {
_AnimationState.idle: animation1,
_AnimationState.running: animation2,
},
current: _AnimationState.idle,
)..autoResize = false;

component.current = _AnimationState.running;
expect(component.size, sprite1.srcSize);

component.autoResize = true;
expect(component.size, sprite2.srcSize);
});

test('stop autoResizing on external size modifications', () {
final testSize = Vector2(83, 100);
final sprite1 = Sprite(image, srcSize: Vector2.all(76));
final sprite2 = Sprite(image, srcSize: Vector2.all(15));
final animation1 = SpriteAnimation.spriteList(
List.filled(5, sprite1),
stepTime: 0.1,
loop: false,
);
final animation2 = SpriteAnimation.spriteList(
List.filled(5, sprite2),
stepTime: 0.1,
loop: false,
);
final animationsMap = {
_AnimationState.idle: animation1,
_AnimationState.running: animation2,
};
final component = SpriteAnimationGroupComponent<_AnimationState>();

// NOTE: Sequence of modifications is important here. Changing the size
// after changing the animations map will disable auto-resizing. So even
// if the current state is changed later, the component should still
// maintain testSize.
component
..animations = animationsMap
..size = testSize
..current = _AnimationState.running;

expectDouble(component.size.x, testSize.x);
expectDouble(component.size.y, testSize.y);
});

test('modify size only if changed while auto-resizing', () {
group('SpriteAnimationGroupComponent.currentAnimationNotifier', () {
test('notifies when the current animation changes', () {
final sprite1 = Sprite(image, srcSize: Vector2.all(76));
final sprite2 = Sprite(image, srcSize: Vector2.all(15));
final animation1 = SpriteAnimation.spriteList(
Expand All @@ -333,84 +219,29 @@ Future<void> main() async {
final component = SpriteAnimationGroupComponent<_AnimationState>(
animations: animationsMap,
);

var sizeChangeCounter = 0;
component.size.addListener(() => ++sizeChangeCounter);
var animationChangeCounter = 0;
component.currentAnimationNotifier.addListener(
() => animationChangeCounter++,
);

component.current = _AnimationState.running;
expect(sizeChangeCounter, equals(1));
expect(animationChangeCounter, equals(1));

component.current = _AnimationState.idle;
expect(sizeChangeCounter, equals(2));
expect(animationChangeCounter, equals(2));

component.update(1);
expect(sizeChangeCounter, equals(2));
expect(animationChangeCounter, equals(2));

component.current = _AnimationState.running;
expect(sizeChangeCounter, equals(3));
expect(animationChangeCounter, equals(3));

component.update(1);
expect(sizeChangeCounter, equals(4));
});
});

group('SpriteAnimationGroupComponent.autoResetTicker', () {
final sprite1 = Sprite(image, srcSize: Vector2.all(76));
final sprite2 = Sprite(image, srcSize: Vector2.all(15));
final animation1 = SpriteAnimation.spriteList(
List.filled(5, sprite1),
stepTime: 0.1,
loop: false,
);
final animation2 = SpriteAnimation.spriteList(
List.filled(5, sprite2),
stepTime: 0.1,
loop: false,
);

test('does reset ticker by default', () {
final component = SpriteAnimationGroupComponent<_AnimationState>(
animations: {
_AnimationState.idle: animation1,
_AnimationState.running: animation2,
},
current: _AnimationState.idle,
);
component.update(0.9);
expect(component.animationTicker!.currentIndex, 4);
expect(animationChangeCounter, equals(3));

component.current = _AnimationState.running;
component.update(0.1);
expect(component.animationTicker!.currentIndex, 1);

component.current = _AnimationState.idle;
expect(component.animationTicker!.currentIndex, 0);

component.current = _AnimationState.running;
expect(component.animationTicker!.currentIndex, 0);
});

test('resets the ticker when enabled', () {
final component = SpriteAnimationGroupComponent<_AnimationState>(
animations: {
_AnimationState.idle: animation1,
_AnimationState.running: animation2,
},
autoResetTicker: false,
current: _AnimationState.idle,
);
component.update(0.9);
expect(component.animationTicker!.currentIndex, 4);

component.current = _AnimationState.running;
component.update(0.1);
expect(component.animationTicker!.currentIndex, 1);

component.current = _AnimationState.idle;
expect(component.animationTicker!.currentIndex, 4);

component.current = _AnimationState.running;
expect(component.animationTicker!.currentIndex, 1);
component.update(1);
expect(animationChangeCounter, equals(3));
});
});
}
47 changes: 47 additions & 0 deletions packages/flame/test/components/sprite_group_component_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,51 @@
expect(sizeChangeCounter, equals(2));
});
});

group('SpriteGroupComponent.currentSpriteNotifier', () {
test('notifies when the current sprite changes', () {
final sprite1 = Sprite(image, srcSize: Vector2.all(76));
final sprite2 = Sprite(image, srcSize: Vector2.all(15));
final animation1 = SpriteAnimation.spriteList(

Check warning on line 140 in packages/flame/test/components/sprite_group_component_test.dart

View workflow job for this annotation

GitHub Actions / analyze

The value of the local variable 'animation1' isn't used.

Try removing the variable or using it. See https://dart.dev/diagnostics/unused_local_variable to learn more about this problem.

Check warning on line 140 in packages/flame/test/components/sprite_group_component_test.dart

View workflow job for this annotation

GitHub Actions / analyze-latest

The value of the local variable 'animation1' isn't used.

Try removing the variable or using it. See https://dart.dev/diagnostics/unused_local_variable to learn more about this problem.
List.filled(5, sprite1),
stepTime: 1,
loop: false,
);
final animation2 = SpriteAnimation.spriteList(

Check warning on line 145 in packages/flame/test/components/sprite_group_component_test.dart

View workflow job for this annotation

GitHub Actions / analyze

The value of the local variable 'animation2' isn't used.

Try removing the variable or using it. See https://dart.dev/diagnostics/unused_local_variable to learn more about this problem.

Check warning on line 145 in packages/flame/test/components/sprite_group_component_test.dart

View workflow job for this annotation

GitHub Actions / analyze-latest

The value of the local variable 'animation2' isn't used.

Try removing the variable or using it. See https://dart.dev/diagnostics/unused_local_variable to learn more about this problem.
[sprite2, sprite1],
stepTime: 1,
);
final spritesMap = {
_SpriteState.idle: Sprite(image, srcSize: Vector2.all(15)),
_SpriteState.running: Sprite(image, srcSize: Vector2.all(15)),
_SpriteState.flying: Sprite(image, srcSize: Vector2(15, 12)),
};
final component = SpriteGroupComponent<_SpriteState>(
sprites: spritesMap,
);
var spriteChangeCounter = 0;
component.currentSpriteNotifier.addListener(
() => spriteChangeCounter++,
);

component.current = _SpriteState.running;
expect(spriteChangeCounter, equals(1));

component.current = _SpriteState.idle;
expect(spriteChangeCounter, equals(2));

component.update(1);
expect(spriteChangeCounter, equals(2));

component.current = _SpriteState.running;
expect(spriteChangeCounter, equals(3));

component.update(1);
expect(spriteChangeCounter, equals(3));

component.current = _SpriteState.running;
component.update(1);
expect(spriteChangeCounter, equals(3));
});
});
}
Loading