flame_example.dart 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import 'dart:math';
  2. import 'package:flame/components.dart';
  3. import 'package:flame/game.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:esotericsoftware_spine_flutter/spine_flutter.dart';
  6. class SpineComponent extends PositionComponent {
  7. final BoundsProvider _boundsProvider;
  8. final SkeletonDrawable _drawable;
  9. late final Bounds _bounds;
  10. final bool _ownsDrawable;
  11. SpineComponent(this._drawable, {
  12. bool ownsDrawable = true,
  13. BoundsProvider boundsProvider = const SetupPoseBounds(),
  14. super.position,
  15. super.scale,
  16. double super.angle = 0.0,
  17. Anchor super.anchor = Anchor.topLeft,
  18. super.children,
  19. super.priority,
  20. }) :
  21. _ownsDrawable = ownsDrawable,
  22. _boundsProvider = boundsProvider {
  23. _drawable.update(0);
  24. _bounds = _boundsProvider.computeBounds(_drawable);
  25. size = Vector2(_bounds.width, _bounds.height);
  26. }
  27. static Future<SpineComponent> fromAssets(String atlasFile, String skeletonFile, {
  28. AssetBundle? bundle, BoundsProvider boundsProvider = const SetupPoseBounds(),
  29. Vector2? position,
  30. Vector2? scale,
  31. double angle = 0.0,
  32. Anchor anchor = Anchor.topLeft,
  33. Iterable<Component>? children,
  34. int? priority,
  35. }) async {
  36. final drawable = await SkeletonDrawable.fromAsset(atlasFile, skeletonFile, bundle: bundle);
  37. return SpineComponent(
  38. drawable,
  39. ownsDrawable: true,
  40. boundsProvider: boundsProvider,
  41. position: position,
  42. scale: scale,
  43. angle: angle,
  44. anchor: anchor,
  45. children: children,
  46. priority: priority);
  47. }
  48. void dispose() {
  49. if (_ownsDrawable) {
  50. _drawable.dispose();
  51. }
  52. }
  53. @override
  54. void update(double dt) {
  55. _drawable.update(dt);
  56. }
  57. @override
  58. void render(Canvas canvas) {
  59. canvas.save();
  60. canvas.translate(-_bounds.x, -_bounds.y);
  61. _drawable.renderToCanvas(canvas);
  62. canvas.restore();
  63. }
  64. get animationState => _drawable.animationState;
  65. get animationStateData => _drawable.animationStateData;
  66. get skeleton => _drawable.skeleton;
  67. }
  68. class SimpleFlameExample extends FlameGame {
  69. late final SpineComponent spineboy;
  70. @override
  71. Future<void> onLoad() async {
  72. // Load the Spineboy atlas and skeleton data from asset files
  73. // and create a SpineComponent from them, scaled down and
  74. // centered on the screen
  75. spineboy = await SpineComponent.fromAssets(
  76. "assets/spineboy.atlas", "assets/spineboy-pro.skel",
  77. scale: Vector2(0.4, 0.4),
  78. anchor: Anchor.center,
  79. position: Vector2(size.x / 2, size.y / 2)
  80. );
  81. // Set the "walk" animation on track 0 in looping mode
  82. spineboy.animationState.setAnimationByName(0, "walk", true);
  83. await add(spineboy);
  84. }
  85. @override
  86. void onDetach() {
  87. // Dispose the native resources that have been loaded for spineboy.
  88. spineboy.dispose();
  89. }
  90. }
  91. class PreloadAndShareSpineDataExample extends FlameGame {
  92. late final SkeletonData cachedSkeletonData;
  93. late final Atlas cachedAtlas;
  94. late final List<SpineComponent> spineboys = [];
  95. @override
  96. Future<void> onLoad() async {
  97. // Pre-load the atlas and skeleton data once.
  98. cachedAtlas = await Atlas.fromAsset("assets/spineboy.atlas");
  99. cachedSkeletonData = await SkeletonData.fromAsset(cachedAtlas, "assets/spineboy-pro.skel");
  100. // Instantiate many spineboys from the pre-loaded data. Each SpineComponent
  101. // gets their own SkeletonDrawable copy derived from the cached data. The
  102. // SkeletonDrawable copies do not own the underlying skeleton data and atlas.
  103. final rng = Random();
  104. for (int i = 0; i < 100; i++) {
  105. final drawable = SkeletonDrawable(cachedAtlas, cachedSkeletonData, false);
  106. final scale = 0.1 + rng.nextDouble() * 0.2;
  107. final position = Vector2(rng.nextDouble() * size.x, rng.nextDouble() * size.y);
  108. final spineboy = SpineComponent(
  109. drawable,
  110. scale: Vector2(scale, scale),
  111. position: position
  112. );
  113. spineboy.animationState.setAnimationByName(0, "walk", true);
  114. spineboys.add(spineboy);
  115. await add(spineboy);
  116. }
  117. }
  118. @override
  119. void onDetach() {
  120. // Dispose the pre-loaded atlas and skeleton data when the game/scene is removed
  121. cachedAtlas.dispose();
  122. cachedSkeletonData.dispose();
  123. for (final spineboy in spineboys) {
  124. spineboy.dispose();
  125. }
  126. }
  127. }
  128. class SpineFlameGameWidget extends StatelessWidget {
  129. final FlameGame game;
  130. const SpineFlameGameWidget(this.game, {super.key});
  131. @override
  132. Widget build(BuildContext context) {
  133. return Scaffold(
  134. appBar: AppBar(title: const Text('Flame Integration')),
  135. body: GameWidget(game: game)
  136. );
  137. }
  138. }