Update: added frame rate controls!
I've been heads-down on art for our upcoming free-to-play game Lunch Bug and I thought I'd take a break to show you a little tool I just put together to improve my process. Have a look:
Please note that this is just the first draft of animations. Improvements are on the way!
In the center of the canvas is a game piece from Lunch Bug. The text in the upper-left can be pressed to play animations:
In Lunch Bug, pieces can be "captured" by being surrounded, which changes their appearance. Normally I could just have had a giant sprite sheet with all the images for the various types of plants, but we can't have any images larger than 1024x1024 pixels due to mobile memory restraints, which meant breaking them up into multiple files.
The complication of switching the image file part-way through the animations made using traditional animation tools difficult. That's why I built this tool! Now that I've got it, I'll be going back and iterating a few more times before the final version, so at this point feedback is especially valuable.
Normally I'd be open sourcing this on the Lost Decade Games GitHub account but it's tightly coupled to our internal game engine. We may open source the engine and its tools eventually but it's getting bulldozed too often to even consider it at the moment. What I can do is copypasta the code so you can get a general idea:
define([
"djinn/Game",
"djinn/assets",
"djinn/Sprite",
"djinn/TextView",
"djinn/utils/viewEffects"
], function (Game, assets, Sprite, TextView, effects) {
return Game.extend({
init: function (conf) {
// Conf
this._super(conf);
this.view.backgroundColor = "white";
// Pull in the assets we need
assets.load([
"media/images/seedling.png",
"media/images/bulb.png",
"media/images/berry.png"
]);
// Create the sprite subject
var state = "seedling";
var w = 100;
var h = 100;
this.sprite = new Sprite({
parent: this.view,
image: "media/images/seedling.png",
x: 0,
y: 0,
width: w,
height: h,
animations: {
"default": {
frames: [
[0, 0]
]
},
"put": {
frameRate: 5,
frames: [
[w * 4, 0],
[w * 3, 0],
[w * 2, 0],
[w, 0],
[0, 0]
],
loop: false
},
"idle": {
frameRate: 12,
frames: [
[0, h],
[w, h],
[w * 2, h],
[w * 3, h],
[w * 4, h]
],
loop: false
},
"grow": {
frameRate: 8,
frames: [
[0, h * 2],
[w, h * 2],
[w * 2, h * 2],
[w * 3, h * 2],
[w * 4, h * 2]
],
loop: false
},
"shrink": {
frameRate: 8,
frames: [
[w * 4, h * 2],
[w * 3, h * 2],
[w * 2, h * 2],
[w, h * 2],
[0, h * 2]
],
loop: false
}
}
}).align("center", "center");
var x = 10;
var height = TextView.defaults.fontSize = 32;
// Put
var putView = new TextView({
parent: this.view,
text: "Put",
x: x,
}).align("center").on("inputStart", this, function () {
// Setup
this.sprite.completeTween();
this.sprite.opacity = 0;
// Execute
this.sprite.playAnimation("put");
effects.fadeIn(this.sprite, {
duration: 500
});
});
// Idle
var idleView = new TextView({
parent: this.view,
text: "Idle",
x: x,
y: height
}).align("center").on("inputStart", this, function () {
this.sprite.playAnimation("idle");
});
// Grow
var growView = new TextView({
parent: this.view,
text: "Grow",
x: x,
y: height * 2
}).align("center").on("inputStart", this, function () {
if (state == "berry") { return; }
var onAnimationEnd = function () {
this.sprite.off("animationEnd", this, onAnimationEnd);
if (state == "seedling") {
state = "bulb";
this.sprite.image = "media/images/bulb.png";
} else {
state = "berry";
this.sprite.image = "media/images/berry.png";
}
this.sprite.playAnimation("default");
};
this.sprite.on("animationEnd", this, onAnimationEnd);
this.sprite.playAnimation("grow");
});
// Shrink
var shrinkView = new TextView({
parent: this.view,
text: "Shrink",
x: x,
y: height * 3
}).align("center").on("inputStart", this, function () {
if (state == "seedling") { return; }
if (state == "berry") {
state = "bulb";
this.sprite.image = "media/images/bulb.png";
} else {
state = "seedling";
this.sprite.image = "media/images/seedling.png";
}
var onAnimationEnd = function () {
this.sprite.off("animationEnd", this, onAnimationEnd);
this.sprite.playAnimation("default");
};
this.sprite.on("animationEnd", this, onAnimationEnd);
this.sprite.playAnimation("shrink");
});
}
});
});
Might be easier to read in this gist. Note that the tool is not very robust as I didn't want to spend too much time on it…
More Lunch Bug news is just around the corner (can you say playable prototype?!) so be sure to subscribe to get it when it's hot. Thanks for reading!
LDG © 2022 • Blog • Terms of Service • Video Policy • v2.1.2