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