-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanimation.go
116 lines (100 loc) · 3.41 KB
/
animation.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
package engine
import (
"image"
"log"
"time"
"engine/vec2"
"github.com/hajimehoshi/ebiten/v2"
)
// Animation provides logic to generate an animation from a single sprite sheet.
// All frames of the animation need to be on this sprite sheet, also all tiles
// on the sprite sheet (one tile on sprite sheet will make up one frame in
// animation) need to be of the same size.
// Animation allows to set the transition times between all frames separately.
type Animation struct {
spritesheet *ebiten.Image
tileSize vec2.I // size of each tile on spritesheet
tiles []vec2.I // list of coordinates for tiles on spritesheet
delays []time.Duration // delay befor going to next frame
lastFrameChange time.Time
currentFrame int // index into `tiles` & `delays` of current frame
paused bool // pauses animation
}
// NewAnimation builds a new animation from the provided parameters
func NewAnimation(spritesheet *ebiten.Image, tileSize vec2.I, tiles []vec2.I, delays []time.Duration) *Animation {
if len(tiles) != len(delays) {
log.Printf("ERR: NewAnimation: tiles and delays need to be of same length.")
return nil
}
if width, _ := spritesheet.Size(); width%tileSize.X != 0 {
log.Printf("WARN: Spritesheet width isn't multiple of tilesize width.")
}
if _, height := spritesheet.Size(); height&tileSize.Y != 0 {
log.Printf("WARN: Spritesheet height isn't multiple of tilesize height.")
}
anim := &Animation{
spritesheet: spritesheet,
tileSize: tileSize,
tiles: tiles,
delays: delays,
lastFrameChange: time.Now(),
currentFrame: 0,
paused: false,
}
return anim
}
// Update needs to be called regularly so that animation progresses.
func (anim *Animation) Update() {
if anim.paused {
return
}
if time.Now().After(anim.nextFrameChange()) {
anim.lastFrameChange = time.Now()
anim.currentFrame = anim.nextFrame()
}
}
// CurrentImage returns image of animation that is ought to be displayed.
func (anim *Animation) CurrentImage() *ebiten.Image {
minX := anim.currentTile().X * anim.tileSize.X
minY := anim.currentTile().Y * anim.tileSize.Y
return anim.spritesheet.SubImage(image.Rect(
minX, minY,
minX+anim.tileSize.X, minY+anim.tileSize.Y),
).(*ebiten.Image)
}
// Pause going through single frames that make up animation.
func (anim *Animation) Pause() {
anim.paused = true
}
// Resume going through frames. Time that was spend paused is not counted, so
// transition to next frame is probably instant.
func (anim *Animation) Resume() {
anim.paused = false
}
// Reset animation back to first frame.
func (anim *Animation) Reset() {
anim.currentFrame = 0
anim.lastFrameChange = time.Now()
}
func (anim *Animation) currentTile() vec2.I {
return anim.tiles[anim.currentFrame]
}
func (anim *Animation) currentDelay() time.Duration {
return anim.delays[anim.currentFrame]
}
func (anim *Animation) nextFrameChange() time.Time {
return anim.lastFrameChange.Add(anim.currentDelay())
}
func (anim *Animation) nextFrame() int {
return (anim.currentFrame + 1) % len(anim.tiles)
}
// UniformDuration is a helper function to create a slice with `n` times the
// same duration. Can be used to create a NewAnimation without repeating
// same duration multiple times.
func UniformDuration(d time.Duration, n int) []time.Duration {
duration := make([]time.Duration, n)
for i := range duration {
duration[i] = d
}
return duration
}