-
Notifications
You must be signed in to change notification settings - Fork 1
/
anim.go
197 lines (170 loc) · 5.26 KB
/
anim.go
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
package anim
import (
"image"
"math"
"github.com/hajimehoshi/ebiten/v2"
)
// AnimationPlayer plays and manages animations.
type AnimationPlayer struct {
// Image atlas for all animation states
SpriteSheet *ebiten.Image
// Current frame of the current animation.
//
// The frame is dynamically updated with `AnimationPlayer.Update()`.
CurrentFrame *ebiten.Image
// Animations for states
Animations map[string]*Animation
// If true, animations will be paused.
Paused bool
// Current state name (animation name)
CurrentState string
tick float64
currentFrameIndex int
}
// NewAnimationPlayer returns new AnimationPlayer with spriteSheet
func NewAnimationPlayer(spriteSheet *ebiten.Image) *AnimationPlayer {
return &AnimationPlayer{
SpriteSheet: spriteSheet,
Paused: false,
Animations: make(map[string]*Animation),
currentFrameIndex: 0,
}
}
// NewAnimationState appends a new Animation to the AnimationPlayer
// and returns the Animation.
//
// The default FPS is 15.
//
// Parameters:
//
// - x, y - Top-left coordinates of the first frame's rectangle.
// - w, h - Width and height of the first frame's rectangle.
// - frameCount - Animation frame count
// - vertical - If true, frames are appended vertically, otherwise horizontally.
// - pingPong - If true, arranges the animation indexes to play back and forth. [0 1 2 3 2 1]
func (ap *AnimationPlayer) NewAnimationState(
stateName string,
x, y,
w, h,
frameCount int,
pingPong bool,
vertical bool) *Animation {
subImages := SubImages(ap.SpriteSheet, x, y, w, h, frameCount, vertical)
if pingPong {
subImages = MakePingPong(subImages)
}
animation := NewAnimation(stateName, subImages, 15)
ap.CurrentState = stateName
ap.Animations[stateName] = animation
ap.CurrentFrame = ap.Animations[ap.CurrentState].Frames[ap.currentFrameIndex]
return animation
}
// CurrentFrameIndex returns current index.
// May be useful for debugging.
func (ap *AnimationPlayer) CurrentFrameIndex() int {
return ap.currentFrameIndex
}
// SetAllFPS overwrites the FPS of all animations.
func (ap *AnimationPlayer) SetAllFPS(FPS float64) {
for _, anim := range ap.Animations {
anim.FPS = FPS
}
}
// AddAnimation adds the given animation to this player.
// Adds the name of the animation as a map key.
func (ap *AnimationPlayer) AddAnimation(a *Animation) {
ap.Animations[a.Name] = a
}
// State returns current active animation state
func (ap *AnimationPlayer) State() string {
return ap.CurrentState
}
// CurrentStateFPS returns FPS of the current animation state
func (ap *AnimationPlayer) CurrentStateFPS() float64 {
return ap.Animations[ap.State()].FPS
}
// SetStateFPS sets FPS of the animation state.
//
// // Shortcut func for
// AnimationPlayer.Animations[stateName].FPS = 15
// Animation.FPS = 15
func (ap *AnimationPlayer) SetStateFPS(stateName string, FPS float64) {
ap.Animations[stateName].FPS = FPS
}
// SetStateAndReset sets the animation state and resets to the first frame.
func (ap *AnimationPlayer) SetStateAndReset(state string) {
if ap.CurrentState != state {
ap.CurrentState = state
ap.tick = 0
ap.currentFrameIndex = 0
}
}
// SetState sets the animation state.
func (ap *AnimationPlayer) SetState(state string) {
if ap.CurrentState != state {
ap.CurrentState = state
}
}
// PauseAtFrame pauses the current animation at the frame. If index is out of range it does nothing.
func (ap *AnimationPlayer) PauseAtFrame(frameIndex int) {
if frameIndex < len(ap.Animations[ap.State()].Frames) && frameIndex >= 0 {
ap.Paused = true
ap.currentFrameIndex = frameIndex
}
}
// Update updates AnimationPlayer. Place this func inside Ebitengine `Game.Update()`.
//
// // example
// func (g *Game) Update() error {
// animPlayer.Update()
// ...
func (ap *AnimationPlayer) Update() {
if !ap.Paused {
ap.tick += ap.Animations[ap.CurrentState].FPS / 60.0
ap.currentFrameIndex = int(math.Floor(ap.tick))
if ap.currentFrameIndex >= len(ap.Animations[ap.CurrentState].Frames) {
ap.tick = 0
ap.currentFrameIndex = 0
}
}
// update current frame
ap.CurrentFrame = ap.Animations[ap.CurrentState].Frames[ap.currentFrameIndex]
}
// Animation for AnimationPlayer
type Animation struct {
Name string // Name of the aimation state
Frames []*ebiten.Image // Animation frames
FPS float64 // Animation playback speed (Frames Per Second).
}
// NewAnimation returns new Animation
func NewAnimation(name string, frames []*ebiten.Image, FPS float64) *Animation {
return &Animation{
Name: name,
Frames: frames,
FPS: FPS,
}
}
// SubImages returns sub-images from spriteSheet image
func SubImages(spriteSheet *ebiten.Image, x, y, w, h, subImageCount int, vertical bool) []*ebiten.Image {
subImages := []*ebiten.Image{}
frameRect := image.Rect(x, y, x+w, y+h)
for i := 0; i < subImageCount; i++ {
subImages = append(subImages, spriteSheet.SubImage(frameRect).(*ebiten.Image))
if vertical {
frameRect.Min.Y += h
frameRect.Max.Y += h
} else {
frameRect.Min.X += w
frameRect.Max.X += w
}
}
return subImages
}
// MakePingPong arranges the animation indexes to play back and forth.
// [0 1 2 3] -> [0 1 2 3 2 1]
func MakePingPong(frames []*ebiten.Image) []*ebiten.Image {
for i := len(frames) - 2; i > 0; i-- {
frames = append(frames, frames[i])
}
return frames
}