Skip to content

Commit

Permalink
Step 3.8: Create a key-frame animation engine
Browse files Browse the repository at this point in the history
  • Loading branch information
DAB0mB committed Aug 29, 2018
1 parent 513c2c4 commit 6ce4287
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 0 deletions.
142 changes: 142 additions & 0 deletions resources/scripts/engine/animations/keyframe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
Engine.Animations.Keyframe = class Keyframe {
constructor(sprite, keyframes) {
this.sprite = sprite;
// The key-frames array contains objects with the properties of the
// sprite at the current time-point, e.g. width of 100 and height of 200
this.keyframes = keyframes;
this.age = 0;
this.frame = 0;
// This flag determines what's gonna happen to the animation once
// it's finished playing
this.repetitionMode = "none";
this.lastKeyframe = _.last(keyframes);
this.lastFrame = this.lastKeyframe.frame;

// These are the properties which we can animate
this.animables = [
"x", "y", "width", "height", "opacity"
];

// Set a map whose keys represent animatable properties and values represent an array
// with relevant key-frames to its belonging property
this.trimmedKeyframes = this.animables.reduce((trimmedKeyframes, key) => {
trimmedKeyframes[key] = keyframes.filter(keyframe => keyframe[key] != null);
return trimmedKeyframes;
}, {});

// Set initial properties on sprite based on initial key-frame
_.each(keyframes[0], (value, key) => {
if (this.animables.includes(key)) sprite[key] = value;
});
}

draw(context, offsetX, offsetY) {
this.sprite.draw(context, offsetX, offsetY);
}

update(span) {
if (!this.playing) return;

this.age += span;

switch (this.repetitionMode) {
// After one cycle animation would stop
case "none":
this.frame += span;

if (this.frame > this.lastFrame) {
this.frame = this.lastFrame;
this.playing = false;
}

break;

// Once finished, replay from the beginning
case "cyclic":
this.frame = this.age % this.lastFrame;
break;

// Once finished, play backwards, and so on
case "full":
this.frame = this.age % this.lastFrame;
let animationComplete = (this.age / this.lastFrame) % 2 >= 1;
if (animationComplete) this.frame = this.lastFrame - this.frame;
break;
}

// Update sprite properties based on given key-frame's easing mode
this.animables.forEach(key => {
let motion = this.getKeyframeMotion(key);

if (motion)
this.sprite[key] = this.calculateRelativeValue(motion, key);
});
}

play() {
this.playing = true;
}

pause() {
this.playing = false;
}

// Gets motion for current refresh
getKeyframeMotion(key) {
let keyframes = this.trimmedKeyframes[key];

// If no key-frames defined, motion is idle
if (keyframes == null) return;
// If there is only one key frame, motion is idle
if (keyframes.length < 2) return;
// If last frame reached, motion is idle
if (this.frame > _.last(keyframes).frame) return;

let start = this.findStartKeyframe(keyframes);
let end = this.findEndKeyframe(keyframes);
let ratio = this.getKeyframesRatio(start, end);

return { start, end, ratio };
}

// Gets the movement ratio
getKeyframesRatio(start, end) {
return (this.frame - start.frame) / (end.frame - start.frame);
}

// Get property end value based on current frame
findEndKeyframe(keyframes) {
return _.find(keyframes, keyframe =>
keyframe.frame >= (this.frame || 1)
);
}

// Get property start value based on current frame
findStartKeyframe(keyframes) {
let resultIndex;

keyframes.some((keyframe, currIndex) => {
if (keyframe.frame >= (this.frame || 1)) {
resultIndex = currIndex;
return true;
}
});

return keyframes[resultIndex - 1];
}

// Get a recalculated property value relative to provided easing mode
calculateRelativeValue(motion, key) {
let a = motion.start[key];
let b = motion.end[key];
let r = motion.ratio;
let easing = r > 0 ? motion.start.easing : motion.end.easing;

switch (easing) {
case "in": r = Math.sin((r * Math.PI) / 2); break;
case "out": r = Math.cos((r * Math.PI) / 2); break;
}

return ((b - a) * r) + a;
}
};
1 change: 1 addition & 0 deletions views/game.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<!-- Scripts -->
<script type="text/javascript" src="/scripts/namespaces.js"></script>
<script type="text/javascript" src="/scripts/engine/sprite.js"></script>
<script type="text/javascript" src="/scripts/engine/animations/keyframe.js"></script>
<script type="text/javascript" src="/scripts/engine/key_states.js"></script>
<script type="text/javascript" src="/scripts/engine/layer.js"></script>
<script type="text/javascript" src="/scripts/engine/screen.js"></script>
Expand Down

0 comments on commit 6ce4287

Please sign in to comment.