1
+ Engine . Animations . Keyframe = class Keyframe {
2
+ constructor ( sprite , keyframes ) {
3
+ this . sprite = sprite ;
4
+ // The key-frames array contains objects with the properties of the
5
+ // sprite at the current time-point, e.g. width of 100 and height of 200
6
+ this . keyframes = keyframes ;
7
+ this . age = 0 ;
8
+ this . frame = 0 ;
9
+ // This flag determines what's gonna happen to the animation once
10
+ // it's finished playing
11
+ this . repetitionMode = "none" ;
12
+ this . lastKeyframe = _ . last ( keyframes ) ;
13
+ this . lastFrame = this . lastKeyframe . frame ;
14
+
15
+ // These are the properties which we can animate
16
+ this . animables = [
17
+ "x" , "y" , "width" , "height" , "opacity"
18
+ ] ;
19
+
20
+ // Set a map whose keys represent animatable properties and values represent an array
21
+ // with relevant key-frames to its belonging property
22
+ this . trimmedKeyframes = this . animables . reduce ( ( trimmedKeyframes , key ) => {
23
+ trimmedKeyframes [ key ] = keyframes . filter ( keyframe => keyframe [ key ] != null ) ;
24
+ return trimmedKeyframes ;
25
+ } , { } ) ;
26
+
27
+ // Set initial properties on sprite based on initial key-frame
28
+ _ . each ( keyframes [ 0 ] , ( value , key ) => {
29
+ if ( this . animables . includes ( key ) ) sprite [ key ] = value ;
30
+ } ) ;
31
+ }
32
+
33
+ draw ( context , offsetX , offsetY ) {
34
+ this . sprite . draw ( context , offsetX , offsetY ) ;
35
+ }
36
+
37
+ update ( span ) {
38
+ if ( ! this . playing ) return ;
39
+
40
+ this . age += span ;
41
+
42
+ switch ( this . repetitionMode ) {
43
+ // After one cycle animation would stop
44
+ case "none" :
45
+ this . frame += span ;
46
+
47
+ if ( this . frame > this . lastFrame ) {
48
+ this . frame = this . lastFrame ;
49
+ this . playing = false ;
50
+ }
51
+
52
+ break ;
53
+
54
+ // Once finished, replay from the beginning
55
+ case "cyclic" :
56
+ this . frame = this . age % this . lastFrame ;
57
+ break ;
58
+
59
+ // Once finished, play backwards, and so on
60
+ case "full" :
61
+ this . frame = this . age % this . lastFrame ;
62
+ let animationComplete = ( this . age / this . lastFrame ) % 2 >= 1 ;
63
+ if ( animationComplete ) this . frame = this . lastFrame - this . frame ;
64
+ break ;
65
+ }
66
+
67
+ // Update sprite properties based on given key-frame's easing mode
68
+ this . animables . forEach ( key => {
69
+ let motion = this . getKeyframeMotion ( key ) ;
70
+
71
+ if ( motion )
72
+ this . sprite [ key ] = this . calculateRelativeValue ( motion , key ) ;
73
+ } ) ;
74
+ }
75
+
76
+ play ( ) {
77
+ this . playing = true ;
78
+ }
79
+
80
+ pause ( ) {
81
+ this . playing = false ;
82
+ }
83
+
84
+ // Gets motion for current refresh
85
+ getKeyframeMotion ( key ) {
86
+ let keyframes = this . trimmedKeyframes [ key ] ;
87
+
88
+ // If no key-frames defined, motion is idle
89
+ if ( keyframes == null ) return ;
90
+ // If there is only one key frame, motion is idle
91
+ if ( keyframes . length < 2 ) return ;
92
+ // If last frame reached, motion is idle
93
+ if ( this . frame > _ . last ( keyframes ) . frame ) return ;
94
+
95
+ let start = this . findStartKeyframe ( keyframes ) ;
96
+ let end = this . findEndKeyframe ( keyframes ) ;
97
+ let ratio = this . getKeyframesRatio ( start , end ) ;
98
+
99
+ return { start, end, ratio } ;
100
+ }
101
+
102
+ // Gets the movement ratio
103
+ getKeyframesRatio ( start , end ) {
104
+ return ( this . frame - start . frame ) / ( end . frame - start . frame ) ;
105
+ }
106
+
107
+ // Get property end value based on current frame
108
+ findEndKeyframe ( keyframes ) {
109
+ return _ . find ( keyframes , keyframe =>
110
+ keyframe . frame >= ( this . frame || 1 )
111
+ ) ;
112
+ }
113
+
114
+ // Get property start value based on current frame
115
+ findStartKeyframe ( keyframes ) {
116
+ let resultIndex ;
117
+
118
+ keyframes . some ( ( keyframe , currIndex ) => {
119
+ if ( keyframe . frame >= ( this . frame || 1 ) ) {
120
+ resultIndex = currIndex ;
121
+ return true ;
122
+ }
123
+ } ) ;
124
+
125
+ return keyframes [ resultIndex - 1 ] ;
126
+ }
127
+
128
+ // Get a recalculated property value relative to provided easing mode
129
+ calculateRelativeValue ( motion , key ) {
130
+ let a = motion . start [ key ] ;
131
+ let b = motion . end [ key ] ;
132
+ let r = motion . ratio ;
133
+ let easing = r > 0 ? motion . start . easing : motion . end . easing ;
134
+
135
+ switch ( easing ) {
136
+ case "in" : r = Math . sin ( ( r * Math . PI ) / 2 ) ; break ;
137
+ case "out" : r = Math . cos ( ( r * Math . PI ) / 2 ) ; break ;
138
+ }
139
+
140
+ return ( ( b - a ) * r ) + a ;
141
+ }
142
+ } ;
0 commit comments