flame_example.dart 4.5 KB

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