flame_example.dart 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. ///
  2. /// Spine Runtimes License Agreement
  3. /// Last updated July 28, 2023. Replaces all prior versions.
  4. ///
  5. /// Copyright (c) 2013-2023, Esoteric Software LLC
  6. ///
  7. /// Integration of the Spine Runtimes into software or otherwise creating
  8. /// derivative works of the Spine Runtimes is permitted under the terms and
  9. /// conditions of Section 2 of the Spine Editor License Agreement:
  10. /// http://esotericsoftware.com/spine-editor-license
  11. ///
  12. /// Otherwise, it is permitted to integrate the Spine Runtimes into software or
  13. /// otherwise create derivative works of the Spine Runtimes (collectively,
  14. /// "Products"), provided that each user of the Products must obtain their own
  15. /// Spine Editor license and redistribution of the Products in any form must
  16. /// include this license and copyright notice.
  17. ///
  18. /// THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. /// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. /// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. /// DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. /// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. /// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. /// BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. /// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. /// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
  27. /// SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. ///
  29. import 'dart:math';
  30. import 'package:spine_flutter/spine_flutter.dart';
  31. import 'package:flame/components.dart';
  32. import 'package:flame/game.dart';
  33. import 'package:flutter/material.dart';
  34. class SpineComponent extends PositionComponent {
  35. final BoundsProvider _boundsProvider;
  36. final SkeletonDrawable _drawable;
  37. late final Bounds _bounds;
  38. final bool _ownsDrawable;
  39. SpineComponent(
  40. this._drawable, {
  41. bool ownsDrawable = true,
  42. BoundsProvider boundsProvider = const SetupPoseBounds(),
  43. super.position,
  44. super.scale,
  45. double super.angle = 0.0,
  46. Anchor super.anchor = Anchor.topLeft,
  47. super.children,
  48. super.priority,
  49. }) : _ownsDrawable = ownsDrawable,
  50. _boundsProvider = boundsProvider {
  51. _drawable.update(0);
  52. _bounds = _boundsProvider.computeBounds(_drawable);
  53. size = Vector2(_bounds.width, _bounds.height);
  54. }
  55. static Future<SpineComponent> fromAssets(
  56. String atlasFile,
  57. String skeletonFile, {
  58. AssetBundle? bundle,
  59. BoundsProvider boundsProvider = const SetupPoseBounds(),
  60. Vector2? position,
  61. Vector2? scale,
  62. double angle = 0.0,
  63. Anchor anchor = Anchor.topLeft,
  64. Iterable<Component>? children,
  65. int? priority,
  66. }) async {
  67. return SpineComponent(await SkeletonDrawable.fromAsset(atlasFile, skeletonFile, bundle: bundle),
  68. ownsDrawable: true,
  69. boundsProvider: boundsProvider,
  70. position: position,
  71. scale: scale,
  72. angle: angle,
  73. anchor: anchor,
  74. children: children,
  75. priority: priority);
  76. }
  77. void dispose() {
  78. if (_ownsDrawable) {
  79. _drawable.dispose();
  80. }
  81. }
  82. @override
  83. void update(double dt) {
  84. _drawable.update(dt);
  85. }
  86. @override
  87. void render(Canvas canvas) {
  88. canvas.save();
  89. canvas.translate(-_bounds.x, -_bounds.y);
  90. _drawable.renderToCanvas(canvas);
  91. canvas.restore();
  92. }
  93. get animationState => _drawable.animationState;
  94. get animationStateData => _drawable.animationStateData;
  95. get skeleton => _drawable.skeleton;
  96. }
  97. class SimpleFlameExample extends FlameGame {
  98. late final SpineComponent spineboy;
  99. @override
  100. Future<void> onLoad() async {
  101. // Load the Spineboy atlas and skeleton data from asset files
  102. // and create a SpineComponent from them, scaled down and
  103. // centered on the screen
  104. spineboy = await SpineComponent.fromAssets("assets/spineboy.atlas", "assets/spineboy-pro.json",
  105. scale: Vector2(0.4, 0.4), anchor: Anchor.center, position: Vector2(size.x / 2, size.y / 2));
  106. // Set the "walk" animation on track 0 in looping mode
  107. spineboy.animationState.setAnimationByName(0, "walk", true);
  108. await add(spineboy);
  109. }
  110. @override
  111. void onDetach() {
  112. // Dispose the native resources that have been loaded for spineboy.
  113. spineboy.dispose();
  114. }
  115. }
  116. class DragonExample extends FlameGame {
  117. late final Atlas cachedAtlas;
  118. late final SkeletonData cachedSkeletonData;
  119. late final SpineComponent dragon;
  120. @override
  121. Future<void> onLoad() async {
  122. cachedAtlas = await Atlas.fromAsset("assets/dragon.atlas");
  123. cachedSkeletonData = await SkeletonData.fromAsset(cachedAtlas, "assets/dragon-ess.json");
  124. final drawable = SkeletonDrawable(cachedAtlas, cachedSkeletonData, false);
  125. dragon = SpineComponent(
  126. drawable,
  127. scale: Vector2(0.4, 0.4),
  128. anchor: Anchor.center,
  129. position: Vector2(size.x / 2, size.y / 2 - 150),
  130. );
  131. // Set the "walk" animation on track 0 in looping mode
  132. dragon.animationState.setAnimationByName(0, "flying", true);
  133. await add(dragon);
  134. }
  135. @override
  136. void onDetach() {
  137. // Dispose the native resources that have been loaded for spineboy.
  138. dragon.dispose();
  139. cachedSkeletonData.dispose();
  140. cachedAtlas.dispose();
  141. }
  142. }
  143. class PreloadAndShareSpineDataExample extends FlameGame {
  144. late final SkeletonData cachedSkeletonData;
  145. late final Atlas cachedAtlas;
  146. late final List<SpineComponent> spineboys = [];
  147. @override
  148. Future<void> onLoad() async {
  149. // Pre-load the atlas and skeleton data once.
  150. cachedAtlas = await Atlas.fromAsset("assets/spineboy.atlas");
  151. cachedSkeletonData = await SkeletonData.fromAsset(cachedAtlas, "assets/spineboy-pro.skel");
  152. // Instantiate many spineboys from the pre-loaded data. Each SpineComponent
  153. // gets their own SkeletonDrawable copy derived from the cached data. The
  154. // SkeletonDrawable copies do not own the underlying skeleton data and atlas.
  155. final rng = Random();
  156. for (int i = 0; i < 100; i++) {
  157. final drawable = SkeletonDrawable(cachedAtlas, cachedSkeletonData, false);
  158. final scale = 0.1 + rng.nextDouble() * 0.2;
  159. final position = Vector2(rng.nextDouble() * size.x, rng.nextDouble() * size.y);
  160. final spineboy = SpineComponent(drawable, scale: Vector2(scale, scale), position: position);
  161. spineboy.animationState.setAnimationByName(0, "walk", true);
  162. spineboys.add(spineboy);
  163. await add(spineboy);
  164. }
  165. }
  166. @override
  167. void onDetach() {
  168. // Dispose the pre-loaded atlas and skeleton data when the game/scene is removed.
  169. cachedAtlas.dispose();
  170. cachedSkeletonData.dispose();
  171. // Dispose each spineboy and its internal SkeletonDrawable.
  172. for (final spineboy in spineboys) {
  173. spineboy.dispose();
  174. }
  175. }
  176. }
  177. class SpineFlameGameWidget extends StatelessWidget {
  178. final FlameGame game;
  179. const SpineFlameGameWidget(this.game, {super.key});
  180. @override
  181. Widget build(BuildContext context) {
  182. return Scaffold(appBar: AppBar(title: const Text('Flame Integration')), body: GameWidget(game: game));
  183. }
  184. }