From ca8f8d36324dd4707e39e20d0f47d0ddcce0ade0 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 29 Nov 2021 21:01:05 +0100 Subject: [PATCH] Added a ButtonComponent (#1146) * Added a ButtonComponent * Don't import VoidCallback * Change epsilon since test is flaky * Added SpriteButtonComponent * Add changelog entry * Add docs --- doc/other-inputs.md | 13 +++ .../input/joystick_advanced_example.dart | 47 ++++++++++ packages/flame/CHANGELOG.md | 4 + packages/flame/lib/input.dart | 2 + .../components/input/button_component.dart | 85 +++++++++++++++++++ .../input/hud_button_component.dart | 4 +- .../input/sprite_button_component.dart | 72 ++++++++++++++++ .../test/effects2/opacity_effect_test.dart | 2 +- 8 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 packages/flame/lib/src/components/input/button_component.dart create mode 100644 packages/flame/lib/src/components/input/sprite_button_component.dart diff --git a/doc/other-inputs.md b/doc/other-inputs.md index d6e932d2b1a..11696736f80 100644 --- a/doc/other-inputs.md +++ b/doc/other-inputs.md @@ -117,6 +117,19 @@ If you want to act upon the button being pressed (which would be the common thin a callback function as the `onPressed` argument, or you extend the component and override `onTapDown`, `onTapUp` and/or `onTapCancel` and implement your logic there. +## SpriteButtonComponent + +A `SpriteButtonComponent` is a button that is defined by two `Sprite`s, one that represents +when the button is pressed and one that represents when the button is released. + +## ButtonComponent + +A `ButtonComponent` is a button that is defined by two `PositionComponent`s, one that represents +when the button is pressed and one that represents when the button is released. If you only want +to use sprites for the button, use the [](#SpriteButtonComponent) instead, but this component can be +good to use if you for example want to have a `SpriteAnimationComponent` as a button, or anything +else which isn't a pure sprite. + ## Gamepad Flame has a separate plugin to support external game controllers (gamepads), check diff --git a/examples/lib/stories/input/joystick_advanced_example.dart b/examples/lib/stories/input/joystick_advanced_example.dart index 9aa6dd2cb19..970d477e0c5 100644 --- a/examples/lib/stories/input/joystick_advanced_example.dart +++ b/examples/lib/stories/input/joystick_advanced_example.dart @@ -8,6 +8,7 @@ import 'package:flame/input.dart'; import 'package:flame/palette.dart'; import 'package:flame/sprite.dart'; import 'package:flutter/animation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; import 'joystick_player.dart'; @@ -112,6 +113,50 @@ class JoystickAdvancedExample extends FlameGame ), ); + // A button, created from a shape, that adds a scale effect to the player + // when it is pressed. + final buttonComponent = ButtonComponent( + button: RectangleComponent( + size: Vector2(185, 50), + paint: Paint() + ..color = Colors.orange + ..style = PaintingStyle.stroke, + ), + buttonDown: RectangleComponent( + size: Vector2(185, 50), + paint: BasicPalette.magenta.paint(), + ), + position: Vector2(20, size.y - 280), + onPressed: () => player.add( + ScaleEffect( + scale: Vector2.all(1.5), + duration: 1.0, + isAlternating: true, + ), + ), + ); + + final buttonSprites = await images.load('buttons.png'); + final buttonSheet = SpriteSheet.fromColumnsAndRows( + image: buttonSprites, + columns: 1, + rows: 2, + ); + + // A sprite button, created from a shape, that adds a opacity effect to the + // player when it is pressed. + final spriteButtonComponent = SpriteButtonComponent( + button: buttonSheet.getSpriteById(0), + buttonDown: buttonSheet.getSpriteById(1), + position: Vector2(20, size.y - 360), + size: Vector2(185, 50), + onPressed: () => player.add( + OpacityEffect.fadeOut( + isAlternating: true, + ), + ), + ); + final _regular = TextPaint( style: TextStyle(color: BasicPalette.white.color), ); @@ -142,6 +187,8 @@ class JoystickAdvancedExample extends FlameGame add(joystick); add(flipButton); add(flopButton); + add(buttonComponent); + add(spriteButtonComponent); add(shapeButton); add(speedWithMargin); add(directionWithMargin); diff --git a/packages/flame/CHANGELOG.md b/packages/flame/CHANGELOG.md index 52206b8bafd..dd6545487b5 100644 --- a/packages/flame/CHANGELOG.md +++ b/packages/flame/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## [Next] + - Add `ButtonComponent` backed by two `PositionComponent`s + - Add `SpriteButtonComponent` backed by two `Sprite`s + ## [1.0.0-releasecandidate.18] - Forcing portrait and landscape mode is now supported on web - Fixed margin calculation in `HudMarginComponent` when using a viewport diff --git a/packages/flame/lib/input.dart b/packages/flame/lib/input.dart index 369f27ad13e..c9451915b4b 100644 --- a/packages/flame/lib/input.dart +++ b/packages/flame/lib/input.dart @@ -1,6 +1,8 @@ +export 'src/components/input/button_component.dart'; export 'src/components/input/hud_button_component.dart'; export 'src/components/input/hud_margin_component.dart'; export 'src/components/input/joystick_component.dart'; +export 'src/components/input/sprite_button_component.dart'; export 'src/extensions/vector2.dart'; export 'src/game/mixins/keyboard.dart'; export 'src/gestures/detectors.dart'; diff --git a/packages/flame/lib/src/components/input/button_component.dart b/packages/flame/lib/src/components/input/button_component.dart new file mode 100644 index 00000000000..3fdf2d2c7f4 --- /dev/null +++ b/packages/flame/lib/src/components/input/button_component.dart @@ -0,0 +1,85 @@ +import 'package:meta/meta.dart'; + +import '../../../components.dart'; +import '../../../extensions.dart'; +import '../../../input.dart'; +import '../../gestures/events.dart'; + +/// The [ButtonComponent] bundles two [PositionComponent]s, one that shows while +/// the button is being pressed, and one that shows otherwise. +/// +/// Note: You have to set the [button] in [onLoad] if you are not passing it in +/// through the constructor. +class ButtonComponent extends PositionComponent with Tappable { + late final PositionComponent? button; + late final PositionComponent? buttonDown; + + /// Callback for what should happen when the button is pressed. + /// If you want to interact with [onTapUp] or [onTapCancel] it is recommended + /// to extend [ButtonComponent]. + void Function()? onPressed; + + ButtonComponent({ + this.button, + this.buttonDown, + this.onPressed, + Vector2? position, + Vector2? size, + Vector2? scale, + double? angle, + Anchor? anchor, + int? priority, + }) : super( + position: position, + size: size ?? button?.size, + scale: scale, + angle: angle, + anchor: anchor, + priority: priority, + ); + + @override + @mustCallSuper + void onMount() { + assert( + button != null, + 'The button has to either be passed in as an argument or set in onLoad', + ); + final idleButton = button; + if (idleButton != null && !contains(idleButton)) { + add(idleButton); + } + } + + @override + @mustCallSuper + bool onTapDown(TapDownInfo info) { + if (buttonDown != null) { + if (button != null) { + remove(button!); + } + add(buttonDown!); + } + onPressed?.call(); + return false; + } + + @override + @mustCallSuper + bool onTapUp(TapUpInfo info) { + onTapCancel(); + return true; + } + + @override + @mustCallSuper + bool onTapCancel() { + if (buttonDown != null) { + remove(buttonDown!); + if (button != null) { + add(button!); + } + } + return false; + } +} diff --git a/packages/flame/lib/src/components/input/hud_button_component.dart b/packages/flame/lib/src/components/input/hud_button_component.dart index 5b848a40e66..d54177f4d71 100644 --- a/packages/flame/lib/src/components/input/hud_button_component.dart +++ b/packages/flame/lib/src/components/input/hud_button_component.dart @@ -1,4 +1,4 @@ -import 'package:flutter/rendering.dart' show EdgeInsets, VoidCallback; +import 'package:flutter/rendering.dart' show EdgeInsets; import 'package:meta/meta.dart'; import '../../../components.dart'; @@ -18,7 +18,7 @@ class HudButtonComponent extends HudMarginComponent with Tappable { /// Callback for what should happen when the button is pressed. /// If you want to interact with [onTapUp] or [onTapCancel] it is recommended /// to extend [HudButtonComponent]. - VoidCallback? onPressed; + void Function()? onPressed; HudButtonComponent({ this.button, diff --git a/packages/flame/lib/src/components/input/sprite_button_component.dart b/packages/flame/lib/src/components/input/sprite_button_component.dart new file mode 100644 index 00000000000..0bd0f53ef5b --- /dev/null +++ b/packages/flame/lib/src/components/input/sprite_button_component.dart @@ -0,0 +1,72 @@ +import '../../../components.dart'; +import '../../../extensions.dart'; +import '../../../input.dart'; + +enum _ButtonState { + up, + down, +} + +/// The [SpriteButtonComponent] bundles two [Sprite]s, one that shows while +/// the button is being pressed, and one that shows otherwise. +/// +/// Note: You have to set the [button] in [onLoad] if you are not passing it in +/// through the constructor. +class SpriteButtonComponent extends SpriteGroupComponent<_ButtonState> + with Tappable { + /// Callback for what should happen when the button is pressed. + void Function()? onPressed; + + Sprite? button; + Sprite? buttonDown; + + SpriteButtonComponent({ + this.button, + this.buttonDown, + this.onPressed, + Vector2? position, + Vector2? size, + Vector2? scale, + double? angle, + Anchor? anchor, + int? priority, + }) : super( + current: _ButtonState.up, + position: position, + size: size ?? button?.originalSize, + scale: scale, + angle: angle, + anchor: anchor, + priority: priority, + ); + + @override + void onMount() { + assert( + button != null, + 'The button sprite has to be set either in onLoad or in the constructor', + ); + sprites = {_ButtonState.up: button!}; + sprites![_ButtonState.down] = buttonDown ?? button!; + super.onMount(); + } + + @override + bool onTapDown(_) { + current = _ButtonState.down; + return false; + } + + @override + bool onTapUp(_) { + onTapCancel(); + return false; + } + + @override + bool onTapCancel() { + current = _ButtonState.up; + onPressed?.call(); + return false; + } +} diff --git a/packages/flame/test/effects2/opacity_effect_test.dart b/packages/flame/test/effects2/opacity_effect_test.dart index de4afa5577a..fcc8f583387 100644 --- a/packages/flame/test/effects2/opacity_effect_test.dart +++ b/packages/flame/test/effects2/opacity_effect_test.dart @@ -168,7 +168,7 @@ void main() { // It should change from 0-255 in 1s so it will change alpha with an // average of 255/100=2.5 per tick, which should not result in a need of // an epsilon value this high. - expectDouble(component.getOpacity(), 1.0, epsilon: 6 * _epsilon); + expectDouble(component.getOpacity(), 1.0, epsilon: 10 * _epsilon); }); }); }