Iridescence development visualized

Three name changes, three frameworks, and 33,697 lines of code and data — this video represents 11 months of nearly solo development at the rate of two days per second.

Each dot is a file, and each cluster represents a folder. Green lines represent new files being created, yellow for modifications, and red for deletions. Source code settles out at the bottom left, with level files top right. Audio is up on top near the levels, with an equal number of .oggs and .mp3s to support multiple platforms.

Towards the end it’s obvious that a lot of work is being done on level design, with scattered changes to source code as I fixed last-minute bugs, and a lot of new image files being added as I rounded up screenshots and promotional art.

Created with Gource. Music is “Hongdae” by Lukhash.

Get the game:

Level count isn’t everything

I’m quickly approaching gold status on Iridescence, and with all foreseeable technical work finished, I’m deep in the throes of level design. Originally I had planned to ship with 100 levels, but today I decided to cut that in half and target 50 instead.

Why am I doing this?

I’ve been stuck in this stage of development for a while, actually. Slide had 16 levels when I released it, and I was intent on having significantly more for Iridescence, being as it’s going to be a commercial release and I want people to feel they’ve gotten their money’s worth. 100 seemed like a nice round number and I assumed that reaching it would be fairly straightforward.

This is the part where I was wrong. Designing puzzles is actually really hard. You want them to be challenging but still fun, subversive but not unfair, and most importantly they have to mesh with one another as a cohesive whole. On top of all that, the puzzle designer has to learn to work backwards from an interesting solution to a starting state that doesn’t make the goal obvious.

The whole process is very creatively-bound, and it’s impossible (at least for me) to sit to down and just crank out new levels up to a set quota. It’s been hard to keep a steady pace, and the constant awareness of how much more work I have in store hasn’t helped.

While considering all this, I realized that I was coming at this whole process from the wrong direction. In all my favorite puzzle games, the number of levels is irrelevant; the game goes on until it runs out of meaningful things to do, and then it stops. My number one goal in Iridescence’s design is that each level provide some way of stretching the player’s mind; whether that’s by introducing a new system, or subverting assumptions to cause misdirection. This is in direct conflict with having to fill a set number of levels. Some puzzle games are based on a small set of mechanics which are then used in levels that are more about going through the motions than finding something new each time.  There’s nothing inherently wrong with that, of course, but it goes against the philosophy I’ve been building Iridescence around.

In the end, I’d rather make a concise game that does what it sets out to do and nothing more, than a long game that’s full of repetition and filler.

Stuck Scripting overhaul

Early on in the development of Slide (before Iridescence had come to be), I added support for small script files to allow levels to contain more complicated puzzles without running into false positives that caused the reset button to show up while the level was still perfectly solvable. I was using C# at the time, and options for proper scripting languages on the .NET platform are shamefully limited, so I rolled my own.

Here’s a sample of what it looked like:

R > B;
G > B;
B > G ! B -> [B];
B > R ! B -> [B];

The use-case for stuck scripts is extremely specific, so the language itself didn’t need too many bells and whistles. All I needed to do at any time was compare the number of game objects of a given color, and check if pawns had a valid path to their targets. The snippet above basically translates like so:

The puzzle is unsolvable if any of these are true:

there are more red pawns than blue
there are more green pawns than blue
there are more blue pawns than green, unless all blue pawns can reach a target
there are more blue pawns than red, unless all blue pawns can reach a target

Needless to say, it’s as ugly as it is functional, and adding new features proved difficult. The inflexibility of the original design meant for some truly impressive hijinks as I tried to implement more complex behaviors with only the original syntax.

Today I stripped the whole system out and replaced it with a new one. Since I’m using Haxe for this remake, I was able to take advantage of hscript, a subset of Haxe that allows for runtime interpretation. Now, the script above looks like this:

var reds = count(Pawns.Red);
var greens = count(Pawns.Green);
var blues = count(Pawns.Blue);

if (reds > blues) fail();
if (greens > blues) fail();

if (blues > greens && !hasPathToExit(Pawns.Blue))
	fail();

if (blues > reds && !hasPathToExit(Pawns.Blue))
	fail();

Much better.

The original scripting system was designed as a black box, with a minimal number of inputs and outputs. By keeping the interface consistent between implementations, I was able to completely change the underlying code without changing any other aspect of the program. It’s nice to be able to change something so integral to the gameplay without worrying about side effects.

Menu work

This week in development on Iridescence I’ve been focusing on menus, settings, and save data. Here’s what I’ve implemented as feedback when the player tries to open a level that hasn’t been unlocked yet. The animation is inspired by WordPress’ login window, and I think it’s pretty clear what’s going on.

Haxe macros are amazing

Here’s a neat trick I discovered today while working on Iridescence.

Haxe has native macros that are very different from those found in C; rather than using a separate pre-processing language, Haxe macros are simply snippets of Haxe code that are run at compile time instead of run time.

In native builds, Iridescence populates its soundtrack dynamically by searching for all song files in the /assets/music folder. On more restrictive platforms like Flash, all assets have to be known at compile time so they can be embedded in the SWF, which meant I needed to create a list of files manually and update it whenever I added a new song. I wrote this little macro to iterate through the music folder at compile time and build a list of song files automatically.

macro public static function getSongList(extension:String)
{
	var songs = FileSystem.readDirectory("assets/music")
		.filter(function(s) return s.endsWith(extension))
		.map(function(s) return "assets/music/" + s)
		.array();

	return Context.makeExpr(songs, Context.currentPos());
}

Color/Shift input woes

When I released the Color/Shift demo, one of the main issues that were being reported was that dragging the pawns around was really difficult. This confused me as I had spent a lot of time painstakingly tweaking the controls so that sliding pieces felt natural and responsive, but it was obvious by watching people play that there was something seriously wrong. I made some changes to try to make it better, but the result was still pretty bad. The pawns would slide around loosely in any direction they wanted until crossing a grid line, at which point they would snap to an axis and move along it, It didn’t feel good, and it introduced all kinds of new problems including the possibility of phasing through objects or traveling in two directions at once. Worst of all, it still didn’t address the issue entirely.

Dwm 2013-11-11 10-21-38-83

Yuck. 🙁

I let it be and moved on to other things, planning to come back to fix it later. There was probably a little bit of hubris involved, if I’m being completely honest with myself; if I didn’t have a problem controlling the game, other people shouldn’t either, right?

A few days after leaving the issue behind, I was working on my laptop (most of Color/Shift’s development has been done on my desktop computer) and suddenly started having the same problem as my testers. Pawns were moving sideways when I wanted to move up, and sometimes they wouldn’t even move visibly before smacking into a wall to either side. What was going on?

As best as I can figure, the input issues had to do with the sensitivity of the mouse being used for control. My desktop has a high DPI gaming mouse with the sensitivity cranked way up, and my wireless mouse and laptop trackpad are much less precise. Armed with this new information, I set about making things right.

angle0

Here’s a visualization of the way I’m handling input now. When the user presses the mouse button, the pawn remembers where the pointer was when it was pressed. In the image above, it’s right in the center of the piece.

At this point, no dragging is actually done yet. The mouse must move 7px in any direction before the pawn will move at all; this is represented by the circle cutout at the center of the transparent fans.

When the mouse has moved far enough from its original position, its angle to that position is checked. If the angle is within 30° of an axial direction, the pawn is then allowed to move on that axis. If not, no movements are made.

angle1

The angle is relative to the mouse click position, so it’s possible to start the drag by clicking anywhere.,

I still need to stress-test this to make sure it works for everyone, but it feels much better with all of my mouse devices and I have yet to move a piece in a direction I didn’t intend since improving this mechanic. Feedback is important! Listen to your testers!