Skip to content

Victory at the Rosetta Stone Game Jam!

Working

David McDonough, Jack Cooke, and I polished off a 1st place win at Rosetta Stone’s first annual 36 hour game jam in Harrisonburg Virginia on Saturday.

The requirements for the game were given out beforehand, and were very open-ended: make a game that “teaches.”  Coming from Firaxis, we decided to make a strategy game, and settled on a “virus” theme.  We liked the idea of a virus strategy game because we could abstract things visually (simple shapes with cool shaders – no characters to animate).  The simulation aspect of the game play could also be very simple, and amounted to little more than three numbers driving a physics-based steering system (like this).

Each of us had some basic knowledge about how vira spread in the body, and we really didn’t go out of our way to do any additional research.  One of Sid Meier’s 10 Rules of Game Design (we’re not really sure which one this is – he changes the order all the time) is to “make the game first, then do the research.”   This may sound a bit strange at first, but it makes sense.  If the game isn’t fun, it doesn’t engage the player and loses the ability to teach.  If the simulation is to detailed or esoteric, the player can’t easily grok it, and  you lose them.  Civilization is a great example of this.  It is built from the assumed historical knowledge of the average gamer (unless you fire up the Civilpedia).  The game doesn’t teach by relaying historical facts, rather it does so by allowing the player to engage in a simulation that, while not historically accurate, illustrates very clearly the dynamics of industry, diplomacy, war, and culture.

The game we ended up with at the end of the day is called Pathogen.  It features game play that is very similar to Phil Hassey’s Galcon with a 3D play surface.  You move your swarm of vira from cell to cell, expanding your population and avoiding autoimmune cells as you go.   Before each level, you load out with three “mutation cards” which give you power-ups as you hit certain population milestones.  The game is very simple, but illustrates well the balancing act a virus must perform when successfully infecting it’s host. Continue reading ›

Tagged , , ,

Cell (Worley) noise in Unity3D

Cell NoiseNoise algorithms are a fantastic thing to have in your 3D graphics bag of tricks.  I can’t tell you how many times Perlin noise has saved my life.  Unfortunately, most good noise algorithms don’t lend themselves particularly well to computation on the GPU.  Enter Cell noise.

Cell noise, otherwise known as Worley noise,  is actually a visualization of a Voronoi diagram: given a random scattering of points, color each pixel on the surface relative to the distance from the closest randomly scattered point.  Cell noise appears all over nature, it looks great, and is easy to compute!  A cell noise function can give us a volume of noise, which we can sample in a fragment shader, giving us seamless patterns on any object – no UV mapping required.  Cool!

Continue reading ›

Tagged , ,

New Jungle Tileset

Really quick before I go to bed.  Here is a screen shot of the new Jungle tileset I’ve been working on.  It’s not finished – still playing around with the colors, and might add some more larger background and foreground elements, but it’s getting there.

JungleTiles

Update

I’ve been doing quite a bit of work on Gaki Gate, but have also been crunching at work (had to get a build to 2K), so I haven’t been updating.

Most of the new stuff is behind the scenes work. I’ve gotten scripted dialogue and NPC AI in there.  Here’s a screens hot of the dialogue system working:

NPC

The scripting system is pretty simple. It just let’s me register functions (cues) which are called after a certain amount of time has passed. This lets me do scripted movement and such. Here’s a code snippet:

package com.willmiller.GakiGate {
	import com.adamatomic.flixel.*;
 
	public class Sequence extends FlxCore implements IAttach {
 
		private var _cues:FlxArray;
		private var _time:Number;
		private var _numComplete:int;
		private var _blockingCue:Cue;
		public var playing:Boolean;
 
		public function Sequence() {
			_cues = new FlxArray();
			_time = 0;
			_numComplete = 0;
			playing = false;
			exists = false;
			active = false;
			visible = false;
			dead = true;
		}
 
		public function getTime():Number {
			return _time;
		}
 
		public function addCue(time:Number, doesBlock:Boolean, startFunc:Function, execFunc:Function):void {
			var cue:Cue = new Cue();
			cue.execute = execFunc;
			cue.start = startFunc;
			cue.executeTime = time;
			cue.time = _time;
			cue.doesBlock = doesBlock;
			cue.complete = false;
			cue.playing = false;
 
			_cues.add(cue);
		}
 
		public function play():void {
			active = true;
			exists = true;
			playing = true;
		}
 
		public function stop():void {
			_time = 0;
			_numComplete = 0;
			exists = false;
			active = false;
			visible = false;
			dead = true;
			playing = false;
		}
 
		public function attach(State:FlxState):void {
			State.add(this);
		}
 
		override public function update():void {
			if (_blockingCue == null) {
				for each (var cue:Cue in _cues) {
 
					cue.time = _time;
 
					if (cue.time >= cue.executeTime && cue.execute != null && !cue.complete) {
 
						if (!cue.playing) {
							cue.playing = true;
							try {
								cue.start(this);
							} catch (error:Error) {
							}
						}
 
						if (cue.doesBlock) {
							_blockingCue = cue;
							_blockingCue.complete = _blockingCue.execute(this);
							if (_blockingCue.complete) {
								_blockingCue = null;
								_numComplete++;
							}
 
							break;
						}
 
						try {
							cue.complete = cue.execute(this);
						} catch (error:Error) {
							cue.complete = true;
						}
 
						if (cue.complete) {
							cue.playing = false;
							_numComplete++;
						}
					}
				}
			} else {
				try {
					_blockingCue.complete = _blockingCue.execute(this);
				} catch (error:Error) {
					_blockingCue.complete = true;
				}
				if (_blockingCue.complete) {
					_blockingCue.playing = false;
					_blockingCue = null;
					_numComplete++;
				}
			}
 
			_time += FlxG.elapsed;
 
			if (_numComplete >= _cues.length) {
				stop();
			}
		}
	}
}
 
class Cue {
	public var doesBlock:Boolean;
	public var executeTime:Number;
	public var time:Number;
	public var execute:Function;
	public var start:Function;
	public var complete:Boolean;
	public var playing:Boolean;
}

That could probably be cleaned up a little, but whatever. It works.

I also did some work on ‘volumes’ – areas which trigger something when Gaki walks into them. If you’ve used Unreal before, you’re probably familiar with this idea (trigger volumes, physics volumes, etc). I’ve created some camera control volumes, as well as fluid volumes that splash and change your physics when you enter them. Here’s the water volume at work:

Water

There will be more coming soon.  I plan on releasing a public build with a some enemies and a sandbox, so that I can get some feedback on the feel of the powerups (jumps, wall jumps, sliding, etc).  Stay tuned for that.  Also, thanks to Sam Posner, who just recently joined the team.  He’s been doing some great work on enemies and art.

New Lava Tileset

Dave and I tag-teamed the tileset for the ‘lava factory’ levels.
GakiLava

I’ve also converted all of Gaki’s logic from spaghetti to a more comprehensible state machine system.  This might look a little strange, but I’m using AS3’s dynamic classes (the State) and anonymous functions to build the state machine, instead of using inheritance.  This seemed a little easier to manage:

public function setupStateMachine():void {
			_stateMachine = new StateMachine(this);
 
			_stateMachine.addState("Idle");
			_stateMachine.addState("Walking");
			_stateMachine.addState("Jumping");
			_stateMachine.addState("Sliding");
			_stateMachine.addState("WallSliding");
			_stateMachine.addState("Dead");
 
			// State machine scoped vars
			var GAKI:Gaki = this;
			var slidingTimer:Number = 0;
			var hasCollidedWithWall:Boolean = false;
			var slidingDirection:Boolean = LEFT;
 
			//------------------------------------------------------------------
			// Idle state
			//------------------------------------------------------------------
			_stateMachine.states["Idle"].enter = function():void {
				Gaki(_stateMachine.owner).acceleration.x = 0;
			}
 
			_stateMachine.states["Idle"].update = function():void {
				// Check if we're jumping while running
				if (FlxG.kRight && FlxG.justPressed(FlxG.A)) {
					GAKI.acceleration.x += GAKI.drag.x;
					_stateMachine.changeState("Jumping");
				} else if (FlxG.kLeft && FlxG.justPressed(FlxG.A)) {
					GAKI.acceleration.x -= GAKI.drag.x;
					_stateMachine.changeState("Jumping");
				}
 
				// Check if we're walking
				if (FlxG.kRight || FlxG.kLeft) {
					_stateMachine.changeState("Walking");
					return;
				}
 
				// Check if we're sliding
				if (GakiG.gameData.hasPants && FlxG.justPressed(FlxG.A) && FlxG.kDown) {
					_stateMachine.changeState("Sliding");
					return;
				}
 
				// Check if we're jumping
				if (FlxG.justPressed(FlxG.A) && !GAKI.velocity.y) {
					_stateMachine.changeState("Jumping");
					return;
				}
 
				// Update our aim
				Gaki(_stateMachine.owner).checkAim();
 
				// Check if we're shooting
				if (FlxG.justPressed(FlxG.B)) {
					Gaki(_stateMachine.owner).doShoot();
					if (Gaki(_stateMachine.owner).up) {
						Gaki(_stateMachine.owner).play("IdleAimUp");
					} else {
						Gaki(_stateMachine.owner).play("IdleAim");
					}
				} else {
					if (Gaki(_stateMachine.owner).up) {
						Gaki(_stateMachine.owner).play("IdleUp");
					} else {
						Gaki(_stateMachine.owner).play("Idle");
					}
				}
			}
 
			_stateMachine.states["Idle"].exit = function():void {
			}

There are of course many more states than that, but you get the idea. The strange thing about anonymous functions in AS3 is there scoping rules. The functions are scoped and can access things defined in the function they were created in, hence the GAKI reference.

First screenshot!

Here you are. Gaki Gate’s first screen shot. The enemies are all ripped from Metroid games, but everything else is original.

CaveEntrance.8.27

Some progress

CaveSpriteSheetDave McDonough and I have been cooking on Gaki Gate. New levels are coming along, and I just finished the sprite sheet for the “grassy cave” areas.

I’ve been experimenting with some different ways to handle actor states. My first approach was a bit unorthodox, and attempted to emulate the built in state handling in UnrealScript using dynamic classes and anonymous functions. This worked, but I had to wrap so much of it in try/catch blocks that it just wasn’t worth it. I’ve reverted back to more traditional state management – a finite state machine implementation that looks something like this.

I also made a cool modification to Flixel to allow me to do overlap tests against FlxTileset instances. This is useful for ‘looking ahead’ of your character to see if you’ll collide with anything soon. The code is very similar to the already existing collides() method in FlxTileset. I have no idea why this wasn’t in there to begin with. Here’s the code:

 // In FlxTileset
override public function overlaps(Core:FlxCore):Boolean
{
	var ix:uint = Math.floor((Core.x - x)/_tileSize);
	var iy:uint = Math.floor((Core.y - y)/_tileSize);
	var iw:uint = Math.ceil(Core.width/_tileSize)+1;
	var ih:uint = Math.ceil(Core.height/_tileSize)+1;
	var c:uint;
	for(var r:uint = 0; r < ih; r++)
	{
		if((r < 0) || (r >= heightInTiles)) continue;
		for(c = 0; c < iw; c++)
		{
			if((c < 0) || (c >= widthInTiles)) continue;
			if(_data[(iy+r)*widthInTiles+ix+c] >= _ci)
			{
				_block.x = x+(ix+c)*_tileSize;
				_block.y = y+(iy+r)*_tileSize;
				if (_block.overlaps(Core))
				{
					return true;
				}
			}
		}
	}
 
	return false;
}

A New Blog and a New Game

Hello all!  Time to start blogging again.

A little background.  I’m Will Miller, game programmer at Firaxis Games, and co-host of the Best Damn Podcast Ever.  I kept a development blog in college, and that was awesome and motivating.  I wish to do the same thing again.

What better way to kick this off than a new game!  GakiGate: The Legend of Tomonobu Itagaki

splashscreen

For those who don’t know who Itagaki is, here’s the Google image search.

Our tale begins when Gaki shows up for work at TECMO TOWER, only to find that he and his staff have been ordered to reduce the breast sizes of all the female characters in his games.  Gaki rushes to the top of TECMO TOWER to find Yasuharu Kakihara, king of TECMO, has been killed, and Brian Crecente of Kotaku fame has assumed the throne.  Gaki is cast out of the building by the super-human Crecente, and falls into the labyrinthine depths below TECMO TOWER, rife with monsters and danger.  Gaki must battle his way out and settle the score with Crecente once and for all!

Continue reading ›

Tagged , ,