forked from oakmound/oak
/
animation.go
151 lines (132 loc) · 3.7 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
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
package render
import (
"errors"
"image"
"image/draw"
"time"
"github.com/oakmound/oak/event"
"github.com/oakmound/oak/physics"
"github.com/oakmound/oak/timing"
)
// Sheet is a 2D array of image rgbas
type Sheet [][]*image.RGBA
//SubSprite gets a sprite from a sheet at the given location
func (sh *Sheet) SubSprite(x, y int) *Sprite {
return NewSprite(0, 0, (*sh)[x][y])
}
//Animation takes a set of frames and provides a framework for animating them
type Animation struct {
LayeredPoint
pauseBool
sheetPos int
frameTime int64
frames [][]int
sheet *Sheet
lastChange time.Time
playing bool
Interruptable bool
cID event.CID
}
//NewAnimation creates an Animation
func NewAnimation(sheetP *Sheet, fps float64, frames []int) (*Animation, error) {
if len(frames)%2 != 0 {
return nil, errors.New("Uneven number of animation coordinates")
}
splitFrames := make([][]int, len(frames)/2)
for i := 0; i < len(frames); i += 2 {
splitFrames[i/2] = []int{frames[i], frames[i+1]}
}
animation := Animation{
LayeredPoint: LayeredPoint{
Vector: physics.NewVector(0, 0),
},
sheetPos: 0,
frameTime: timing.FPSToNano(fps),
frames: splitFrames,
sheet: sheetP,
lastChange: time.Now(),
playing: true,
Interruptable: true,
}
return &animation, nil
}
//Copy creates a new Modifiable that is a copy of the current animation.
func (a *Animation) Copy() Modifiable {
newA := new(Animation)
newA.LayeredPoint = a.LayeredPoint.Copy()
newA.sheetPos = a.sheetPos
newA.frameTime = a.frameTime
newA.frames = a.frames
newA.lastChange = a.lastChange
newA.playing = a.playing
newA.Interruptable = a.Interruptable
newA.cID = a.cID
// Manual deep copy of pointers
aSheet := *a.sheet
newA.sheet = new(Sheet)
newSheet := make(Sheet, len(aSheet))
for x, col := range aSheet {
newSheet[x] = make([]*image.RGBA, len(aSheet[x]))
for y, val := range col {
newRGBA := new(image.RGBA)
*newRGBA = *val
newSheet[x][y] = newRGBA
}
}
*newA.sheet = newSheet
return newA
}
//SetTriggerID sets animation's id to the passed in id.
func (a *Animation) SetTriggerID(id event.CID) {
a.cID = id
}
func (a *Animation) update() {
if a.playing && time.Since(a.lastChange).Nanoseconds() > a.frameTime {
a.lastChange = time.Now()
a.sheetPos = (a.sheetPos + 1) % len(a.frames)
// Eventually, if an animation is cut off before
// it finishes by another animation starting,
// AnimationCancelled (or maybe AnimationShortCircuit)
// should trigger instead of AnimationEnd
if a.sheetPos == 0 && a.cID != 0 {
a.cID.Trigger("AnimationEnd", nil)
}
}
}
//DrawOffset draws the animation with some x,y offset from its logical location
func (a *Animation) DrawOffset(buff draw.Image, xOff, yOff float64) {
a.update()
img := a.GetRGBA()
ShinyDraw(buff, img, int(a.X()+xOff), int(a.Y()+yOff))
}
//Draw draws the animation at its logical location
func (a *Animation) Draw(buff draw.Image) {
a.update()
img := a.GetRGBA()
ShinyDraw(buff, img, int(a.X()), int(a.Y()))
}
//GetRGBA returns the current frames rgba
func (a *Animation) GetRGBA() *image.RGBA {
return (*a.sheet)[a.frames[a.sheetPos][0]][a.frames[a.sheetPos][1]]
}
//GetDims returns the dimensions of the animation in terms of x, y
func (a *Animation) GetDims() (int, int) {
r := a.GetRGBA()
return r.Bounds().Max.X, r.Bounds().Max.Y
}
//Modify applies a set of modifications to the Animation
func (a *Animation) Modify(ms ...Modification) Modifiable {
sheet := *a.sheet
for x, row := range sheet {
for y, rgba := range row {
for _, m := range ms {
sheet[x][y] = m(rgba)
}
}
}
return a
}
// IsStatic returns false for animations
func (a *Animation) IsStatic() bool {
return false
}