(Activity time: about 60 minutes)
(Download the Day Four Files Here)
Previous posts:
Game Jam Day 1
Game Jam Day 2
Game Jam Day 3
Intro
Hello, and and welcome to day 4 of the Dad and Daughter Quarantine Game Jam Tutorial!
We’re making a game called Mia’s Daring Escape. On day 1, we set up the game and put Mia on the screen. On day 2, we gave Mia a brick level to run, and a jumping skill. On day 3, we made falling bricks that stacked up on the ground or bopped Mia on the head.
Today’s pure fun day. Today we add flavor!
You’re definitely going to have to download the day four files, so you can get the new sounds and new sprites!
You can keep the game.js file you’ve been working, but be sure to swap out index.html for the new one, because it has some code to load those sounds.
First Task: Have fun with sounds!
Games are always better with sound. I’ve added a lot of sound files to the project.
Grab the Sound folder from the today’s project files and put it in your game folder.
Then, grab the new copy of index.html and replace your old copy.
Detail: if you look at the two copies of index.html, you’ll see that the only difference is loading a bunch of sound files in html, like this:
<audio preload="auto" id="coin" ><source src="Sound/coin.wav" type="audio/wav"></audio>
There’s already code in plumbing.js to let you play sound and music.
Try it out by adding these lines to game.js:
let mia = null;
let bricks = [];
let stacks = {};
let colors = [
color(1,0,0), // Red
color(0,1,0), // Green
color(0,0,1), // Blue
]
let tracker = 0;
let last_brick = Date.now();
music_volume = 0.4;
sound_volume = 0.6;
setMusic("music_6");
...
...
...
// Check if Mia has fallen on top of the current stack.
if (mia.state == "falling" && mia.y >= floor_height && mia.y_velocity > 0) {
mia.y = floor_height;
mia.y_velocity = 0;
mia.state = "running";
soundEffect("hyah_3");
}
...
...
...
// If the space bar got pushed, make Mia jump
if (key_down[" "] && mia.state == "running") {
mia.state = "jumping";
mia.y_velocity = -20;
soundEffect("jump_3");
}
...
...
...
Zounds! Sounds!
Kid, here’s your sound menu:
airhorn
cat
chicken
clunk
coin_1
coin_2
countdown
damage_1
damage_2
ding_ding_ding
dog
explosion
fairy_down
fairy_up
fireball_1
fireball_2
game_over
goat
hey
horse
hurt
hyah_1
hyah_2
hyah_3
hyah_4
jump_1
jump_2
jump_3
jump_4
listen
negative_1
negative_2
pickup_1
pickup_2
pickup_3
pickup_4
plink_1
plink_2
poops
pop_1
pop_2
positive
punch
rocket_1
rocket_2
seal
shatter
sheep
splash
success
swing
throw
toot
train_whistle
tree_shake
victory_1
victory_2
victory_3
yak
zam
You’ve also got 6 pieces of music:
music_1
music_2
music_3
music_4
music_5
music_6
You can play a sound anywhere in your code with the soundEffect function, eg, soundEffect(“sheep”). You can set the music at the top of your code with setMusic(“music_6”).
Take all the time you want. Put the sounds wherever you want. GO NUTS!
Second Task: Little Smokies
A nice effect in platformers is to have little puffs of smoke when the character changes direction. I’ve added a nice function called makeSmoke in plumbing.js.
To use it, you have to supply a container (more on that later, but for now, just the stage), x and y coordinates, and x and y scale (so you can control the size of it).
We’re going to give Mia some smoke puffs. Change updateGame like so:
function updateGame(diff) {
// Don't try to update the game until we've created Mia,
// or the game will crash.
if (mia == null) return;
dropBricks();
// If the right key got pushed, move Mia to the right
if (key_down["ArrowRight"]) {
mia.last_x = mia.x;
mia.x = mia.x + 7;
if (mia.state != "kaput") mia.scale.set(1,1);
if (mia.state == "running" && mia.last_arrow == "left") makeSmoke(stage, mia.x - 20, mia.y - 40, 1.4, 1.4);
mia.last_arrow = "right";
}
// If the left key got pushed, move Mia to the left
if (key_down["ArrowLeft"]) {
mia.last_x = mia.x;
mia.x = mia.x - 7;
if (mia.state != "kaput") mia.scale.set(-1,1);
if (mia.state == "running" && mia.last_arrow == "right") makeSmoke(stage, mia.x + 20, mia.y - 40, 1.4, 1.4);
mia.last_arrow = "left";
}
// If the space bar got pushed, make Mia jump
if (key_down[" "] && mia.state == "running") {
mia.state = "jumping";
mia.y_velocity = -20;
soundEffect("jump_3");
if (mia.last_arrow == "left") makeSmoke(stage, mia.x + 20, mia.y - 40, 1.4, 1.4);
if (mia.last_arrow == "right") makeSmoke(stage, mia.x - 20, mia.y - 40, 1.4, 1.4);
}
...
...
...
When Mia changes direction or jumps, we make a little puff of smoke.
Optional Third Task: Color the Sky!
This one’s totally optional.
If you look in the Art folder from today’s new files, you’ll see that there are some new pictures.
Find the place in your code where you make the blue sky:
for (num = -1; num < 8; num += 1) {
let blue_sky = makeSprite("Art/blue_sky.png");
blue_sky.position.set(game_width * num, 0);
stage.addChild(blue_sky);
}
Instead, try orange_sky, pink_sky, purple_sky, or green_sky.
Fourth Task: Give Mia different poses.
Mia is always running, running running running.
We should make her stand still when the player isn’t pressing any buttons, and we should give her jumping and falling poses.
In order to do that, we’re going to learn about containers. A container is something that can contain multiple sprites, which you can still move around and scale and rotate.
Maybe in a future game, you have an end boss with two robot octopus arms. You draw the arms separately from the head, so you have three sprites to manage. Instead of moving all three sprites around the level, you can make a container, put the three sprites inside it, and then just move that around instead. It’s easier.
We’re going to change Mia from a sprite to a container, and then we’re going to put Mia’s four sprites (running, rising, falling, and idle) into that container.
All of the code for moving Mia around will stay the same. We’ll just have to add some code to change which animation is visible at any given time.
First, let’s make the container and add all the animations. Change initializeGame as follows:
function initializeGame() {
for (num = -1; num < 8; num += 1) {
let blue_sky = makeSprite("Art/blue_sky.png");
blue_sky.position.set(game_width * num, 0);
stage.addChild(blue_sky);
}
for (num = -8; num < 70; num += 1) {
if (num % 16 < 14) {
let brick = makeSprite("Art/brick.png");
brick.anchor.set(0.5,1);
brick.position.set(120 * num, game_height);
brick.tint = pick(colors);
stage.addChild(brick);
bricks.push(brick);
brick.column = num;
brick.y_velocity = 0;
stacks[brick.column] = 1;
}
else {
stacks[num] = -100;
}
}
mia = makeAnimatedSprite("Art/mia_animations.json", "run");
mia.anchor.set(0.5, 0.9);
mia.position.set(200, game_height - 40);
stage.addChild(mia);
mia.animationSpeed = 0.3;
mia.play();
mia.state = "running";
mia.y_velocity = 0;
mia = makeContainer();
mia.run = makeAnimatedSprite("Art/mia_animations.json", "run");
mia.run.anchor.set(0.5, 0.9);
mia.run.animationSpeed = 0.3;
mia.run.play();
mia.addChild(mia.run);
mia.fall = makeAnimatedSprite("Art/mia_animations.json", "fall");
mia.fall.anchor.set(0.5, 0.9);
mia.fall.animationSpeed = 0.3;
mia.fall.play();
mia.addChild(mia.fall);
mia.rise = makeAnimatedSprite("Art/mia_animations.json", "rise");
mia.rise.anchor.set(0.5, 0.9);
mia.addChild(mia.rise);
mia.idle = makeAnimatedSprite("Art/mia_animations.json", "idle");
mia.idle.anchor.set(0.5, 0.9);
mia.addChild(mia.idle);
// Change mia's state
mia.setState = function(state) {
mia.state = state;
// Set all sprites invisible
mia.run.visible = false;
mia.fall.visible = false;
mia.rise.visible = false;
mia.idle.visible = false;
// Then set the right state back to visible
if (mia.state == "running") mia.run.visible = true;
if (mia.state == "falling") mia.fall.visible = true;
if (mia.state == "jumping") mia.rise.visible = true;
if (mia.state == "idle" || mia.state == "kaput") mia.idle.visible = true;
}
mia.position.set(200, game_height - 40);
mia.setState("idle");
mia.y_velocity = 0;
stage.addChild(mia);
}
Here we’re making Mia into a container, then making four animations and adding each of them to the container by using the function addChild.
After that, we’re giving Mia a new function called setState which both changes Mia’s state and makes sure the right sprite is visible.
Then we’re calling setState to set Mia to the idle state.
After that, we’re doing the same stuff we did before: give Mia a position and a y velocity, and adding Mia to the stage.
But when we do that, we’re going to see that Mia stays idle no matter how we move her.
That’s because all of the rest of our code is still using mia.state = something, instead of calling the new function mia.setState(something).
Let’s change testBricks:
function testBricks() {
// Don't test anything if Mia is already kaput
if (mia.state == "kaput") return;
mia.column = Math.floor((mia.x + 60) / 120);
// Don't test bricks if Mia is too far to the left or right.
if (mia.column < -8 || mia.column >= 70) return;
// Figure out the floor for Mia's current column.
let floor_height = game_height - 36 * stacks[mia.column] - 4;
// First, check if Mia has run into thin air,
// like Wile E Coyote, and make her fall.
if (mia.y < floor_height) {
mia.state = "falling"
}
if (mia.y < floor_height && mia.y_velocity >= 0) {
mia.setState("falling")
}
// Check if Mia has fallen on top of the current stack.
if (mia.state == "falling" && mia.y >= floor_height && mia.y_velocity > 0) {
mia.y = floor_height;
mia.y_velocity = 0;
mia.state = "running";
mia.setState("running");
soundEffect("hyah_3")
}
// Then, loop through the whole brick list
for (i = 0; i < bricks.length; i += 1) {
let brick = bricks[i];
// If a brick is not falling, make sure Mia can't run through it.
if (brick.y_velocity == 0) {
// Calculate the floor height of this particular brick
this_brick_floor_height = game_height - 36 * stacks[brick.column] - 4;
// If Mia is below this brick's floor height, and she's too close
// to the brick, push her back out.
if (Math.abs(mia.x - brick.x) < 90 && mia.y > this_brick_floor_height) {
if (mia.x < brick.x) mia.x = mia.x - 7;
if (mia.x > brick.x) mia.x = mia.x + 7;
}
}
else if (brick.y_velocity > 0 && mia.state != "kaput") {
// If Mia is too close to a falling brick, she goes kaput.
if (Math.abs(mia.x - brick.x) < 80
&& brick.y < mia.y - 10
&& brick.y > mia.y - 175) {
mia.state = "kaput";
mia.setState("kaput");
mia.scale.y = -1;
mia.y_velocity = -5;
mia.y = mia.y - 175;
}
}
}
}
Finally, let’s change updateGame:
We have to change the logic a little bit to keep up with Mia’s new states, but this is basically mostly just changing to use the setState function.
Et voila,
And there we have it! The game is juicy.
Tomorrow we’ll add some baddies.
Detail: the sound effects for this tutorial come from Splice.com. Credit for the music files is as follows:
music_1: Abstraction – The Skies of 20X9
music_2: Abstraction – Underwater Cave
music_3: Celestialghost8 – Breaking the Barrier
music_4: Disyman – Adventure
music_5: Juhani Junkala – Stage 2
music_6: Juhani Junkala – Stage Select
Optional Bonus Task, mostly for Dad: Smooth Motion!
If you play a lot of platformers, you’ve probably noticed that Mia’s movements are a bit basic and clunky.
If you want, here’s a quick change which will make Mia’s motion smoother.
Feel free to skip this section entirely.
Mia has y velocity. To make smooth motion, we’ll add x velocity as well.
When the left or right keys are pressed, the x velocity will be increased, up to a maximum.
At all times, the x velocity will also be decayed, and if it falls close to zero, we’ll set Mia back to idle.
Add the following code, try it out, and then tweak the values until you get something you like.
...
...
...
mia.position.set(200, game_height - 40);
mia.setState("idle");
mia.y_velocity = 0;
mia.x_velocity = 0;
mia.max_x_velocity = 8;
stage.addChild(mia);
...
...
...
function updateGame(diff) {
// Don't try to update the game until we've created Mia,
// or the game will crash.
if (mia == null) return;
dropBricks();
if (mia.state == "running") {
mia.setState("idle");
}
// If the right key got pushed, move Mia to the right
if (key_down["ArrowRight"]) {
if (mia.state == "idle") mia.setState("running");
mia.last_x = mia.x;
mia.x = mia.x + 7;
if (mia.state != "kaput") mia.scale.set(1,1);
if (mia.state == "running" && mia.last_arrow == "left") makeSmoke(stage, mia.x - 20, mia.y - 40, 1.4, 1.4);
mia.last_arrow = "right";
}
// If the left key got pushed, move Mia to the left
if (key_down["ArrowLeft"]) {
if (mia.state == "idle") mia.setState("running");
mia.last_x = mia.x;
mia.x = mia.x - 7;
if (mia.state != "kaput") mia.scale.set(-1,1);
if (mia.state == "running" && mia.last_arrow == "right") makeSmoke(stage, mia.x + 20, mia.y - 40, 1.4, 1.4);
mia.last_arrow = "left";
}
// If the right key got pushed, move Mia to the right
if (key_down["ArrowRight"]) {
if (mia.state == "idle") mia.setState("running");
if (mia.x_velocity < 0) {
mia.x_velocity = 0;
makeSmoke(stage, mia.x - 20, mia.y - 40, 1.4, 1.4);
}
mia.x_velocity += 1;
if (mia.x_velocity > mia.max_x_velocity) mia.x_velocity = mia.max_x_velocity;
if (mia.state != "kaput") mia.scale.set(1,1);
}
// If the left key got pushed, move Mia to the left
if (key_down["ArrowLeft"]) {
if (mia.state == "idle") mia.setState("running");
if (mia.x_velocity > 0) {
mia.x_velocity = 0;
makeSmoke(stage, mia.x + 20, mia.y - 40, 1.4, 1.4);
}
mia.x_velocity -= 1;
if (mia.x_velocity < -1 * mia.max_x_velocity) mia.x_velocity = -1 * mia.max_x_velocity;
if (mia.state != "kaput") mia.scale.set(-1,1);
}
mia.last_x = mia.x;
mia.x += mia.x_velocity;
mia.x_velocity *= 0.93;
if (mia.state == "running" && Math.abs(mia.x_velocity) < 0.5) mia.setState("idle");
// If the space bar got pushed, make Mia jump
if (key_down[" "]) {
if (mia.state == "running" || mia.state == "idle") {
mia.setState("jumping");
mia.y_velocity = -20;
soundEffect("jump_3");
if (mia.last_arrow == "left") makeSmoke(stage, mia.x - 20, mia.y - 40, 1.4, 1.4);
if (mia.last_arrow == "right") makeSmoke(stage, mia.x + 20, mia.y - 40, 1.4, 1.4);
makeSmoke(stage, mia.x - 3 * mia.x_velocity, mia.y - 40, 1.4, 1.4);
}
}
// If Mia is jumping, move her upwards, use gravity to pull her downwards,
// and if she reaches the ground, stop the jump.
if (mia.state == "jumping" || mia.state == "falling" || mia.state == "kaput") {
mia.y = mia.y + mia.y_velocity;
mia.y_velocity = mia.y_velocity + 0.8;
if (mia.y_velocity > 0 && mia.state == "jumping") {
// switch to falling
mia.setState("falling");
}
}
testBricks();
if (mia.y > 1200) {
stage.removeChildren();
bricks = [];
stacks = {};
initializeGame();
}
followMia();
}
Motion works a little bit differently now.
When an arrow key is pushed down, first we check if we’re running in the other direction. If so, we drop the velocity back to zero and add a little puff of smoke (this is basically a quick turn).
Then, we add 1 to the velocity. If it’s larger than the max, we set it to the max. (For the left key, if it’s less than -1 * max, we set it to -1 * max).
The jump smoke now depends on x velocity; if you’re going 8 pixels to the right per frame, the jump smoke is placed 24 pixels to the left.
Play around with these numbers until you get something you really like!