Progress Video – Some Kind of Poetry Game?


Here’s a quick progress video for an idea I’m chasing down.

It’s the core mechanic for either a school RPG or a dreamy adventure game. In either case, the idea is to have a poetry jam where the player has to know a poem well enough to pick the next lines at speed.

For the RPG, I think what I want here is to have the player and the opponent exchange verses; if the player fails a verse, the opponent automatically picks it up, and vice versa. The winner is whoever does it better.

I’ll post more later if this idea bears fruit, and if it doesn’t, I won’t!

Cliffs Notes for a 2D Practice Game!


Woohoo, I just finished a 2D practice game! I took a bunch of notes, and I’m presenting them here for anyone who wants to make a 2D game of their own.

I had three goals here:

1. Practice character animation.
2. Take an old concept and make it juicy.
3. Communicate the feeling of frustration that I get from horses.

Soooooo here are my notes:


Javascript Canvas

2D practice games should be easy. No framework, no heavyweight engine. Everything you need, images, sound, mouse and keyboard, is all available in Javascript with a canvas element. I use a basic index file with a canvas tag and a source file something like this:

If you’re new to games, the basic idea is either:

  1. You have a loop where you check the user inputs, update your game state, and render.
  2. You have a loop where you update your game state and render, and you use event listeners to keep track of user input asynchronously.

Javascript prefers the latter and I’m very okay with that. We listen for keyboard input, and there’s a main loop to update, and that update calls render. Actually, the dude is faster, so there are two loops. This is in general a very bad idea because it can introduce aync game state bugs, but this is a small game and I decided it was a cheap way to speed up the dude. See below about coding standards.


Drawing Practice

You don’t have to be good, but you have to be decent. I started this game after forty days of drawing practice. I just finished it around day seventy. I’m not good, but I’m decent, and getting better!


Reference Material

This game was inspired by King of the Hill, so I drew pretty heavily on it for references.

Don’t be afraid to keep and use references. Just don’t push your reference folder to github!

For the horses I just looked at a bunch of Google Images results. For the fence posts I looked at Legend of Zelda: A Link to the Past.

Shareicon is your friend for a variety of small icons, in this case the Frown, the Home, and the Heart.


Posing and Perspective

A Link to the Past, Secret of Mana and a bunch of other 2D adventure games use a perspective called 3/4 perspective. This has always been hard for me, so to get the right foreshortening proportions for the man and the horse, I used models from free3d.com. Since my Maya subscription isn’t active, and I’m not assuming you even have Maya, it’s worth mentioning that I viewed these models with the free FBX Review tool. I took screenshots and used them directly for sizes in Photoshop.

Bro.
Bro.

Tools

To make this game, I made use of Photoshop for drawing and finicky image editing, Nima for character animation, Xee 2.2 for photo previews, Audacity for sound recording, and Sublime for programming. I kept track of my work in Evernote and Workflowy. More on some of these below.


Photoshop Tips

First, you really kind of do need Photoshop. Gimp isn’t as easy to use, and there aren’t nearly as many good recipes online. I’ll look the other way if you pirate it.

Second, Photoshop is recipe based. You don’t need to learn all of Photoshop. You just need to ask the internet how to do one thing at a time.

Third, when drawing, use at least two layers, an Ink and a Fill. You fill in the color underneath the outline, and because it’s underneath, it doesn’t have to match exactly, and your coloring won’t mess up your original lines.

Fourth, if you want to draw characters for animation, it’s best to think ahead and decompose your drawing into layers for each body part. You can save a second copy of the file with this layering.

Fourth, batch actions are absurdly useful. I made batch actions to resize collections of images to various different sizes (10 percent, 15 percent, 19 percent), as well as batch actions to recolor collections of images (this is how I got the different color horses).


Character Animation

Character animation is all done using a wonderful free tool called Nima. If you’ve never used any character animation software before, it uses something called Inverse Kinematics. IK works by attaching “bones” to pieces of the animation, then allowing you to move the pieces by dragging the connected bones. You fix the shoulder in place, then drag the hand, and the forearm and upper arm adjust in (hopefully) realistic fashion. You rotate from the shoulder, and the other parts of the arm come along.

You can get pretty far with just the Quickstart Tutorial and the Animate a Robot Character followup. I admit I have a liiiittle experience playing with IK in other programs, but not much, and I was able to read these tutorials and make this game.

Here are my Nima files if you want to look at my work.


Animation Reference

In order to get passable walking and trotting, I made careful use of this Youtube tutorial of a man walking (both as learning and as a direct reference), this tutorial of a man walking and some google images of horse trotting.


Iterations

Yeah, almost everything took at least two iterations. The initial character animations took three or four iterations, but once I got comfortable, everything else was easier. I got the horse kicking and man falling on the first try!


Animation in Code

We’re going for simple, so let’s make it simple. To animate a character, I simply make a dictionary of different animations (“walking”, “falling”, “crap kicking”, etc), then load each collection of images as an array in the dictionary.

To animate, I just increment a frame counter, loop back to the first frame when it’s complete, and draw the current animation current frame.

It is very simple and it works.


Coding Standards

You’re making a 2D practice game. I’m making a 2D practice game. Don’t make stupid mistakes, but also, don’t insist on the highest standards. You aren’t building a Cathedral. You’re building a Military Camp. Part of being a good engineer is knowing the right and wrong context for particular kinds of quality.

“Jesus Christ Martha, The Horses Got Out Again” is relatively clean and understandable, but it won’t go on my source code resume, and that’s a good thing.

There’s a game source file, a map source file (with hard coded maps), a dude source file, and a horse source file. Everything is ECMAScript 2015 classes. There are globals because in a tiny program, globals are supremely useful.


Logic The Shit Out Of It

I wanted the horses to be difficult and inscrutable. So they don’t respond directly to user behavior very often. Instead, they wander around randomly on a series of waypoints.

If the horses are close to the man, they’ll pick waypoints that take them farther away from him. If they haven’t recently reversed direction, they’ll even reverse back to the last waypoint.

If they recently saw the man but he’s no longer close, they’ll choose waypoints that take them home. They need to be either afraid of the man or dreaming of home to choose to walk back into the pen.

The map contains the waypoints as a poor man’s annotated graph.

The map also contains a string diagram of the pen. Both hit detection and rendering make mechanical use of this diagram.

Javascript’s z-ordering (what to draw in front of or behind what else) isn’t very good for my purposes, so the rendering logic also checks draw order; after each row of fence pegs has been drawn, there’s a little check to see if the man or any horses are at the right height to be drawn next, and if so, they’re drawn.


Project Tracking

This is getting hard to keep straight, isn’t it? I use Evernote and Workflowy to keep track. I write ideas and talk to myself in Evernote, and write to-do lists in Workflowy.


Juicy Sound

For music, I went to Youtube and searched for “Royalty Free Western Music“. I got some great stuff, including this collection from which came all four game songs. I had to buy two of them, for a buck and six bucks respectively, and I couldn’t be happier about doing so.

For horse sound effects and a few other random things, I used fellow FriendsOnMountains guy Trace’s http://splice.com account and found some nice things. I edited a few of these in Audacity.

The horse kick was a boxing glove punch sound I got from Youtube, slowed down in Audacity.

For cussin’, I got myself in a bad mood and just recorded my best Hank Hill impression in Audacity, then cut up the files and exported mp3s.

Doing sound in Javascript games is as easy and as ugly as this:

and this:


One Last Bit of Juice

When a horse kicks the player, you should shake the whole screen.


And there you have it, boom, done!

Let’s Make Honey! Version 0.15c – Sprites!


 

Let’s Make Honey!

 

We finally get to finish version 0.15 by making sprites.

 

 

I tried to avoid this for a long time, I think because I’m still afraid of allocating and deleting in C++. Now I think I was crazy to wait so long. Sprites are so damned convenient.

 

The concept here is simple. We make a class which abstracts a 2d object drawn to the screen. It draws a particular picture (a graphics label in our system). It has all the things our 2d graphics system is currently aware of: position, color, rotation, scale, and opacity. It has methods to get and set each of these, and what’s more, it has methods to set each of these with a transition animation, by using an effect under the hood.

 

When using Honey, instead of calculating all of these things in the draw method for each picture we want to draw, we now create a Sprite, set the parameters wherever we want, and in the draw method, we just call sprite.draw().

 

I feel really dumb for not introducing this earlier, because it is a core element of all 2d graphics systems since the Logo Turtle.

 

Anyway, here is the diff for sprite.cpp.

 

In lines 13 through 20, we have a constructor which takes a label (assumed to be defined in graphics), position, color, opacity, rotation, and scale.

 

In lines 22 through 116, we have ordinary getters and setters, all simple, as well as animated set methods. The animated set methods take an animation duration, and each define a tween effect which starts at the old value, ends at the new value, and lasts for the specified duration. For now, these are all going to use the tween style that I think is best for that type.

 

Color requires three tweens, one each for R, G, and B. We could have had one tween from 0 to 1, but we would have had to store both colors, which is less good if someone calls the animation method twice quickly. More on that in a minute.

 

Opacity gets one additional convenience method to fade the sprite in and out. We use a sinewave tween from 0 to 1 to do this. Position also gets an additional method to shake the position.

 

We have two draw methods, one that just uses the default position, and (for drawing the same object multiple times) one which takes a new position value.

 

Lines 118 to 120 is the method that uses the default position. It just calls the other method with the default position.

 

Lines 122 to 165 are the proper draw method.

 

We take each thing, position, color, opacity, rotation, and scale, start with the stored value, adding the tweens if they’re busy, using my chosen blending style. I chose sigmoid for position, color and scale, linear for opacity and rotation, and sinewave for the in-and-out opacity fade.

 

Note that the tweens actually update the values as they go, so if someone creates a second animation to override the first one, it won’t reset to the oldest position, but instead start from the current location.

 

We call graphics to set the color, and finally we call graphics to draw the image with all the other values.

 

Done! That was not hard.

 

Well, sort of. In a subsequent hot fix, I gave each sprite a unique id and used it to make tween labels unique for each sprite. This was necessary because multiple sprites were interfering with each other’s animations. The hot fix also made Textbox a subclass of Sprite, which was natural.

 

Now done.

 

 

Let’s Make Honey! Version 0.13 – You’re a Tool for Packaging Apps


 

Let’s Make Honey!

 

Today we’re going to turn the app packaging process into a tool.

 

 

No, no, it’s only going to be a small tool. We’re making a lot of assumptions, and it won’t be very versatile, but it will work for now for packaging games made with Honey.

 

Before we forget, let’s set the version to 0_13 in both Engine’s SConstruct and BearsLoveHoney’s SConstruct files, just in case. We won’t be building a new version of Honey today, but if we do, we don’t want it to clobber the last one.

 

Instead, we’re adding one file to the Tools directory, which has sat empty until now, and we’re wrapping up BearsLoveHoney into an app.

 

So. What are the simplifying assumptions our simple tool is making? We’re going to have to copy frameworks, and we only know about the ones from the SDL, so our tool won’t work with programs that need to copy other frameworks besides these. Also, we’re letting folks specify a series of additional material (folders like Art and Sound and Fonts, and files like config.txt) but we’re copying it all to one place, so our tool won’t work with programs that store their content somewhere other than next to the program. And we’re taking a stab at automatic icon generation, but we’re starting from a 512×512 png and making a minimal set of icons, so our tool won’t work with programs that start with a different base size or need retina icons. And heck, we’re assuming the SDL frameworks are in /Library/Frameworks, so our tool won’t work for programs where those are somewhere else.

 

But our tool will work with programs that use the Honey library (and its dependency on SDL2, SDL2_image, SDL2_mixer, and SDL2_ttf), have a 512×512 png file ready for icon making, and have dependent files in folders at the same level as the program (ie, next to ABearCs are Art, Sound, etc). In fact, these will be the parameters the tool asks for.

 

To start, let’s prepare BearsLoveHoney for packaging. Make a directory called BearsLoveHoney/Release, and inside that, BearsLoveHoney/Release/BearsLoveHoney. Grab a copy of this file (here or from github) and add it to BearsLoveHoney/Release with the name bearslovehoney.png:

 

 

Adorable.

 

Your folder structure should look like this now:

 

 

Add a new script file named app to the Tools directory, and make it executable by running chmod +x app inside Tools.

 

Our app script is just uses python to make some system calls, following roughly the plan we layed out a few posts ago when we did this manually for ABearCs. Super low tech. Add this to app:

 


#!/usr/bin/python

import argparse
import os

parser = argparse.ArgumentParser()

parser.add_argument("--name", action="store", required=True)
parser.add_argument("--program", action="store", required=True)
parser.add_argument("--extra_files", action="store", required=True)
parser.add_argument("--icon", action="store", required=True)
parser.add_argument("--output_path", action="store", required=True)

args = parser.parse_args()
name = args.name
program = args.program
extra_files = args.extra_files.split(",")
icon = args.icon
output_path = args.output_path



######

print "Making directories"

def place(item):
  return os.path.join(output_path, "%s.app" % name, item)

os.system("mkdir -p %s" % place(""))
os.system("mkdir -p %s" % place("Contents/MacOS"))
os.system("mkdir -p %s" % place("Contents/Resources"))
os.system("mkdir -p %s" % place("Contents/Frameworks"))



######

print "Writing Info.plist and launch file"

info = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleGetInfoString</key>
  <string>_name_</string>
  <key>CFBundleExecutable</key>
  <string>launch</string>
  <key>CFBundleIdentifier</key>
  <string>_name_</string>
  <key>CFBundleName</key>
  <string>_name_</string>
  <key>CFBundleIconFile</key>
  <string>_name_.icns</string>
  <key>CFBundleShortVersionString</key>
  <string>0.01</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundlePackageType</key>
  <string>APPL</string>
  <key>IFMajorVersion</key>
  <integer>0</integer>
  <key>IFMinorVersion</key>
  <integer>1</integer>
</dict>
</plist>
"""

with open(place("Contents/Info.plist"), "w") as infofile:
  infofile.write(info.replace("_name_", name))


launch = """#!/bin/bash
cd "${0%/*}"
./_program_
"""

with open(place("Contents/MacOS/launch"), "w") as launchfile:
  launchfile.write(launch.replace("_program_", os.path.basename(program)))

os.system("chmod +x %s" % place("Contents/MacOS/launch"))



######

print "Copying program files"

os.system("cp -R %s %s" % (program, place("Contents/MacOS")))
for path in extra_files:
  os.system("cp -R %s %s" % (path, place("Contents/MacOS")))



######

print "Making icons"

os.system("mkdir %s" % place("Contents/Resources/%s.iconset" % name))
big_icon_path = place("Contents/Resources/%s.iconset/icon_512x512.png" % name)
os.system("cp %s %s" % (icon, big_icon_path))
for size in [16, 32, 128, 256]:
  little_icon_path = place("Contents/Resources/%s.iconset/icon_%dx%d.png" % (name, size, size))
  os.system("sips -z %d %d %s --out %s" % (size, size, big_icon_path, little_icon_path))

os.system("iconutil -c icns -o %s %s" % (place("Contents/Resources/%s.icns" % name), place("Contents/Resources/%s.iconset" % name)))
os.system("rm -rf %s" % place("Contents/Resources/%s.iconset" % name))



######

print "Copying and relinking frameworks"

for framework in ["SDL2", "SDL2_image", "SDL2_mixer", "SDL2_ttf"]:
  os.system("cp -R /Library/Frameworks/%s.framework %s" % (framework, place("Contents/Frameworks")))
  os.system("install_name_tool -change @rpath/%s.framework/Versions/A/%s @executable_path/../Frameworks/%s.framework/Versions/A/%s %s" % (framework, framework, framework, framework, place("Contents/MacOS/%s" % os.path.basename(program))))



######

print "Done! Your app is an app."

 

We’re going to run this with the command:

 

./app --name BearsLoveHoney --program ../BearsLoveHoney/BearsLoveHoney --extra_files ../BearsLoveHoney/config.txt,../BearsLoveHoney/Sound,../BearsLoveHoney/Art,../BearsLoveHoney/Fonts --icon ../BearsLoveHoney/Release/bearslovehoney.png --output_path ../BearsLoveHoney/Release/BearsLoveHoney/

 

So we supply the name BearsLoveHoney, the location of the program file in the BearsLoveHoney folder, a comma separated list with each of config.txt, Sound, Art, and Fonts, the new bearslovehoney.png as the icon file, and the output path Release/BearsLoveHoney.

 

In lines 6-19, we parses these arguments into variables. No validation, just parses ’em and go. Very simple for now.

 

In lines 25-33, we makes directories, first the .app container directory, then Contents, then MacOS, Frameworks, and Resources.

 

In lines 37-70, we defines a string with the contents of the Info.plist file, replaces the name with the name of our new program, and writes that file to .app/Contents/Info.plist.

 

Similarly in lines 73-81, we defines a string with the contents of the program launcher, replaces the program field with the path to our program, and writes the launcher to .app/Contents/MacOS/launcher, which we then makes executable with chmod +x.

 

In lines 87-91, we copies the program and all associated files to .app/Contents/MacOS.

 

In lines 97-107, we copies the icon png file to a special location as icon_512x512.png, then uses the sips tool to make shrunken copies at 256×256, 128×128, 32×32 and 16×16. Then we uses iconutil to wrap these all up into a single icns file, which we places in .app/Contents/MacOS/Resources, and we cleans up our temporary files.

 

Finally, in lines 113-117, we copies our frameworks from /Library/Frameworks to .app/Contents/Frameworks and uses install_name_tool to make the program point at the frameworks in that location.

 

So, try running it:

 

./app --name BearsLoveHoney --program ../BearsLoveHoney/BearsLoveHoney --extra_files ../BearsLoveHoney/config.txt,../BearsLoveHoney/Sound,../BearsLoveHoney/Art,../BearsLoveHoney/Fonts --icon ../BearsLoveHoney/Release/bearslovehoney.png --output_path ../BearsLoveHoney/Release/BearsLoveHoney/

 

You should get a shiny new app file in BearsLoveHoney/Release/BearsLoveHoney, and you should double click it, and it should run, and in an ideal world, which I’m just going to assume is what you’ve got, our work here is done:

 

 

Let’s Make Honey! Version 0.10 – Starshine the Graphics!


 

Let’s Make Honey!

 

I’ve always liked cute stars in cute games.

 

 

Today we’ll modify Honey Graphics to allow scale, rotation, color and transparency, and add an oscillation to Effects, and we’ll use all of that to make some dang stars.

 

Standard caveat: I’m opening up my learning process in the spirit of the CRAPL license. I’ll be making C++ mistakes and learning to correct them in public! For the first ten or twenty posts, don’t trust me as a C++ teacher!

 

First, let’s set the version to 0_10 in both Engine’s SConstruct and BearsLoveHoney’s SConstruct files.

 

Let’s make a little change to Effects. We’re going to add oscillations, basically tweens that go back and forth on a sinewave forever (or until they’re destroyed). Add this to effects.h:

 


/*

  Honey
  Copyright 2018 - Matthew Carlin

  Effects class provides convenience methods to create visual effects.
*/

#ifndef HONEY_EFFECTS_H_
#define HONEY_EFFECTS_H_

#include <chrono>
#include <math.h>
#include <string>
#include <stdlib.h>
#include <unordered_map>

#include "graphics.h"
#include "logic.h"

namespace Honey {
  class Effects {
   public:

      Effects();

      enum STYLES {
        LINEAR,
        SIGMOID,
        CUBIC,
        RAMPDOWN,
        RAMPUP,
        SINEWAVE
      };

      const float sigmoid_steepness = 5;

      std::unordered_map<std::string, float> tween_starts;
      std::unordered_map<std::string, float> tween_ends;
      std::unordered_map<std::string, float> widths;

      void makeTween(std::string label, float start_value, float end_value, float seconds);
      float tween(std::string label, int style);

      void makeShake(std::string label, int shake_width, float seconds);
      float shake(std::string label);

      void makeOscillation(std::string label, float oscillation_width, float period_in_seconds);
      float oscillation(std::string label);
  };

  extern Effects* effects;
}
#endif

 

We’re adding graphics to effects for the future, so we can do complex graphical effects. We won’t really use it today thought. We also change the shake_widths map to widths, because we’re going to re-use it for oscillations. Finally, we add methods to make and get oscillations.

 

We’ll need to make a few changes to account for widths instead of shake_widths, and we’ll need to fill in our new methods. Do this in effects.cpp:

 


/*

  Honey
  Copyright 2018 - Matthew Carlin

*/

#include "effects.h"

namespace Honey {
  Effects* effects = new Effects();

  Effects::Effects() {
    tween_starts = {};
    tween_ends = {};
    widths = {};
  }

  ...

  void Effects::makeShake(std::string label, int shake_width, float seconds) {
    logic->markDuration(label, seconds);
    logic->markTime(label);
    widths[label] = shake_width;
  }

  float Effects::shake(std::string label) {
    // If there's no label, return 0
    if (logic->time_markers.count(label) == 0) {
      return 0;
    }

    // If the label is expired, destroy it and return 0
    if (logic->timeSince(label) > logic->duration(label)) {
      logic->remove(label);
      widths.erase(label);
      return 0;
    }

    // Return a random number between -width/2 and width/2
    return rand() % (int) widths[label] - widths[label] / 2.0;
  }

  void Effects::makeOscillation(std::string label, float oscillation_width, float period_in_seconds) {
    logic->markDuration(label, period_in_seconds);
    logic->markTime(label);
    widths[label] = oscillation_width;
  }

  float Effects::oscillation(std::string label) {
    // If there's no label, return 0
    if (logic->time_markers.count(label) == 0) {
      return 0;
    }

    // We *don't* check if the oscillation has expired. Oscillations are infinite, until they're manually deleted.

    // We're misusing duration for period
    float time_fraction = logic->timeSince(label) / logic->duration(label);
    float space_fraction = sin(2 * M_PI * time_fraction);
    return (2 * space_fraction - 1) * widths[label];
  }
}

 

Only that last bit needs explanation. Instead of checking for the end of our effect, we let it go on forever. We use the sin function to run one complete oscillation per time period, and we use (2 * space_fraction - 1) * widths[label] to oscillate between -width and width. Forever!

 

Randomly, I need you to do this in graphics.cpp:

 


  void Graphics::clearScreen(std::string color) {
    // The clear screen method takes a hex-string color (eg #A4F4E3 or #FFFFFF or #003030)
    // and decomposes it into r, g, and b floats, which are each a fraction from 0 (black)
    // to 1 (fully saturated). (1,0,0) is full red, (0,1,0) is full green, (0,0,1) is full
    // blue, and (1,1,1) is full white.
    float r = std::stoi(color.substr(1,2), 0, 16) / 255.0f;
    float g = std::stoi(color.substr(3,2), 0, 16) / 255.0f;
    float b = std::stoi(color.substr(5,2), 0, 16) / 255.0f;

    // Tell OpenGL to clear the whole screen to our chosen color.
    glClearColor(r, g, b, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Also, for convenience, reset the model matrix
    model = glm::mat4(1.0);
    if (using_2d) {
      glUniformMatrix4fv(mvp_matrix_id, 1, GL_FALSE, glm::value_ptr(projection * model));
    }
  }

 

I forgot to do that in a previous commit. It resets the model every time we clear the screen, meaning if we translate something in one iteration, it doesn’t stick around in later iterations to throw off our calculations.

 

If you build the library (Go to Engine and run scons), it should work just fine.

 

Let’s test the oscillations with… a star! I made you a star. Just a plain old simple star. It’s nice though. You can find it here. Add it to BearsLoveHoney/Art.

 

Let’s add some things to BearsLoveHoney’s 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">false</param>
  <param type="int" name="bear_margin">340</param>
  <param type="string" name="screen_color">#F0F0F0</param>
  <param type="int" name="first_bear_x">60</param>
  <param type="int" name="first_bear_y">350</param>
  <param type="int" name="star_y">100</param>
</section>

<section name="animation">
  <param type="float" name="animation_duration">0.35</param>
  <param type="float" name="choose_duration">0.25</param>
  <param type="int" name="tween_type">1</param>
  <param type="float" name="dip_height">40</param>
  <param type="float" name="shake_width">10</param>
  <param type="float" name="starbounce_height">20</param>
  <param type="float" name="starbounce_period">2</param>
</section>

 

Just some stuff to move the bears a little lower, position a star, and bounce it.

 

Add this to BearsLoveHoney’s main.cpp:

 


  // Load images
  graphics->addImage("explorer_bear", "Art/explorer_bear.png");
  graphics->addImage("grim_bear", "Art/grim_bear.png");
  graphics->addImage("lawn_dart_bear", "Art/lawn_dart_bear.png");
  graphics->addImage("star", "Art/star.png");

  // Add music and some sound effects
  sound->addMusic("background_music", "Sound/Nothing_to_Fear.mp3");
  sound->addSound("move_left", "Sound/C_Square1.wav");
  sound->addSound("move_right", "Sound/C_Square2.wav");
  sound->addSound("choose_1", "Sound/Chant1.wav");
  sound->addSound("choose_2", "Sound/Chant2.wav");
  sound->addSound("choose_3", "Sound/Chant3.wav");
  sound->addSound("choose_4", "Sound/Chant4.wav");
  sound->addSound("choose_5", "Sound/Chant5.wav");

  // Start playing music
  sound->playMusic("background_music", -1);

  // Constants to determine the carousel
  std::string bears[] = {"explorer_bear", "grim_bear", "lawn_dart_bear"};

  // Variables to track which bear is where and which animation is which.
  int first_bear = 0; // Mod 3
  int animation_direction = 0;

  // Set action keys
  input->addActionKey("select left", hot_config->getString("input", "select_left_key"));
  input->addActionKey("select right", hot_config->getString("input", "select_right_key"));
  input->addActionKey("choose", hot_config->getString("input", "choose_key"));

  // Make a star oscillation effect
  int star_y = hot_config->getInt("layout", "star_y");
  float starbounce_height = hot_config->getFloat("animation", "starbounce_height");
  float starbounce_period = hot_config->getFloat("animation", "starbounce_period");
  effects->makeOscillation("starbounce", starbounce_height, starbounce_period);


 

We’re adding a star, and creating an oscillation effect using the height and period values from configuration. Note that this bit of configuration won’t hot update, because we only checked it outside the loop.

 

Add some more stuff to main.cpp:

 


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

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

    // Draw the bears
    for (int i = 0; i <= 2; i++) {
      // Set the basic bear position
      float x = first_bear_x + i * bear_margin;
      float y = first_bear_y;
      // Add movement tweens if we're moving
      if (logic->isTimeLocked("movement")) {
        x += effects->tween("slide_bear_" + std::to_string(i), tween_type);
        y += effects->tween("dip_bear_" + std::to_string(i), effects->SINEWAVE);
      }
      // Add shakey shakey to the middle bear.
      // This will only do anything if the effect is turned on.
      if (i == 1) {
        x += effects->shake("shakey shakey");
        y += effects->shake("shakey shakey");
      }
      // Draw the dang ol' bear.
      graphics->drawImage(bears[(first_bear + i) % 3], x, y);
    }

    // Draw the stars
    // Translate once to change the position of all the stars
    graphics->translate(0, effects->oscillation("starbounce"), 0);
    for (int i = 0; i <= 2; i++) {
      float x = first_bear_x + i * bear_margin;
      float y = star_y;
      graphics->drawImage("star", x, y);
    }

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

  // Get rid of the bears. Throw those bears down the garbage chute!
  graphics->destroyImage("explorer_bear");
  graphics->destroyImage("grim_bear");
  graphics->destroyImage("lawn_dart_bear");
  graphics->destroyImage("star");


 

Now we draw the stars, three of ’em, above the heads of the bears. Note that we’re re-using the x values we already had for the bears. Also, we’re putting our tween inside a call to graphics->translate. That way we only have to do it once and it will apply to all three stars. Actually, it will apply to anything we draw below this point, and if we hadn’t reset the model on the next loop, it would apply then too. On the last line of this change, we destroy the star when we end the program.

 

Build the game (Go to BearsLoveHoney and run scons BearsLoveHoney), and then run ./BearsLoveHoney.

 

 

Wow, it’s kinda ugly. These are too large. They’re too white. They’re not well centered. They kinda just hang in place while they oscillate. We can change all that!

 

Back to graphics! It’s time to add some new powers to the Graphics class. We don’t need big ones. Small ones will do for today. Add this to graphics.h:

 


void pushModelMatrix();
void popModelMatrix();
void translate(float x, float y, float z);
void rotate(float angle, float x, float y, float z);
void scale(float x, float y, float z);

void clearScreen(std::string color);
void draw2D();
void cacheRectangle(float width, float height);
void drawRectangle(float x_position, float y_position, float width, float height);

void addImage(std::string label, std::string path);
void setImage(std::string label);
void drawImage(std::string label, int x_position, int y_position);
void drawImage(std::string label, int x_position, int y_position, bool centered, float rotation, float scale);
void destroyImage(std::string label);

 

We’ve added methods to rotate and scale, and we’ve added a second version of drawImage which looks like it’s going to let us use the rotation and scale, and decide whether to center the image.

 

Let’s fill in the rotate and scale methods. We’re doing these the same way we did translate: by using glm’s built in methods, multiplying them by the existing model matrix, and passing an updated copy of projection*model to the graphics card.

 


void Graphics::rotate(float angle, float x, float y, float z) {
  // GLM has a method for us to rotate objects. This changes the model matrix,
  // through multiplication, as though we applied a rotation, ie, rotated the object
  // by angle degrees around the vector x, y, z.
  model = model * glm::rotate(glm::radians(angle), glm::vec3(x, y, z));
  if (using_2d) {
    glUniformMatrix4fv(mvp_matrix_id, 1, GL_FALSE, glm::value_ptr(projection * model));
  }
}

void Graphics::scale(float x, float y, float z) {
  // GLM has a method for us to scale objects. This changes the model matrix,
  // through multiplication, as though we applied a scale, ie, scaled the object
  // by factors of x, y, and z in those respective directions.
  model = model * glm::scale(glm::vec3(x, y, z));
  if (using_2d) {
    glUniformMatrix4fv(mvp_matrix_id, 1, GL_FALSE, glm::value_ptr(projection * model));
  }
}

 

drawImage is a little more interesting. We’re now in a position to translate, rotate, and scale, and if there image is centered, we’ll have to scale a little bit more. There is a particular order to make things work correctly.

 

The key is realizing that all the operations take place from the perspective of the origin (0,0,0). If you translate first, and then rotate, since you’re rotating around the origin, you’ll rotate your object in a wide arc around 0.Instead, if you rotate first, and then translate, your rotation takes place at the origin, the object is still at the origin (just facing a different direction), and translate moves it correctly.

 

Similarly, if you translate first, and then scale, since you’re scaling around the origin, you’ll blow your object out further than the original translate (ie, one translated unit will become 2 translated units, or whatever). But if you scale first, since you’re scaling around the origin, your object will just be larger, and then the translate will move that larger object the correct number of units.

 

Finally, if you rotate first, and then scale, you’ll change the scale directions. For instance, if you double the size on the x axis, but first you rotate the object 90 degrees, you’ll instead double the size on the y axis. Scale first, and you’ll correctly rotate the larger object.

 

So we scale first, then rotate, then translate.

 

Except… we’re multiplying from the right. So right now, sigh, if we scale S, then rotate R, then translate T, the matrix we get is S * R * T, and when we apply it to an object, S * R * T * X, we get translation first T * X, then rotation R * T * X, then scale S * R * T * X.

 

Shut up! So that means we do all our operations in reverse order. We’re probably going to fix this later. I’m very sorry. But for now, in code, we translate, then rotate, then scale. And that centering translation? Well, that should come first (to put the object at the origin), sooooo… now it comes last.

 

I’m sorry. Now you know how weird graphics programming is. We’ll fix this in a later blog post. I’ll bring charts. And cookies.

 

Add this to graphics.cpp:

 


void Graphics::drawImage(std::string label, int x_position, int y_position, bool centered, float rotation, float scale) {
  pushModelMatrix();
  
  translate(x_position, y_position, 0);
  rotate(rotation, 0, 0, 1);
  this->scale(scale, scale, scale);

  if (centered) {
    int width = texture_widths[label];
    int height = texture_heights[label];
    translate(-width / 2.0, -height / 2.0, 0);
  }

  drawImage(label, 0, 0);

  popModelMatrix();
}

 

The important thing is it does what you think it does.

 

We’re going to test this rotation. Make some quick additions to BearsLoveHoney’s config.txt and main.cpp:

 


<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">false</param>
  <param type="int" name="bear_margin">340</param>
  <param type="string" name="screen_color">#F0F0F0</param>
  <param type="int" name="first_bear_x">60</param>
  <param type="int" name="first_bear_y">350</param>
  <param type="int" name="star_y">280</param>
</section>

<section name="animation">
  <param type="float" name="animation_duration">0.35</param>
  <param type="float" name="choose_duration">0.25</param>
  <param type="int" name="tween_type">1</param>
  <param type="float" name="dip_height">40</param>
  <param type="float" name="shake_width">10</param>
  <param type="float" name="starbounce_height">5</param>
  <param type="float" name="starbounce_period">2</param>
  <param type="float" name="startilt_angle">20</param>
  <param type="float" name="startilt_period">2</param>
</section>

 


float starbounce_height = hot_config->getFloat("animation", "starbounce_height");
float starbounce_period = hot_config->getFloat("animation", "starbounce_period");
effects->makeOscillation("starbounce", starbounce_height, starbounce_period);
float startilt_angle = hot_config->getFloat("animation", "startilt_angle");
float startilt_period = hot_config->getFloat("animation", "startilt_period");
effects->makeOscillation("startilt", startilt_angle, startilt_period);

 


// Draw the stars
// Translate once to change the position of all the stars
graphics->translate(0, effects->oscillation("starbounce"), 0);
for (int i = 0; i <= 2; i++) {
  float x = first_bear_x + i * bear_margin + 78;
  float y = star_y;
  graphics->drawImage("star", x, y, true, effects->oscillation("startilt"), 0.25);
}

 

We’ve added another new oscillation, startilt, and we now draw the stars shrunk down to 0.25 times their original size, and centered. Note that we changed the x value of the stars, adding 78 pixels to put them over the centers of the bears.

 

Build the library (Go to Engine and run scons) and the game (Go to BearsLoveHoney and run scons BearsLoveHoney), and then run ./BearsLoveHoney. Better! We’ve got some cute stars now. But they could be in color.

 

We’re going to pass a color to the graphics card, and use it as the global color for everything; if you set this color to green, everything will be green tinted until you set it back. Back to what? Well, to white, which is the neutral color that doesn’t modify anything. Way to play it safe, buddy.

 

First, we’ll modify graphics.cpp to add another variable for passing to the graphics card. This works just like our mvp_matrix:

 


// Variables for communicating with the graphics card
GLuint mvp_matrix_id;
GLuint color_id;
GLuint texture_sampler_id;
GLuint shader_program;

// Variables for working on the CPU size
glm::mat4 projection;
glm::mat4 model;
glm::vec4 color;
bool using_2d;
std::vector<glm::mat4> model_stack;

void initialize(Window* window);
void initializeOpenGL();
void initializeBuffersAndGeometry();
void initializeShaders();

void pushModelMatrix();
void popModelMatrix();
void translate(float x, float y, float z);
void rotate(float angle, float x, float y, float z);
void scale(float x, float y, float z);

void setColor(std::string color, float opacity);
void setColor(float r, float g, float b, float opacity);

 

We also add setColor methods so we can change this color.

 

We need to register our variable with the graphics card, and set it to the default value:

 


void Graphics::initializeBuffersAndGeometry() {
  // Here we set up some things that are going to be fed to the shader program.
  // First is the mvp (model-view-projection) matrix.
  mvp_matrix_id = glGetUniformLocation(shader_program, "mvp_matrix");
  // Second is the texture sampler.
  texture_sampler_id = glGetUniformLocation(shader_program, "texture_sampler");
  // Third is the color.
  color_id = glGetUniformLocation(shader_program, "color");

  // Remember, modern OpenGL works by feeding data (vertices, normals, textures, colors)
  // straight to the graphics card. Here we're telling OpenGL to prepare for an array of
  // vertices.
  GLuint vertex_array_id;
  glGenVertexArrays(1, &vertex_array_id);
  glBindVertexArray(vertex_array_id);

  // Here we create our rectangle texture data and pass it to the card.
  GLfloat texture_data[] = { 
    0.0f, 0.0f,
    0.0f, 1.0f,
    1.0f, 1.0f,
    1.0f, 0.0f
  };

  glGenBuffers(1, &rectangle_texture_buffer);
  glBindBuffer(GL_ARRAY_BUFFER, rectangle_texture_buffer);
  glBufferData(GL_ARRAY_BUFFER, sizeof(texture_data) * 4 * 2, texture_data, GL_STATIC_DRAW);

  // The model matrix stores the transform (rotation, translation, scale) of
  // one object. We start out with the identity matrix, which corresponds to
  // "no transform at all".
  model = glm::mat4(1.0);

  // We also start out with white as the color.
  color = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f);
}

 

Now let’s fill in the setColor methods in graphics.cpp:

 


void Graphics::setColor(std::string color, float opacity) {
  // The clear screen method takes a hex-string color (eg #A4F4E3 or #FFFFFF or #003030)
  // and decomposes it into r, g, and b floats, which are each a fraction from 0 (black)
  // to 1 (fully saturated). (1,0,0) is full red, (0,1,0) is full green, (0,0,1) is full
  // blue, and (1,1,1) is full white.
  float r = std::stoi(color.substr(1,2), 0, 16) / 255.0f;
  float g = std::stoi(color.substr(3,2), 0, 16) / 255.0f;
  float b = std::stoi(color.substr(5,2), 0, 16) / 255.0f;

  setColor(r, g, b, opacity);
}

void Graphics::setColor(float r, float g, float b, float opacity) {
  color = glm::vec4(r, g, b, opacity);
  glUniform4fv(color_id, 1, glm::value_ptr(color));
}

void Graphics::clearScreen(std::string color) {
  // The clear screen method takes a hex-string color (eg #A4F4E3 or #FFFFFF or #003030)
  // and decomposes it into r, g, and b floats, which are each a fraction from 0 (black)
  // to 1 (fully saturated). (1,0,0) is full red, (0,1,0) is full green, (0,0,1) is full
  // blue, and (1,1,1) is full white.
  float r = std::stoi(color.substr(1,2), 0, 16) / 255.0f;
  float g = std::stoi(color.substr(3,2), 0, 16) / 255.0f;
  float b = std::stoi(color.substr(5,2), 0, 16) / 255.0f;

  // Tell OpenGL to clear the whole screen to our chosen color.
  glClearColor(r, g, b, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // Also, for convenience, reset the model matrix
  model = glm::mat4(1.0);
  if (using_2d) {
    glUniformMatrix4fv(mvp_matrix_id, 1, GL_FALSE, glm::value_ptr(projection * model));
  }

  // and the color
  setColor(1.0f, 1.0f, 1.0f, 1.0f);
}

 

The first method does the same string trick we already did in clearColor, decomposing strings like #A4F4E3 into r,g,b values. Then it calls the second method. The second method just sends our color to the graphics card. Finally, in clearScreen, we do our users the courtesy of resetting the global color, just like we reset the model matrix. This means if you turn everything green, the effect won’t stick around in the next iteration of the game loop.

 

Using the color on the graphics card is actually pretty simple. Add this to shaders.h:

 


const std::string fragment_shader = R"(
#version 330 core

uniform sampler2D texture_sampler;
uniform vec4 color;

in vec2 fragment_texture_vector;

out vec4 final_color_vector;

void main(){
  final_color_vector = color * texture(texture_sampler, fragment_texture_vector);
} 
)";

 

We just tell the fragment shader (which, remember, operates on every pixel that gets drawn, thousands or millions of pixels) to take in a color value, and multiply whatever else we have (currently just the texture color) by the new color. This multiplication is done component by component; red * red, green * green, blue * blue, opacity * opacity. This is why white is the neutral color; white is 1, 1, 1, 1, which means it does nothing when you multiply it.

 

Now we can add colors to the stars in BearsLoveHoney! Modify config.txt and main.cpp like so:

 


<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">false</param>
  <param type="int" name="bear_margin">340</param>
  <param type="string" name="screen_color">#F0F0F0</param>
  <param type="int" name="first_bear_x">60</param>
  <param type="int" name="first_bear_y">350</param>
  <param type="int" name="star_y">280</param>
  <param type="string" name="star_color_0">#ff6961</param>
  <param type="string" name="star_color_1">#77dd77</param>
  <param type="string" name="star_color_2">#779ecb</param>
</section>

 


// Draw the stars
// Translate once to change the position of all the stars
graphics->translate(0, effects->oscillation("starbounce"), 0);
for (int i = 0; i <= 2; i++) {
  float x = first_bear_x + i * bear_margin + 78;
  float y = star_y;
  graphics->setColor(hot_config->getString("layout", "star_color_" + std::to_string(i), 1);
  graphics->drawImage("star", x, y, true, effects->oscillation("startilt"), 0.25);
}

 

Build the library (Go to Engine and run scons) and the game (Go to BearsLoveHoney and run scons BearsLoveHoney), and then run ./BearsLoveHoney.

 

 

Yay!

 

One last thing. When the player presses choose, I don’t just want the middle bear to shake. God damnit, I want stars. We’re going to make a little star field.

 

Add the following to BearsLoveHoney’s main.cpp:

 


// Make a star oscillation effect
int star_y = hot_config->getInt("layout", "star_y");
float starbounce_height = hot_config->getFloat("animation", "starbounce_height");
float starbounce_period = hot_config->getFloat("animation", "starbounce_period");
effects->makeOscillation("starbounce", starbounce_height, starbounce_period);
float startilt_angle = hot_config->getFloat("animation", "startilt_angle");
float startilt_period = hot_config->getFloat("animation", "startilt_period");
effects->makeOscillation("startilt", startilt_angle, startilt_period);

// Make a star field
int star_field_x[60];
int star_field_y[60];
for (int i = 0; i < 60; i++) {
  star_field_x[i] = logic->randomInt(-screen_width / 2.0, 1.5 * screen_width);
  star_field_y[i] = logic->randomInt(-screen_height / 2.0, 1.5 * screen_height);
}

...

// If the users presses the choose button, shake the middle bear.
if (input->actionDown("choose") && !logic->isTimeLocked("movement")) {
  sound->playSound("choose_" + std::to_string(logic->randomInt(1,5)), 1);
  logic->makeTimeLock("movement", lock_duration);
  effects->makeShake("shakey shakey", shake_width, choose_duration);
  effects->makeTween("star_phasing", 0, 1, animation_duration);
  animation_direction = 0;
}

...

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

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

// Light up the sky
if (effects->tween("star_phasing", 0) != 0) {
  float opacity = effects->tween("star_phasing", effects->SINEWAVE);
  float shooting_star = 300 * effects->tween("star_phasing", effects->LINEAR);
  graphics->setColor(hot_config->getString("layout", "star_color_1"), opacity);

  for (int i = 0; i < 60; i++) {
    graphics->drawImage("star", star_field_x[i] + shooting_star, star_field_y[i] - shooting_star, true, 0, 0.15);
  }

  graphics->setColor(1, 1, 1, 1);
}

 

First we use logic->randomInt to make a bunch of points in space. These points are all in a box that’s twice the size of the screen (ie, from -0.5 to 1.5 in both x and y directions). Then, when the player presses the choose button, we make a new tween called star_phasing.

 

Finally, at the beginning of the draw mode (first, so that it draws behind the bears), we use star_phasing to get the opacity; these stars will fade in and then fade out on a sinewave tween. Then, we use it again to set a shooting star effect; these stars will also fly off in one direction together. Then we iterate through our 60 stars, and draw each one using both tweens.

 

Finally, we set the color back to default so it doesn’t affect anything else.

 

Build the library (Go to Engine and run scons) and the game (Go to BearsLoveHoney and run scons BearsLoveHoney), and then run ./BearsLoveHoney.

 

 

It’s full of stars!

 

 

Next time we’ll add fonts and letter printing, so we can print arbitrary strings to the screen.

 

A Wild Game Appears! Part 2


 

Ahoyo!

 

 

Today we’re going to go from this:

 

 

to this:

 

And what took me hours and hours to figure out should hopefully, hopefully only take you a few weird minutes to copy.

 

This post isn’t theory. It’s recipe. I barely understand what I’m doing in the OS X application configuring space, and my goal is just to pull you up to my bare level of understanding, while shouting at you loudly to avoid the stupid mistake I made.

 

So let’s start with that mistake. Don’t worry about setting the minimum version of your program. It’s all bunk. Don’t use variables like MACOSX_DEPLOYMENT_TARGET or mmacos-version-min, because they don’t actually matter if your underlying frameworks are higher version, and they will be. Oh, they will be. You’ll end up with something that you know plays on an old machine, but the OS will refuse to play it.

 

Instead, just use a launcher program, and figure out the minimum version yourself by trying it on some old macs. I ignored that advice and it cost me hours and hours.

 

Packaging an application, in the common case, is actually pretty simple, albeit pretty weird. We’ll be working from a really nice stack overflow answer that seems to have worked out pretty well. In fact, it has worked so well that we’re going to make a tool out of it in a later version of Honey.

 

But for now, the manual way.

 

In order to present regular mac users a simple application, we need to do three (or four? six?) things:

 

1. Put our game inside a folder called ABearCs.app with a particular folder structure.
2. Make some icons.
3. Add our frameworks.
4. Add a little launcher.
5. Write a config file.
6. Use a truly bizarre little hacky tool to point our executable at the frameworks.

 

Cool, six things. Pretty much the only sensible thing is making icons.

 

 

Sigh. Weird thing 1.

 

Make a folder in ABearCs called Release. Inside this, make another folder called ABearCs. This is where we’re going to put our single application. It’s the last sensible thing we’ll do. Inside that folder, make another folder called ABearCs. Your folders should now look like this:

 

 

That last folder looks like a folder now, but later we’re going to add “.app” to the name, and it’s going to turn into a single application, with a pretty icon and everything.

 

Mac applications have a specific structure, so inside that app folder, we need to create a bunch more folders. Create one called Contents, and then inside it, create three more, called MacOS, Frameworks, and Resources. Your folders should now look like this mess:

 

 

Now we just need to put the game in there. From the top level, copy the ABearCs executable, config.txt, Art folder, and Sound folder to MacOS. Your folders should now look like this:

 

 

So that’s weird thing 1 done: we’ve put our actual program in a set of folders that conform to a very particular format.

 

 

Sigh. Weird thing 2.

 

 

I lied. Icons aren’t that sensible. In order to make an icon package, at bare minimum we’re going to have to take one large file (512×512 is ideal) and make it into five files in a weirdly named folder, like this:

 

 

Those names, specifically. You need those names, and each file needs to be the size indicated by the name. One way to do this is to open your image editor (eg, www.adobe.com/Illustrator‎ or Gimp) and manually shrink and save new copies several times.

 

Another way is to use the command line sips tool, running:

 

sips -z 256 256 icon_512x512.png --out icon_256x256.png
sips -z 128 128 icon_512x512.png --out icon_128x128.png
sips -z 32 32 icon_512x512.png --out icon_32x32.png
sips -z 16 16 icon_512x512.png --out icon_16x16.png

 

When you have your folder looking like the picture above, go to the command line and run:

iconutil -c icns -o ABearCs.icns ABearCs.iconset

This creates the iconset file that OS X needs to display icons. It’s just a collection of different sized pictures the system can use to scale icons.

 

 

Actually thing 3 makes sense.

 

 

When we compiled ABearCs last time with our SConstruct file, we specified frameworks. OpenGL we don’t need to worry about; every Mac has this. But the SDL frameworks aren’t on every Mac, aren’t even on most Macs. We need to copy these frameworks into the application.

 

Go to /Library/Frameworks and copy SDL2.framework, SDL2_image.framework, and SDL2_mixer.framework into the Frameworks folder of our weird construction. Your folders should now look like this:

 

 

That’s that done.

 

 

Back to weird things. Weird thing 4.

 

 

We could configure the application to call our executable ABearCs program directly, but if we do, the operating system will check which versions of OS X are allowed to run it, and for stupid reasons, it’s difficult to get our program to build without making itself require our (ie, the latest or almost latest) version of OS X. We could do it in XCode (gag), or by downloading a compatibility SDK (ugh).

 

Or, we could just make a little launcher program, version free, and have the application call the launcher, and have the launcher call your program. It’s crazy, but it works.

 

It’s also the case that the working directory of a Mac application is wherever the application is located (usually your Applications directory), and not the MacOS folder where we were required to put our actual program. That means all our local file loads (to config.txt, art files, and sound files) will break. We could write an operating system dependent method in our program which changes the working directory on OS X, meaning a lot more gross OS code.

 

Or we could just make a little launcher program. We can change the directory here before calling the actual game. Yeah, let’s do that.

 

In the MacOS folder, make a little file called launch, and chmod +x to give it executable permission. Add this shell script to the file:

 


#!/bin/bash
cd "${0%/*}"
./ABearCs

 

This changes to its own directory (HA!) and runs ABearCs. So that’s done.

 

 

Sigh. Weird thing 5.

 

 

If it’s not already crystal clear, I’ll say it: I don’t think this is a good process.

 

Now we come to the configuration file, and you’d think that’s sensible. It is not. Our version is very small and will be almost sensible. But the world of Mac application configurations is generally not sensible.

 

Make a file called Info.plist inside Contents, and fill it with this:

 


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleGetInfoString</key>
  <string>ABearCs</string>
  <key>CFBundleExecutable</key>
  <string>launch</string>
  <key>CFBundleIdentifier</key>
  <string>FriendsOnMountains</string>
  <key>CFBundleName</key>
  <string>ABearCs</string>
  <key>CFBundleIconFile</key>
  <string>ABearCs.icns</string>
  <key>CFBundleShortVersionString</key>
  <string>0.01</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundlePackageType</key>
  <string>APPL</string>
  <key>IFMajorVersion</key>
  <integer>0</integer>
  <key>IFMinorVersion</key>
  <integer>1</integer>
</dict>
</plist>

 

The important things hee are the name (ABearCs), the IconFile (ABearCs.icns, which by default gets fetched from the resources directory), and the Executable (launch, which by default gets fetched from the MacOS directory). Don’t think too hard about anything else, or else go here and here knock yourself out.

 

 

Finally! Weird thing 6!

 

 

This one’s really weird. We’ve put our frameworks in a Frameworks directory, but we haven’t hooked them up to the program. Our program was compiled to search in /Library/Frameworks (or somewhere similar, somewhere outside our application) for those frameworks. We need to modify it to search inside the application.

 

Do we do that with a config setting? Oh no. No, we use a special tool to modify the executable itself.

 

In a terminal, navigate to the MacOS folder (ie, ABearCs/Release/ABearCs/ABearCs/Contents/MacOS). Run this command: otool -L ABearCs

 

You should see output roughly like this:

 


/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1443.13.0)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
@rpath/SDL2.framework/Versions/A/SDL2 (compatibility version 1.0.0, current version 7.0.0)
@rpath/SDL2_image.framework/Versions/A/SDL2_image (compatibility version 1.0.0, current version 1.1.0)
@rpath/SDL2_mixer.framework/Versions/A/SDL2_mixer (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0)

 

Those @rpaths are wrong; we’re going to change them to use @executable_path. Run the following three commands:

 

install_name_tool -change @rpath/SDL2.framework/Versions/A/SDL2 @executable_path/../Frameworks/SDL2.framework/Versions/A/SDL2 ABearCs

install_name_tool -change @rpath/SDL2_image.framework/Versions/A/SDL2_image @executable_path/../Frameworks/SDL2_image.framework/Versions/A/SDL2_image ABearCs

install_name_tool -change @rpath/SDL2_mixer.framework/Versions/A/SDL2_mixer @executable_path/../Frameworks/SDL2_mixer.framework/Versions/A/SDL2_mixer ABearCs

 

Now run otool -L ABearCs again. This time you should see:

 

/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1443.13.0)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
@executable_path/../Frameworks/SDL2.framework/Versions/A/SDL2 (compatibility version 1.0.0, current version 7.0.0)
@executable_path/../Frameworks/SDL2_image.framework/Versions/A/SDL2_image (compatibility version 1.0.0, current version 1.1.0)
@executable_path/../Frameworks/SDL2_mixer.framework/Versions/A/SDL2_mixer (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0)

 

Now the executable points at the local frameworks, and everything should work.

 

 

Now we have an app! Let’s package it up.

 

So your folders should look like this:

 

 

Rename the ABearCs folder (the one just above Contents, not the one just below Release) to ABearCs.app, and this magic should happen:

 

 

Yay! It’s a single application. If you double click it, it should run like any other. You can copy it to Applications, you can add it to the dock, everything.

 

Let’s wrap it up as a dmg. Open Disk Utility, and go to File->New Image->Image from Folder. Choose the outer ABearCs (the one just below Release). Save as ABearCs, inside the release folder. It should make you a dmg. Your folders, now in their final form, should look like this:

 

 

And we’re done with this dumb process. Let’s blow this popsicle stand.

 

To reward myself, I made a little landing page for this little game.

 

Next time we’ll return to writing new features for Honey. Yay!

 

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).

 

 

Let’s Make Honey! Version 0.09 – Sounds Good


 

Let’s Make Honey!

 

 

Today we add squishy squishy sound.

 

Standard caveat: I’m opening up my learning process in the spirit of the CRAPL license. I’ll be making C++ mistakes and learning to correct them in public! For the first ten or twenty posts, don’t trust me as a C++ teacher!

 

 

Prepping the SDL

 

We’ll be using the SDL, and like when we worked with images, what this really means is we’ll be using another external library some nice people made to work with the SDL. This time it’s SDL_mixer, which will allow us to load mp3s and play multi-channel sound. Multi-channel sound just means sounds that stack on top of each other. It’s something we’ll see in passing today but we really won’t think much about it.

 

Go to the SDL_mixer download site, and I CANNOT STRESS THIS ENOUGH, get the version 2.0.0 dmg file, not the version 2.0.2. Per a comment from an obscure gist snippet, version 2.0.2 has an error which will prevent it from loading mp3s, whereas version 2.0.0 works fine, and I can confirm that this is true.

 

Seriously, download version 2.0.0, not version 2.0.2.

 

Open it and install the SDL2_mixer framework in /Library/Frameworks, same as you did with SDL2 and SDL2_image.

 

Now set the version to 0_09 in both Engine’s SConstruct and BearsLoveHoney’s SConstruct files. Additionally, modify line 10 of BearsLoveHoney’s SConstruct to read:

 


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

 

We’re just telling our builds to use the SDL2_mixer framework along with all those others.

 

Before we really get going, we’re also going to make some small changes to the logic and effects class, to seed our random numbers earlier and to add a random integer generator:

 

 

As always, that diff screenshot is also a link to the actual diff on github.

 

Now, before we write an actual sound class, we’re going to have to modify our very early init work to turn sound on. Make these modifications to window.h and window.cpp:

 

 

We’re adding SDL2_mixer to the window class, and we’re making sure SDL initializes with audio as well as video and game controllers. On lines 56-61 of window.cpp, we use the SDL_mixer Mix_Init function to tell the mixer to initialize mp3 functionality. Later, when we quit the window, we also quit the mixer.

 

Build the library (Go to Engine and run scons) and the game (Go to BearsLoveHoney and run scons BearsLoveHoney), and then run ./BearsLoveHoney. It should still work as before.

 

Classy Audio

 

 

Now we’ll write the audio wrapper class. In Honey, we’re going to have sounds (which are all wave files) and music (which are all mp3 files). From the user perspective, these won’t be very different. We may even combine them later if this structure proves too unwieldy.

 

But for now, since the SDL thinks of them as two different things under the hood, in Honey we’ll treat them as two different things for code simplicity.

 

Much like images, we want to be able to add, play and destroy sounds, and also add, play and destroy music. We also want volume control. For now, we’re going to settle on just this fairly simple functionality.

 

Sooooo, add empty sound.h and sound.cpp files to Engine/Source, and add this to sound.h:

 


/*

  Honey
  Copyright 2018 - Matthew Carlin

  Sound class wraps SDL sound functions.
*/

#ifndef HONEY_SOUND_H_
#define HONEY_SOUND_H_

#include <string>
#include <stdlib.h>
#include <unordered_map>

#include <SDL2/SDL.h>
#include "SDL2_mixer/SDL_mixer.h"

namespace Honey {
  class Sound {
   public:

      std::unordered_map<std::string, Mix_Chunk*> sounds;
      std::unordered_map<std::string, Mix_Music*> music;

      Sound();

      void initialize();

      // From 0 silent to 1 full volume
      void setSoundVolume(float fraction);
      void setMusicVolume(float fraction);

      void addSound(std::string label, std::string path);
      //-1 loops forever
      void playSound(std::string label, int loops);
      void destroySound(std::string label);

      void addMusic(std::string label, std::string path);
      //-1 loops forever
      void playMusic(std::string label, int loops);
      void destroyMusic(std::string label);
  };

  extern Sound* sound;
}
#endif

 

Just what I said. We have methods for adding, playing and destroying sound, and adding, playing, and destroying music. We can choose how many times the sound or the music loops. We also have methods for controlling sound and music volume independently of each other.

 

Under the hood, sound is stored in Mix_Chunk objects, and music is stored in Mix_Music objects. They’re not too different from our casual perspective.

 

Sound.cpp isn’t actually tooooo complicated, so let’s just punch it out all in one go:

 


/*

  Honey
  Copyright 2018 - Matthew Carlin

*/

#include "sound.h"

namespace Honey {
  Sound* sound = new Sound();

  Sound::Sound() {
    sounds = {};
    music = {};
  }

  void Sound::initialize() {
    Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 640);
  }

  void Sound::setSoundVolume(float fraction) {
    Mix_Volume(-1, (int) (MIX_MAX_VOLUME * fraction));
  }

  void Sound::setMusicVolume(float fraction) {
    Mix_VolumeMusic((int) (MIX_MAX_VOLUME * fraction));
  }  

  void Sound::addSound(std::string label, std::string path) {
    Mix_Chunk *sound;
    sound = Mix_LoadWAV(path.c_str());
    sounds[label] = sound;
  }

  void Sound::playSound(std::string label, int loops) {
    if (sounds.count(label) == 0) {
      printf("Failed to find %s in sounds.\n", label.c_str());
      return;
    }

    if (loops > 0) {
      // In PlayChannel, 0 is 1 loop, 1 is two loops, etc.
      loops--;
    }

    Mix_PlayChannel(-1, sounds[label], loops);
  }

  void Sound::destroySound(std::string label) {
    if (sounds.count(label) == 0) {
      printf("Failed to delete %s because it wasn't in sounds.\n", label.c_str());
      return;
    }

    Mix_FreeChunk(sounds[label]);
    sounds.erase(label);
  }

  void Sound::addMusic(std::string label, std::string path) {
    Mix_Music *musica;
    musica = Mix_LoadMUS(path.c_str());
    music[label] = musica;
  }

  void Sound::playMusic(std::string label, int loops) {
    if (music.count(label) == 0) {
      printf("Failed to find %s in music.\n", label.c_str());
      return;
    }

    Mix_PlayMusic(music[label], loops);
  }

  void Sound::destroyMusic(std::string label) {
    if (music.count(label) == 0) {
      printf("Failed to delete %s because it wasn't in sounds.\n", label.c_str());
      return;
    }

    Mix_FreeMusic(music[label]);
    music.erase(label);
  }
}

 

The initialize method uses Mix_OpenAudio to turn on very standard mid-quality stereo sound. You can read about it in depth if you want. I didn’t.

 

setSoundVolume uses Mix_Volume, and setMusicVolume uses Mix_VolumeMusic, both of which take an integer between 0 and MIX_MAX_VOLUME (which I think is 128). We want to set volume in fractions, so we just multiply by MIX_MAX_VOLUME and cast to an int.

 

addSound uses the Mix_LoadWAV function to load a wave file given by the path. I’m feeling lazy today, so we haven’t done checking to make sure this load works. We’ll add that in later. The file is loaded into a Mix_Chunk object, which we store in our map using the label as a key.

 

Similarly addMusic uses the Mix_LoadMUS function to load an mp3 file given by the path. We’re not checking success here either; again, we’ll add it later. The file is loaded into a Mix_Music object, which we store in our other map using the label as a key.

 

destroyChunk uses Mix_FreeChunk and destroyMusic uses Mix_FreeMusic, each to actually free the object from memory, after which we delete the label.

 

playMusic uses Mix_PlayMusic to play the Mix_Music object. It just needs this object and a number of loops. If we pass -1, it will loop forever. If we pass 0, it will treat the request as a No-op. Otherwise, if we pass N, it will play N times.

 

Similarly but not quite the same, playSound uses Mix_PlayChannel to play the Mix_Chunk sound object. In this case we have to specify a channel, the object, and a number of loops. Giving -1 for the channel means “just use the next available channel”, which is what we want, so we always do that. For the number of loops, -1 will again loop forever, but in this case, 0 means “play once”, 1 means “play twice”, etc. So if it’s not -1, we actually subtract 1 (to keep consistency with our other method). So for Honey, 1 always means “play once”, 2 means “play twice”, etc.

 

And that’s it! Build the library (Go to Engine and run scons) and the game (Go to BearsLoveHoney and run scons BearsLoveHoney), and then run ./BearsLoveHoney. It should still still still work as before. Only now we can put sounds in it.

 

 

Mr. Soundgood Goodsounds

 

 

To add to BearsLoveHoney today, we have some adorable sound by Friends On Mountain’s Trace Ronning. He’s given me a cute piece of music called “Nothing to Fear”. We also went a sound site called splice.com and bought some royalty free sounds to use in this and other games. You can find all of these on the Honey github page at https://github.com/bugsbycarlin/Honey/tree/master/BearsLoveHoney/Sound. Go there and grab those files. Make a directory in BearsLoveHoney called Sound and put all these sweet files there.

 

Modify config.txt to add some volume settings, and, heck, we’re going to need to shorten the animations to match the sound speeds, so modify those too:

 

 

And finally, modify BearsLoveHoney’s main.cpp like so:

 

 

Build the library (Go to Engine and run scons) and the game (Go to BearsLoveHoney and run scons BearsLoveHoney), and then run ./BearsLoveHoney. You should now have some candy sweet background music and some juicy action sounds when you move the carousel. Try changing the volume in the config file and see if it updates in game.

 

As for the code in main.cpp, I’m not even going to explain myself. We’re pretty literally just adding sounds, playing sounds, and destroying sounds, using the class we just created.

 

And it sounds good.