tl;dr;


This is a detailed account of the first thirty days of a new game I’m building. I’m hoping this gives you some insight into what it takes to build a game all by yourself. Each day in this developer log has a lesson learned/nugget of wisdom. If you don’t have time to read the entire log, here are the three most important things I’d want you to walk away with (you can read week one’s tl;dr; here):

  1. Getting feedback is better than long/up-front design. Get feedback often. More often than you’re comfortable with. This external pressure of getting feedback will keep you focused on building things end-to-end, something you can actually show off.

  2. Learn trig Euclidean geometry. I thought trig would be used only occasionally. But both Mildly Interesting RTS (MIRTS) and A Noble Circle (frankly any 2D game) will need a good understanding of “finding angles and distances between points”.

  3. Gritty code that kinda works and can be shown off, beats perfect code that hasn’t been written yet. Don’t write perfect code. Don’t worry about the perfect class name, the perfect object hierarchy. Don’t try to find (or worse, write) the perfect game engine, the perfect language. They don’t exist. Stop wasting time navel gazing/trying to find the perfect design pattern. Write code with the purpose of getting your game into someone else’s hands as fast as possible.

Week Two of MIRTS


Almost all of MIRTS was built in 30 days, and was released to the App Store during that time. While reading this developer log, ask yourself if you can build this fast (or if your need to maybe take a step back and try a smaller project). You should really read week one’s write up before reading week two.

Day 8: Get External Feedback Early and Often, Time Doesn’t Equal Value


You will eventually become numb to everything about your game. Difficult game mechanics will become second nature. Difficult levels will be a breeze to beat. Parts that are actually fun in your game will feel boring and dull. All the “new” will fade.

But we’ll talk about that later. For now, let’s talk about assumptions.

Some of the assumptions you’ve made about your game’s “really awesome” mechanics will be completely wrong. After Day 7, I found that I had exactly this problem.

This:

Became this:

Originally, I thought that having non-capture-able nodes in between control points would add a concept of “blocking”. During play testing it turned out that both of us went directly to control points as opposed to setting up any kind of “front-line” units to block incoming enemies. This concept of blocking was going to be a core part of the game mechanics. After play testing, it turned out that the concept (and the code that went with it), wasn’t very good (I didn’t even use that mechanic to win). If I hadn’t play tested early and gotten feedback, I probably would have kept going down the (bad) rabbit hole of “blocking”. So. Get external feedback often, and reflect objectively. Some ideas may have seemed great in your head, but turn out being stupid when actually implemented (don’t hang onto these kind of “good ideas” for too long).

After the change above, the map became a little bare. I thought I’d take the rest of the day by adding some sort of “cool” sprites to the game (such as ships battling against each other).

I’m moderately above average at drawing. Here is a sample of what I can do (a character for Disgaea 5):

And to continue tooting my own horn:

Turns out, this skill didn’t translate well to super tiny low fidelity sprites (it did work out well enough in A Noble Circle):

Here is a high fidelity sprite I sketched out:

Here is what it looked like in-game:

I decided to revert this change when all was said and done, and stuck with the simple sprites I already had. I spent a few hours doing this, but remember (as with the work put into the “blocking” game mechanic), time invested doesn’t translate into value/worth. “Working really hard” on something doesn’t mean you should get rewarded for it (if the end product isn’t any good).

Day 9: Concentrate on Incorporating Feedback While It’s Still Fresh


Quite a few things came out during play testing. Again, these were things that I thought were not an issue (good thing I got early feedback):

All these (except for the last bullet point) were fixed during this day while it was still fresh in my head.

Here is an example of how utterly broken the DPS thing was. Here is 10 minutes of game play (sped up). You’ll notice that red never loses his final base:

Day 10: Concentrate on Incorporating Feedback While It’s Still Fresh (Again)


Initial play testing showed that the game was completely reactive as opposed to strategic. You can go to any node at any point in time (plus you could see what the enemy was doing). I needed to fix this. Here is all the stuff that was worked on today:

Aside - cool math stuff. The shaking logic was more usage of that orbit logic. Here is what the code looks like:

new_x = quadrant_center.x + Math.cos(unit_angle) * orbit_distance
new_y = quadrant_center.y + Math.sin(unit_angle) * orbit_distance
sprite.position = CGPointMake(new_x, new_y)

if unit_is_charging_warp
  if orbit_distance < 0
    orbit_distance += orbit_growth_speed * 2

    if orbit_distance > 0
      orbit_distance -= orbit_growth_speed
    end
  elsif orbit_distance > 0
    orbit_distance -= orbit_growth_speed * 2

    if orbit_distance < 0
      orbit_distance += orbit_growth_speed
    end
  end
end

In essence, if the unit is “charging up to move”, I change the orbiting distance around the center every frame (at 60 fps, it creates a really jarring shake).

Here are what all these changes look like in action:

Day 11: Get Good at Trigonometry Euclidean Geometry


Edit:

It has been brought to my attention that the following math problem can also be solved with a good understanding of Euclidean Geometry. Generally speaking, the more math you know, the better. Unfortunately, general terms like that lead to paralysis in the form of “where the hell do I start”? So a more specific starting point would be:

From there, you can expand your math knowledge.

End Edit.

During the play testing, I found that my taste for “strategy” was lacking (you can move from any node to any other node). Additionally, the fog of war was a bit too… foggy. I decided to add the concept of “edges” to the map so that you could see units in connected/adjacent nodes (but not nodes “behind your front-line”. Drawing the edges required math (gasp).

Here is a math problem. Try to solve it:

Given the location of two points, draw a line between those two points. But make the line start and end a distance of d from the center of said points.

Here is what the end result should look like:

Every time I think that trig is accidental/one-off, I get another visual representation problem that requires me to draw an invisible triangle. So I think at this point it’s safe to say that getting a solid understanding of trig will help you build games (2D ones at least).

Let’s dig in to some math and data structures!

Here is what my map looks like in code (remember everything is granular to an x, y position on an 84 point coordinate system):

def the_map
  [
    [[0,  0],  [3,  0]],
    [[3,  0],  [6,  0]],
    [[3,  0],  [1,  2]],
    [[3,  0],  [4,  2]],
    [[1,  2],  [0,  4]],
    [[1,  2],  [3,  5]],
    [[3,  5],  [4,  2]],
    [[1,  2],  [4,  2]],
    [[0,  4],  [3,  5]],
    [[4,  2],  [6,  6]],
    [[3,  5],  [6,  6]],
    [[0,  4],  [2,  8]],
    [[2,  8],  [3,  5]],
    [[5,  8],  [6,  6]],
    [[5,  8],  [3,  5]],
    [[2,  8],  [5,  8]],
    [[2,  8],  [3, 10]],
    [[3, 10],  [0, 10]],
    [[3, 10],  [6, 10]],
    [[3, 10],  [5,  8]]
  ]
end

This is a pretty brittle, imperfect representation of the map but it works dammit (redesign of this data structure will be covered later). Given the map above, I can get pixels-on-device using the following formula:

def quadrant_position_in_pixels x, y
  quadrant_size = device_screen_width.fdiv(game.width)

  CGPointMake x * quadrant_size + quadrant_size.fdiv(2),
              y * quadrant_size + quadrant_size.fdiv(2) + ARBITRARY_OFFSET
end

For each edge on the map, I do the following math. My fancy method takes in two parameters, the edge tuple and the distance_from_center:

def draw_edge tuple, distance_from_center

Given these values, I can get the exact pixel locations:

edge_1 = tuple[0]
edge_2 = tuple[1]
point_1 = quadrant_position_in_pixels edge_1[0], edge_1[1]
point_2 = quadrant_position_in_pixels edge_2[0], edge_2[1]

I can then get the angle between those two points by calculating the arctan:

adjacent = point_2.x - point_1.x
opposite = point_2.y - point_1.y

angle = Math.atan(opposite.fdiv(adjacent))

With the angle, I can then figure out how far from the center the line should start and end (in pixels):

 dx = (distance_from_center * Math.cos(angle))
 dy = (distance_from_center * Math.sin(angle))

I then need to figure out where to start the line, this is dependent on whether point_1, is the the left or right of point_2:

if point_1.x < point_2.x
  start_line = [point_1.x + dx, point_1.y + dy]
  end_line = [point_2.x - dx, point_2.y - dy]
else
  start_line = [point_1.x - dx, point_1.y - dy]
  end_line = [point_2.x + dx, point_2.y + dy]
end

After I get all the arguments, I use SpriteKit’s terrible, unintuitive API to draw the line.

Aside: I’d like to rant about something real quick. It really doesn’t matter what game engine you use, just don’t build one from scratch. In my experience, the best engines, frameworks, libraries are extracted from production code, not built up front. So build and ship your game first. Then build another game and ship that. Then look at what’s common between the two games and extract that. What you extract is your game engine. Don’t be the guy that spends months building a game engine without ever building a game. I don’t care if you want to open source it and let others use it. If you didn’t extract that engine, it’s viability is suspect.

Seriously, don’t be this guy. End rant.

Here is the SpriteKit code:

line = SKShapeNode.new
pathToDraw = CGPathCreateMutable()

CGPathMoveToPoint(pathToDraw, nil, *start_line)
CGPathAddLineToPoint(pathToDraw, nil, *end_line)

line.path = pathToDraw
line.setStrokeColor(UIColor.grayColor)
scene.addChild(line)

Here is what this new map looks like:

Day 12: The Ebb and Flow of Development


All software development has an ebb and flow. Here is mine:

My vertical cut was “edges and fog of war” (with a broader goal of making MIRTS more strategic, as opposed to reactive).

My terrible code was an extremely primitive, imperfect, gritty data structure that represented a map. It was just an array of arrays of arrays.

I didn’t build my own 2D engine. I used SpriteKit’s terrible API to draw the edges. Here’s what pretty looks like (if you can’t build what’s below in 12 days, you have no business building a game engine… don’t be that guy).

More math. Better usability (optimizations for touch devices).

The map above is inspired by a Starcraft map called Lost Temple (remember Everything is a Remix).

Here was the trig I used to make the arrows point in the right direction. Learn trig:

def pi
  3.14159
end

def arrow_direction arrow_button, from_quadrant_x, from_quadrant_y
  pos = quadrant_position from_quadrant_x, from_quadrant_y, sprite_size
  if pos.y.round == arrow_button.sprite.position.y.round
    if pos.x.round <= arrow_button.sprite.position.x.round
      -pi.fdiv(2)
    else
      pi.fdiv(2)
    end
  elsif pos.y < arrow_button.sprite.position.y
    arrow_button.original_angle
  elsif pos.y > arrow_button.sprite.position.y
    arrow_button.original_angle + pi
  end
end

Day 13: The Meditative Aspect of Cleaning up Code


I mentioned the ebb and flow that I followed in the previous section. I’m on my absolute favorite step: code cleanup.

Pro tip:

Whenever you have a mental/creative road block, are sleepy, or are just burned out on new feature development, read the code that you’ve written and see if it makes sense. Make notes about what you don’t understand under in that “less optimal” state of mind. Then, when you’re refreshed, go back to those places in the code and fix them.

There are days where I just don’t feel like coding. But habit and discipline is what builds games, not passion and motivation. So on those days where you just don’t have the mental strength to build something new, read through the old stuff you’ve written and see if any of it makes sense. This is a great way to have a “relaxing” day, but still work on code.

That’s what I did on day 13. Here is a lines of code breakdown of all the things in the project:

Aside: Ruby (specifically RubyMotion) is an incredibly terse language (relative to C++, Objective C, Java, C#). This would be quite a bit more lines (double I’d say) if I used a C inspired language. Use what you’re comfortable with of course. Just be aware that I’m benefiting from using a dynamic language (I lose type safety, but gain expressiveness). Do not start a language war over this statement please. This aside is merely a hat tip to the framework I use to build my games in.

Day 14: Play Testing and Failed Assumptions


I bought my friend lunch for the favor he was doing me (again). We played the game. He didn’t find it fun at all. The reason? Fog of war.

I thought it was obvious than an RTS should have fog of war. Every RTS I’ve ever played had this. But with the addition of fog of war into MIRTS, the game became terrible.

This is why it’s important to play test often. You never know what assumptions you’ve made are incorrect. I spent some time commenting out all of the fog of war code. We played again. It was fun again.

The addition of edges and the slower game speed served the goal of “strategic over reactionary” (even withougt fog of war). Mission accomplished.

Keep an eye out for week three.



Subscribe to get high signal, low noise entries with regards to software development, doing what you love, and never being content with where you are.

Also, I've spent the three years fighting the uphill battle of getting noticed in the App Store. I've decided to write some words about it in book format.

powered by MailChimp

24 February 2017