by Jen Downs
I love video games.
Like many other self-described gamers, I have an origin story that involves getting a console for Christmas when I was a kid.
I have grown up appreciating good games the same way I appreciate good books. Both video games and books have stories to tell, and—when done right—both capture your imagination and take you to new, exciting worlds. However, one of the foremost differences between the two is accessibility for creators. If you have a story to tell, you can put pen to paper (or fingers to a keyboard) easily enough, but creating a video game involves much more than that. Truly, a video game is more than just a compelling story; it is a multifaceted art-form that involves design and programming, in addition to a story.
To be honest, I have fantasized about making my own video game. For years, I filled notebooks with ideas, rough sketches, and plots. But I never imagined getting beyond that conceptual stage until I heard about a class being offered by my local community college last fall: Game Scripting in Unity.
Unity is a game development platform with a built-in 2D and 3D physics engine, among other things. I had never used Unity before, but this Game Scripting class was going to be in JavaScript (or, rather, Unity’s flavor of JavaScript).
I have a confession: I’m not a traditional programmer.
I learned JavaScript on my own over the years, as my front-end web development hobby grew into a career and overshadowed my English degree. I’m confident in my ability to use JavaScript as part of my front-end toolkit, but I was admittedly apprehensive about using it for game development.
Walking into the classroom—a computer lab—on the first day, I immediately spotted the only other female student and sat beside her with a silent smile and a nod. The rest of the room was buzzing with guys talking about their past projects and the kind of games they wanted to make.
The atmosphere was pleasant enough, but the escalating tone of the other students’ voices, the way people tried speaking over each other, and the cacophony of dismissive chuckles made me feel like people were sizing each other up—testing who was the more experienced programmer. The tension reached a high point when the professor walked in and introduced himself, and a guy quickly spat out, “Can I use C# in this class?” He then sidetracked the professor with a back and forth about how he preferred the syntax and “well-defined” standards of C# to JavaScript’s “disorganization.” The rest of us just watched. I started to wonder if I should just walk out right then.
But I stuck around to see the syllabus and listen to the first lecture, and I’m glad I did. This was definitely my kind of class.
The first project, due mid-semester, was a game. Specifically, we were tasked with creating a 2D arcade game, similar in gameplay to either Frogger or Space Invaders. I chose Frogger. My game starred a bunny instead (aptly named “Hopper”).
If you love video games and are looking for an opportunity to jump into game development, I highly suggest taking the route that I ended up taking in this class: seek to imitate a game or style of gameplay and attempt to recreate the game features and functionality on your own. I want to share my approach to creating Hopper and some of the programming challenges I faced.
Diving into Unity
Understanding the Terminology & the UI
Unity has specific terminology that I had to familiarize myself with prior to getting started on my game. Here’s a breakdown of some common terms. (Keep in mind, this is all in the context of Unity 5, the latest major version).
- Project = a game
- **Scene**= a game level
- **Game object**= anything that you add to the level, including the camera, light sources, the terrain, the player, and any NPCs (non-player characters)
- **Component**= anything that is attached to an object, including geometry, material, transforms, and scripts
- Prefab = a game object template
- Colliders = a specific type of component that allows you to create a trigger zone around a game object
- MonoDevelop = a built-in development environment that opens any time you want to create and add a new script to a game object
The way game objects and components (particularly colliders) work helped me understand how I should structure my game as I built it. I started breaking down functionality based on where it belonged, relative to its corresponding game object. I highly suggest checking Unity’s detailed documentation for more information on these and other terms: http://docs.unity3d.com/Manual/index.html
Planning My Approach
Sitting down with a blank Unity project is a lot like sitting down with a blank word processor. The emptiness is intimidating. Where do you begin?
To make my Frogger-like game, I wrote and sketched what I knew (basic game information) and then added some goals for myself. In a way, it was a basic project plan. For game functionality, I asked myself, what do I need to accomplish first, second, third? How will I know the game is ready? It also helped me to draw the environment and characters myself (pixel art), but you can easily find assets for terrain and characters for free online or, sometimes, in Unity’s asset store.
Game Name: Hopper
Player Character: a Bunny
Goals:
- Create the environment (pixel art)
- Create the bunny, cars, and logs (pixel art)
- Create the animations for the bunny sprite
- Define the movement patterns for the bunny (player controlled), the cars (two rows going left/right), and the logs (three rows going left/right/left)
- Set the game perimeter.
- Make car and log game objects into prefabs so their clones can spawn on one side of the screen and get destroyed as they leave the other side
- Allow the bunny to walk on the logs and hop from log to log
- Make the cars and the river kill
- Add carrot lives, tracked at the top right of the screen, which the player loses each time she dies
- Add a victory trigger (getting to the end of the level) and a “game over” condition (losing all lives)
Maybe you can spot the tasks that are more difficult, programmatically. If you can’t, that’s okay: for some of these, the level of difficulty wasn’t apparent until I started to code and play around with Unity’s features.
Spawning Cars and Logs
I went back and forth on the best way to go about spawning cars and logs, but I settled on the following:
- Make a “car” and a “log” prefab
- Add empty game objects as “spawn points” (five total: two for the car, three for the log)
- Instantiate car and log clones at timed intervals from each spawn point
- Destroy the car and log clones when they cross the level boundary
I created an empty game object for the singular purpose of being a “game manager.” This empty object had a component–a script–attached to it, which controlled the car and log spawns. I named the empty object’s script game_manager and wrote the following:
// these public variables show up in the Unity’s UI in the game_manager inspector and allow you to assign the log (prefab) to “log_left” and the log spawn point (empty game object) to log_spawn_left. The variable log_spawn_left_rate is also public so that I can change it quickly in the UI.
var log_left : GameObject;
var log_spawn_left : Transform;
var log_spawn_left_rate : float;
...
private var next_car_spawn_left : float = 0f;
...
// this is a built-in function that runs at every frame
function Update () {
var current_time = Time.time;
// the spawn rate of the logs so that they don’t pile up
if (current_time > next_car_spawn_left)
{
next_car_spawn_left = current_time + car_spawn_left_rate;
Instantiate(car_left, car_spawn_left.position, car_spawn_left.rotation);
}
...
}
With this code for each car and log, coupled with placement of the spawn points, I could control where and how fast they spawned.
Once spawned, each car and log also were assigned a speed. This short snippet was assigned to each prefab, which means that each clone that spawned also had it:
var speed : float;
function Update () : void {
var velocity : Vector2 = Vector2.left \* speed \* Time.deltaTime;
transform.position += velocity;
}
To destroy a car or log clone, I added a simple script to an empty game object with a 2D collider component attached, which acted as my level boundary. When another game object touched this boundary and entered the collider’s trigger zone, it was destroyed:
function OnTriggerExit2D(other : Collider2D)
{
Destroy(other.gameObject);
}
The screenshot taken from Unity while the game is running shows logs and cars moving outside of the boundaries of the game. These out-of-bounds objects are not visible during gameplay, but in this special view I can watch them spawn at their origin point off screen, travel across the road over the river, and then get destroyed once they cross a certain threshold, just outside of the gameplay perimeter.
Killing or Saving the Bunny
I needed to define two primary interactions for the bunny: getting hit by a car and hopping on a log. In Unity, these kinds of interactions are called “collisions” and they are triggered when collider components touch.
I added a small rectangular collider to the bunny player character and selected the “Is Trigger” option, which basically told Unity: “if something enters this bubble, it interacts with my bunny.”
I also added collider components and triggers to my car and log prefabs, so, when their clones spawned during gameplay, each clone had it’s own little collider to define its personal bubble.
With my colliders in place, I defined the interaction between the bunny and the cars: the bunny dies.
The code below was attached to the bunny in a script called player_controller
.
function OnTriggerEnter2D(other : Collider2D){
if (other.gameObject.tag == "car"){
Death_Sequence();
}
}
The function applies directly to the bunny–as if to say: “when you collide with an object, do this.” The parameter (other : Collider2D)
passes in any other game object with a 2D collider attached Inside the function, if the “other” game object is tagged as a “car,” Unity initiates my death sequence function (not shown in the code sample).
So this particular interaction was simple enough, right? But what about those logs? Coding this functionality is a little more complicated. Recall that Frogger can hop from log to log to get across the river in the arcade game. Once on a log, he moves with the log; if he moves, his movement is relative to the log’s movement.
There are probably numerous ways to solve the log problem and save the bunny, but I went with what I thought was the easiest.
One of the laws of Unity is that a child object inherits the movement of its parent. So I reasoned that I could dynamically assign the bunny game object as a child of a log game object when the bunny’s collider entered the log’s collider (i.e., when the bunny hopped on a log) and then “de-parent” the bunny when the two colliders separate (i.e., when the bunny hops off the log).
The parent/child trick worked, for the most part, but assigning and unassigning child objects takes several seconds, which is too long. The bunny needs to be able to hop from log to log, which means that the bunny is assigned and unassigned as a child object multiple times in quick succession. To accomplish this, I added a flag to operate as a kind of counter, because incrementing/decrementing a counter was faster to process:
function OnTriggerEnter2D(other : Collider2D){
// river_bounds is an empty game object with a collider that covers the
// entire river, delineating the ~danger zone~
if (other.gameObject.tag == "river_bounds"){
on_river = true;
// if the bunny is in the river but not on a log, bunny dies
if(log_count == 0){
Death_Sequence();
}
}
if (other.gameObject.tag == "log"){
// add to log count when bunny hops on a log
bunny.transform.parent = other.gameObject.transform;
log_count++;
}
// if the bunny leaves the river & hops on the bank,
// she doesn't move with a log anymore
if (other.gameObject.tag == "river_bank"){
on_river = false;
// “de-parents” the bunny
bunny.transform.parent = null;
}
}
function OnTriggerExit2D(other : Collider2D){
// subtract from log count when bunny leaves a log
if (other.gameObject.tag == "log") {
log_count--;
// if the bunny is on the river & not on a log, bunny dies
if (log_count == 0 && on_river == true){
bunny.transform.parent = null;
Death_Sequence();
}
}
}
In the first function, log_count
is incremented every time the bunny enters a log’s collider space, and in the second function, log_count
is decremented when the bunny leaves a log space. It is possible for the log_count
to get up to 2 temporarily if the bunny jumps from log to log, but the decrement/exit function will catch up. And when/if that log_count
reaches 0, the bunny dies.
Again, there are probably several ways to tackle the slowness I encountered with the parent/child movement trick, but using this method was easy to work out and is easy to read.
Why Unity?
The class that I took only offered game scripting with Unity, not another game development platform or engine. However, based on my experience with Unity, I can honestly see some great perks, particularly for someone who is new to game development but understands code and programming fundamentals (like me):
- You see results quickly. Unity’s graphical interface allows you to see your scene come together as you add game components.
- You can easily import your artwork. I was able to upload a sprite sheet I created and then use Unity’s 2D sprite animator to build a character (bunny) that transversed the environment and transitioned to different animation sequences. I didn’t talk about sprite animations in this article, but there are a ton of great video tutorials out there on YouTube.
- The free version of Unity is feature-rich. As I built my 2D arcade game, I didn’t run into any issues with locked features or functionality. The free version seemed perfect for a learner or a hobbyist.
- You will find a lot of documentation and videos, both “official” and from third-parties. There are many, many resources out there on Unity. Just be mindful that Unity 5 came out in 2015, so anything before earlier may suggest code solutions that are considered obsolete. Unity will warn you if you attempt to save code that is considered out of date.
Advantages of 2D
I’m glad that my first assignment in my game scripting class was a 2D game. (And it’s not just because I love pixel art!) Creating a 3D game is much more involved, in every aspect: art and character design, movement, physics, etc.
Moreover, I feel like we have more stringent expectations for 3D games, particularly when it comes to achieving realism for games with human characters. By comparison, some of the best 2D games out there celebrate simple design. If I had been tasked with making a 3D game, I feel like I would have been more anxious to make it look right. I would have focused on trivial cosmetic pieces rather than on actual game functionality.
Of course, if you are up for the challenge of starting out with a 3D game, go for it!
Getting Started
Getting started on your own game is as easy as downloading the latest version of Unity and creating a new project. You could even pick up a sample game from Unity (usually for free in the their Asset Store) to see how a typical project is structured.
Looking for ideas? It helps to pick a well-known game (like Frogger!) and try to recreate it with your own spin.
Good luck!
Jen Downs is a self-taught front-end developer and lifelong video game player living in Austin, TX.