A Wild Game Appears! Part 1


 

Let’s Make H-oh wait, no, today we’re not making Honey. Today we’re using Honey to make a game for my daughter in 35 minutes of programming.

 

Why is it part 1 if we’re making a whole game, eh? Ehhhhh?

 

It’s because we’re making the whole game in part 1, and figuring out how to package it as a downloadable app in part 2. A downloadable app? You know, a program. A whole program. The kind of program you can ship to mom and it will just work on her mac.

 

So let’s just get right to part 1.

 

 

My six month old daughter loves to play with computers, but we don’t want her to mess with all the finicky settings and break all the nice things, so we’re going to make her a game that’s very hard to exit; you have to quickly push escape three times to quit it.

 

We’ll call the game ABearCs. It’s a very simple game which mimicks an ABC book. Every time the lil’ baby gamer presses a key, the game shows the letter, a word starting with that letter, and a picture corresponding to that word. It also reads out loud, eg, “B, Bear” or “U, Umbrella”, and there is a bit of sliding animation between the current letter and the last letter to make it look smooth.

 

 

When I wrote this for my daughter today, it took me an hour or so to gather the images and sounds. But it took me just 35 minutes to write the game itself. Dead simple. But a good little demonstration of what Honey can do after 10 commits.

 

Let’s get to it.

 

 

Downloads!

 

 

We’re going to keep this project separate from Honey, treating ourselves as end users of version 0_09. So, make a new project folder called ABearCs.

 

We’ll need a copy of Honey version 0_09 in the folder, and we’ll need to install SDL2, SDL2_image, and SDL2_mixer (2.0.0, not 2.0.2!) in /Library/Frameworks.

 

And now we need to go get some assets.

 

For images, go to openclipart.org, which offers royalty free art you can use in any project. Here are the files we’re using:

 

If you’re just interested in programming, you can use my copies of the files in https://github.com/bugsbycarlin/ABearCs/tree/master/Art.

 

If you’re interested in learning the whole process, follow on. You’ll need an art program which allows you to work with transparent png files. I used Adobe Illustrator, which costs about $30 a month, but you can also use Gimp, which is free and gets this job done just as well.

 

Download the medium sized png of each of those assets, and open each one in your image editor. Center it in a 400×400 square, and save that as a png, apple.png, bear.png, cat.png, etc. I composed the three yoga pictures into a single 400×400 picture, and I turned the transparent zebra green, but you can basically do this work to your own taste, or even find other images!

 

For text, I went to 1001freefonts.com, a supremely useful resource where you can find, yes, a thousand and one royalty free fonts to work with. I wouldn’t blame you if you stopped this tutorial right now and just played around on that site for a few hours.

 

Anyway, I chose a very soft and child friendly font called A Song For Jennifer. Choose a font that works for you, and install it on your system. Then go back to your image editor of choice, and make new 400×400 squares, each with a big letter and a smaller text:

 

 

 

Save each of these as a transparent png, a.png, b.png, c.png, d.png, etc.

 

The reference copies are also in https://github.com/bugsbycarlin/ABearCs/tree/master/Art if you want to use those.

 

Put all of this art in ABearCs/Art.

 

For sound, we’ll use the free and wonderful Audacity. Audacity is a multi-purpose audio editing program that, among other things, makes it pretty easy to record and lightly edit your own voice. FriendsOnMountains uses it for our podcasts. Today, you’ll be using it to record alphabet words (or, you can get my files from https://github.com/bugsbycarlin/ABearCs/tree/master/Sound).

 

Open audacity.

 

 

When you’re ready, hit the record button. Speak this phrase to your computer as clearly as you can: “A… Apple”.

 

 

What you see in front of you is a picture of your voice recording. The first bump is you saying “A”. The second bump is you saying “Apple”.

 

 

Drag to select the empty space before the first bump. This is a delay we want to cut.

 

 

Choose “cut” from the edit menu to cut this audio.

 

 

Now your audio file is smaller. Drag to select the long empty space after the second bump. We want to cut this too.

 

 

Choose “cut” from the edit menu again.

 

 

Now that our audio file is cut to the right size, we want to export it. Choose “export audio” from the file menu.

 

 

Save the file as “a.wav”, making sure the file type is WAV.

 

 

And Bob’s your uncle. Or at least, he’s my uncle.

 

Do this 25 more times, once for each letter. Save each file, b.wav, c.wav, etc, in ABearCs/Sound.

 

When you’re done gathering art and recording sound (or you’ve pulled my files from github), you should have three things in the ABearCs directory: an Art folder, a Sound folder, and a Honey folder.

 

 

Game!

 

Programming the game is the easy part.

 

Add empty SConstruct and config.txt files to ABearCs. Add a Source folder to ABearCs, then add an empty main.cpp file to Source. Your folder should now look like this:

 

 

Let’s add the following skeleton to main.cpp:

 


#include <string>

#include "window.h"
#include "logic.h"
#include "hotconfig.h"
#include "input.h"
#include "graphics.h"
#include "sound.h"
#include "effects.h"

using namespace Honey;

std::string letters[] = {
  "a",
  "b",
  "c",
  "d",
  "e",
  "f",
  "g",
  "h",
  "i",
  "j",
  "k",
  "l",
  "m",
  "n",
  "o",
  "p",
  "q",
  "r",
  "s",
  "t",
  "u",
  "v",
  "w",
  "x",
  "y",
  "z"
};

std::string pictures[] = {
  "apple",
  "bear",
  "cat",
  "dog",
  "eggs",
  "flowers",
  "grapes",
  "hamburger",
  "icecream",
  "juicebox",
  "key",
  "lemon",
  "monkey",
  "nixon",
  "orange",
  "pizza",
  "queen",
  "rainbow",
  "strawberry",
  "trees",
  "umbrella",
  "viola",
  "watermelon",
  "xylophone",
  "yoga",
  "zebra"
};

int main(int argc, char* args[]) {

}

 

Ahhhh. Source code. We’re just including everything from Honey, and using it as a namespace (so we don’t have to refer to it by name everywhere). We have fixed string arrays of letters and words to use later.

 

Now set up SConstruct. It’s pretty much the same as what we’ve been doing for our test game, only we’ve changed the LIBPATH and CPPPATH since we have a copy of Honey 0_09 local to this folder:

 


import os

honey_version = "0_09"

env=Environment()
env.Append(CXXFLAGS = "-std=c++11")

libraries = ["libHoney.a"]

env.AppendUnique(FRAMEWORKS = Split("OpenGL SDL2 SDL2_image SDL2_mixer"))

ABearCs = env.Program(target = "ABearCs",
  source = Glob("./Source/*.cpp"),
  LIBS = libraries,
  LIBPATH="./Honey/%s/Library" % honey_version,
  CPPPATH="./Honey/%s/Includes" % honey_version)

Default()

def cleanObjects(target, source, env):
  os.system("rm ./Source/*.o")

cleanup = Command("cleanObjects", [], cleanObjects)
Depends(cleanup, BUILD_TARGETS)
BUILD_TARGETS.append("cleanObjects")

 

As a quick refresher, our build system is scons, it runs SConstruct build files, and they’re written in Python. We’re compiling with C++11, pointing to our libHoney.a library file, telling the compiler to use a bunch of frameworks, making a build target called ABearCs, and telling it to build everything in the Source directory. We’re pointing to the Honey library, and when we’re done building, we’re running a cleanup function that deletes .o files. You can learn more about this by visiting the “Let’s Make Honey” page on the right and reading some of the early tutorials.

 

Build the game (Go to ABearCs and run scons ABearCs), and then run ./ABearCs. The build should work and the program should do nothing.

 

Let’s set up a config file like we’ve been doing for the test game. Add this stuff to config.txt:

 


<section name="layout">
  <param type="int" name="screen_width">960</param>
  <param type="int" name="screen_height">600</param>
  <param type="bool" name="full_screen">true</param>
  <param type="string" name="screen_color">#CCCCCC</param>
  <param type="int" name="picture_x">50</param>
  <param type="int" name="picture_y">100</param>
  <param type="int" name="text_x">510</param>
  <param type="int" name="text_y">100</param>
</section>

<section name="animation">
  <param type="float" name="animation_duration">0.5</param>
  <param type="int" name="tween_type">1</param>
  <param type="int" name="shake_width">6</param>
  <param type="float" name="shake_duration">0.25</param>
</section>

<section name="input">
  <param type="float" name="key_swap_duration">2.0</param>
</section>

<section name="sound">
  <param type="float" name="sound_volume">1.0</param>
  <param type="float" name="sound_lock_duration">3.0</param>
</section>

 

No big deal, right? We’re making some layout choices, some animation choices, and some various locks.

 

Back to main.cpp. Add this stuff to int main:

 


int main(int argc, char* args[]) {

  // Load configuration, after setting the right path with mod_path.
  if(hot_config->checkAndUpdate() != hot_config->SUCCESS) {
    exit(1);
  }

  int screen_width = hot_config->getInt("layout", "screen_width");
  int screen_height = hot_config->getInt("layout", "screen_height");
  bool full_screen = hot_config->getBool("layout", "full_screen");

  Window* window = new Window("ABearCs", screen_width, screen_height, full_screen);

  graphics->initialize(window);
  sound->initialize();

 

We’re just checking our load function (which, remember, looks for config.txt by default), then reading the window parameters from config, then making a window called ABearCs, then initializing graphics and sound. These are all nice Honey functions we’ve written over the last 10 tutorials.

 

Now we’ll load some assets and get our loop a-going. Add this below the graphics and sound initializations:

 

  ...
  graphics->initialize(window);
  sound->initialize();

  int current_letter = -1;
  int last_letter = -1;

  for (int i = 0; i < 26; i++) {
    graphics->addImage(letters[i], "Art/" + letters[i] + ".png");
    graphics->addImage(pictures[i], "Art/" + pictures[i] + ".png");
    sound->addSound(letters[i], "Sound/" + letters[i] + ".wav");
  }

  bool quit = false;
  while (!quit) {
    // Check and load configuration every 2 seconds
    hot_config->checkAndUpdate();

    // Set a bunch of variables from configuration
    std::string screen_color = hot_config->getString("layout", "screen_color");
    int picture_x = hot_config->getInt("layout", "picture_x");
    int picture_y = hot_config->getInt("layout", "picture_y");
    int text_x = hot_config->getInt("layout", "text_x");
    int text_y = hot_config->getInt("layout", "text_y");
    int tween_type = hot_config->getInt("animation", "tween_type");
    float animation_duration = hot_config->getFloat("animation", "animation_duration");
    float shake_duration = hot_config->getFloat("animation", "shake_duration");
    int shake_width = hot_config->getInt("animation", "shake_width");
    float key_swap_duration = hot_config->getFloat("input", "key_swap_duration");
    float sound_volume = hot_config->getFloat("sound", "sound_volume");
    float sound_lock_duration = hot_config->getFloat("sound", "sound_lock_duration");
    

    sound->setSoundVolume(sound_volume);

    input->processInput();

    if (input->threeQuickKey("escape")) {
      quit = true;
    }

    // Clear the screen to a soft white color
    graphics->clearScreen(screen_color);

    // Switch to 2D drawing mode
    graphics->draw2D();

    // Put everything we've drawn on screen
    graphics->updateDisplay();
  }

  for (int i = 0; i < 26; i++) {
    graphics->destroyImage(letters[i]);
    graphics->destroyImage(pictures[i]);
    sound->destroySound(letters[i]);
  }

  window->destroy();
}

 

In lines 5-12, we’re loading 26 pictures of stuff (cat, zebra, etc), 26 letters (a, b, etc), and 26 sound files. We do this in a for loop using our fixed letter and picture arrays. We’ll destroy these at the bottom in lines 52-56 using a similar for loop.

 

Line 15 begins our game loop, and in lines 16-31 we’re setting the values of a bunch of variables we’ll be using. In line 34 we set the volume, and in line 36 we’re processing the input, just like in our test game.

 

Lines 38-40 are the three quick escape to quit logic. Here we’re not accepting the quit signal as a means to quit; the player will pretty much have to press escape three times.

 

After that we clear the screen and tell Honey we’re going to be drawing in 2d, but then we don’t do anything but update the display.

 

If you build the game (Go to ABearCs and run scons ABearCs), and then run ./ABearCs, it should show a black screen until you press escape three times.

 

So that’s the skeleton. The real game is in a little bit of input handling and logic, and a little bit of creative drawing.

 

Our game is going to work as follows: we have defined a current_letter and a last_letter. They’re both set to -1 to start, indicating that there is no letter being shown. When the player presses a letter key, if it’s not the one we already have, we will set the old_letter to the current_letter, and the current_letter to the key number that was pressed (0 for a, 1 for b, etc). Then we’ll set a lock for key presses (a long one) and a lock for animation (a short one), and use a tween to animate the current letter flying away to the top, and the new letter rolling in from the bottom.

 

When the animation tween has finished, we’ll play a sound, and set another lock to make sure we don’t double play the sound. We’ll also set a short effects shake.

 

The code looks like so. Add this to main.cpp’s game loop below the input->processInput step:

&nbsp


    sound->setSoundVolume(sound_volume);

    input->processInput();

    // do stuff with input
    for (int i = 0; i < 26; i++) {
      if (input->keyDown(letters[i]) && !logic->isTimeLocked("key_swap") && i != current_letter) {
        logic->remove("sound_lock");
        logic->makeTimeLock("key_swap", key_swap_duration);
        logic->makeTimeLock("animation", animation_duration);
        effects->makeTween("slide_last_letter", screen_height, 0, animation_duration);
        last_letter = current_letter;
        current_letter = i;
      }
    }

    if (logic->isTimeLocked("key_swap") && !logic->isTimeLocked("animation") && !logic->isTimeLocked("sound_lock")) {
      logic->makeTimeLock("sound_lock", sound_lock_duration);
      sound->playSound(letters[current_letter], 1);
      effects->makeShake("sound_shake", shake_width, shake_duration);
    }

    if (input->threeQuickKey("escape")) {
      quit = true;
    }

    // Clear the screen to a soft white color
    graphics->clearScreen(screen_color);

 

In lines 5-15, we loop over 26 values corresponding to our 26 letters. We check if the letter in our letter array (0 for a, 1 for b, etc) was pressed, and if so, we clear out the old sound lock (so we’re ready to play a new sound), and make locks for animation and keys. We make a tween, and we swap the current letter to the new letter and the old letter to the current letter. The tween is built to slide from 960 (the screen height) to 0; we’re going to move the current letter up by 960, but then we’re going to put it back down by the tween value, so it will start at 0 and gradually move up off screen.

 

In lines 17-22, we check if the conditions are right to play a sound, then play that sound (and make a shake tween, and lock everything for a while).

 

Now the drawing. We’ll draw the current picture if current_letter isn’t -1, and we’ll draw the old picture if it isn’t -1 and we’re animating. Add this code to the main loop after the 2d draw setup:

 


// Clear the screen to a soft white color
    graphics->clearScreen(screen_color);

    // Switch to 2D drawing mode
    graphics->draw2D();

    // draw stuff
    float x, y;

    // Current picture
    if (current_letter != -1) {
      x = picture_x;
      y = picture_y;
      if (logic->isTimeLocked("animation")) {
        y += effects->tween("slide_last_letter", tween_type);
      }
      graphics->drawImage(pictures[current_letter], x, y);
      x = text_x;
      y = text_y;
      if (logic->isTimeLocked("animation")) {
        y += effects->tween("slide_last_letter", tween_type);
      }
      if (logic->isTimeLocked("key_swap")) {
        x += effects->shake("sound_shake");
        y += effects->shake("sound_shake");
      }
      graphics->drawImage(letters[current_letter], x, y);
    }

    // Last picture, if animating
    if (logic->isTimeLocked("animation") && last_letter != -1) {
      x = picture_x;
      y = picture_y - screen_height + effects->tween("slide_last_letter", tween_type);
      graphics->drawImage(pictures[last_letter], x, y);
      x = text_x;
      y = text_y - screen_height + effects->tween("slide_last_letter", tween_type);
      graphics->drawImage(letters[last_letter], x, y);
    }

    // Put everything we've drawn on screen
    graphics->updateDisplay();
  }

 

On line 8 we make position variables that we will use repeatedly below.

 

Starting on line 12, we set the position to the current picture location (defined in layout in config.txt). Then, we add a tween if we’re animating. Then we draw the picture. Then we set the position to the current text location (also defined in config). Then, we again add a tween if we’re animating, and a shake if we’re doing the sound shake. Then we draw the text.

 

If we’re animating, on line 30 we repeat this process for the old picture and text, but adjusted for screen height (basically, one screen above the new current picture and text).

 

That’s it! That’s literally the whole game. Here’s the full cpp file for your reference:

 


#include <string>

#include "window.h"
#include "logic.h"
#include "hotconfig.h"
#include "input.h"
#include "graphics.h"
#include "sound.h"
#include "effects.h"

using namespace Honey;

std::string letters[] = {
  "a",
  "b",
  "c",
  "d",
  "e",
  "f",
  "g",
  "h",
  "i",
  "j",
  "k",
  "l",
  "m",
  "n",
  "o",
  "p",
  "q",
  "r",
  "s",
  "t",
  "u",
  "v",
  "w",
  "x",
  "y",
  "z"
};

std::string pictures[] = {
  "apple",
  "bear",
  "cat",
  "dog",
  "eggs",
  "flowers",
  "grapes",
  "hamburger",
  "icecream",
  "juicebox",
  "key",
  "lemon",
  "monkey",
  "nixon",
  "orange",
  "pizza",
  "queen",
  "rainbow",
  "strawberry",
  "trees",
  "umbrella",
  "viola",
  "watermelon",
  "xylophone",
  "yoga",
  "zebra"
};

int main(int argc, char* args[]) {

  // Load configuration, after setting the right path with mod_path.
  if(hot_config->checkAndUpdate() != hot_config->SUCCESS) {
    exit(1);
  }

  int screen_width = hot_config->getInt("layout", "screen_width");
  int screen_height = hot_config->getInt("layout", "screen_height");
  bool full_screen = hot_config->getBool("layout", "full_screen");

  Window* window = new Window("ABearCs", screen_width, screen_height, full_screen);

  graphics->initialize(window);
  sound->initialize();

  int current_letter = -1;
  int last_letter = -1;

  for (int i = 0; i < 26; i++) {
    graphics->addImage(letters[i], "Art/" + letters[i] + ".png");
    graphics->addImage(pictures[i], "Art/" + pictures[i] + ".png");
    sound->addSound(letters[i], "Sound/" + letters[i] + ".wav");
  }

  bool quit = false;
  while (!quit) {
    // Check and load configuration every 2 seconds
    hot_config->checkAndUpdate();

    // Set a bunch of variables from configuration
    std::string screen_color = hot_config->getString("layout", "screen_color");
    int picture_x = hot_config->getInt("layout", "picture_x");
    int picture_y = hot_config->getInt("layout", "picture_y");
    int text_x = hot_config->getInt("layout", "text_x");
    int text_y = hot_config->getInt("layout", "text_y");
    int tween_type = hot_config->getInt("animation", "tween_type");
    float animation_duration = hot_config->getFloat("animation", "animation_duration");
    float shake_duration = hot_config->getFloat("animation", "shake_duration");
    int shake_width = hot_config->getInt("animation", "shake_width");
    float key_swap_duration = hot_config->getFloat("input", "key_swap_duration");
    float sound_volume = hot_config->getFloat("sound", "sound_volume");
    float sound_lock_duration = hot_config->getFloat("sound", "sound_lock_duration");
    

    sound->setSoundVolume(sound_volume);

    input->processInput();

    // do stuff with input
    for (int i = 0; i < 26; i++) {
      if (input->keyDown(letters[i]) && !logic->isTimeLocked("key_swap") && i != current_letter) {
        logic->remove("sound_lock");
        logic->makeTimeLock("key_swap", key_swap_duration);
        logic->makeTimeLock("animation", animation_duration);
        effects->makeTween("slide_last_letter", screen_height, 0, animation_duration);
        last_letter = current_letter;
        current_letter = i;
      }
    }

    if (logic->isTimeLocked("key_swap") && !logic->isTimeLocked("animation") && !logic->isTimeLocked("sound_lock")) {
      logic->makeTimeLock("sound_lock", sound_lock_duration);
      sound->playSound(letters[current_letter], 1);
      effects->makeShake("sound_shake", shake_width, shake_duration);
    }

    if (input->threeQuickKey("escape")) {
      quit = true;
    }

    // Clear the screen to a soft white color
    graphics->clearScreen(screen_color);

    // Switch to 2D drawing mode
    graphics->draw2D();

    // draw stuff
    float x, y;

    // Current picture
    if (current_letter != -1) {
      x = picture_x;
      y = picture_y;
      if (logic->isTimeLocked("animation")) {
        y += effects->tween("slide_last_letter", tween_type);
      }
      graphics->drawImage(pictures[current_letter], x, y);
      x = text_x;
      y = text_y;
      if (logic->isTimeLocked("animation")) {
        y += effects->tween("slide_last_letter", tween_type);
      }
      if (logic->isTimeLocked("key_swap")) {
        x += effects->shake("sound_shake");
        y += effects->shake("sound_shake");
      }
      graphics->drawImage(letters[current_letter], x, y);
    }

    // Last picture, if animating
    if (logic->isTimeLocked("animation") && last_letter != -1) {
      x = picture_x;
      y = picture_y - screen_height + effects->tween("slide_last_letter", tween_type);
      graphics->drawImage(pictures[last_letter], x, y);
      x = text_x;
      y = text_y - screen_height + effects->tween("slide_last_letter", tween_type);
      graphics->drawImage(letters[last_letter], x, y);
    }

    // Put everything we've drawn on screen
    graphics->updateDisplay();
  }

  for (int i = 0; i < 26; i++) {
    graphics->destroyImage(letters[i]);
    graphics->destroyImage(pictures[i]);
    sound->destroySound(letters[i]);
  }

  window->destroy();
}

 

Build (Go to ABearCs and run scons ABearCs), and then run ./ABearCs, and viola, you’ve got a cute little game for babies. Drawing still takes a lot of fairly confusing arithmetic, but everything else is pretty straight forward. “sound->playSound”. “if input->keyDown(letters[i])”. “logic->makeTimeLock(“animation”, animation_duration)”.

 

Here’s the video of my result:

 

 

Honey is working as intended. We’ve made a smooth game in about an hour or two of asset work and just over a half hour of programming.

 

Next time we’ll package it all up into an OS X application we can share. Figuring that out took me hours and hours (but hopefully you can benefit from my mistakes).

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *