who moved my cheese-y avatar?

My past games all used a first-person camera. For the next one, I’m using third person because I think it will help the player better understand who they are in the game world.

Yes, you’re a beach toy with wings. My apologies.

When I switched to the third person, the first thing I noticed was how jittery all the model motion became. I’d drag the mouse across the screen to change the yaw and all of my models would shake as if they were trying out a new dance step.

I traced the cause to how I was handling mouse events and camera rotation. My original first-person code established a mouse move event handler, which rotated the camera directly.

mouse: {
	x: 0,
	y: 0,
	down: false
},

onMouseMove: function(event) {
	var dx, dy;

	// if the left mouse button is down (i.e., mouse is dragging)
	if (mouse.down) {
		// generate mouse deltas
		dx = SPIN_RATE * (event.pageX - mouse.x);
		dy = SPIN_RATE * (event.pageY - mouse.y);
		// rotate camera yaw by dx and pitch by dy
		camera.turn(dx, dy);
	}
	// store off last mouse position
	mouse.x = event.pageX;
	mouse.y = event.pageY;
	return false;
}

Note that the degree of camera rotation is determined solely by the change in the mouse position. There’s no attempt to synchronize the rotation to the animation timings. This seemed to work okay in first person, but third person perspective reveals its flaws–very likely because now I have a fixed object sitting in front of the camera to compare the rest of the scene to.

The new code splits things up a little. Now, the mouse move handler only tracks changes to the mouse position.

mouse: {
	down: false,
	last: {
		x: 0,
		y: 0
	},
	next: {
		x: 0,
		y: 0
	}
},

onMouseMove: function(event) {
	// if left mouse button is down (i.e., mouse is dragging)
	if (mouse.down) {
		// store off "new" mouse position
		mouse.next.x = event.pageX;
		mouse.next.y = event.pageY;
	}
	return false;
}

In the player update function–which is called on every animation frame–I actually rotate the camera.

update: function() {
	var dt, dx, dy;

	// calculate number of seconds elapsed since last animation frame
	dt = SOAR.interval * 0.001;
	// calculate mouse deltas based on position AND time interval
	dx = 0.5 * dt * (mouse.next.x - mouse.last.x);
	dy = 0.5 * dt * (mouse.next.y - mouse.last.y);
	// rotate the camera
	camera.turn(dx, dy);
	// store off "new" position as "old" position
	mouse.last.x = mouse.next.x;
	mouse.last.y = mouse.next.y;

	... player position update ...

}

Camera rotation is much smoother. It’s still slightly clunky, as I’m computing deltas based on integer mouse positions rather than receiving them directly from the mouse driver. Hopefully, Mozilla’s Pointer Lock API will enter general usage before long.

how to thicken a tumbleweed (in space)

So, I’m working on a new game, and it takes place in an “open” world for a change. No more underground ruins, moon caves, or pastel factories for this cat.

We’re hanging some hundreds of kilometers above a gas giant world here. (Don’t worry, we can float.) To one side, we have a space tumbleweed.

I generate the weed using Brownian motion. I have a point and a direction vector. Inside a loop, I add the direction vector to the point, then apply a slight random error to the direction vector, and normalize it to keep the distance constant between iterations. I also constrain the point to remain within a spherical radius, and maintain a direction vector at right angles to the direction of motion. Some rough code is below.

// p is our point undergoing Brownian motion
var p = SOAR.vector.create(24, 0, 0);
// f is our direction vector
var f = SOAR.vector.create(0, 0, -1);
// r is a right-hand vector for generating the strip
var r = SOAR.vector.create(-1, 0, 0);
// d is a scratch vector
var d = SOAR.vector.create();

for (i = 0; i < 30000; i++) {

 	// this is a triangle strip, so generate two points on either side of the point
 	this.mesh.set(p.x - 0.05 * r.x, p.y - 0.05 * r.y, p.z - 0.05 * r.z, i % 2, 0);
 	this.mesh.set(p.x + 0.05 * r.x, p.y + 0.05 * r.y, p.z + 0.05 * r.z, i % 2, 1);

 	// move the point
 	p.x += f.x;
 	p.y += f.y;
 	p.z += f.z;

 	// add a random error to the direction
	f.x += 0.5 * (Math.random() - Math.random());
 	f.y += 0.5 * (Math.random() - Math.random());
 	f.z += 0.5 * (Math.random() - Math.random());
 	f.norm();

 	// add a smaller random error to the right angle vector
 	r.x += 0.01 * (Math.random() - Math.random());
 	r.y += 0.01 * (Math.random() - Math.random());
 	r.z += 0.01 * (Math.random() - Math.random());

 	// this is a trashy, hacky way of insuring that the
 	// right angle vector remains at right angles to the
 	// direction vector.
 	r.cross(f).cross(f).neg().norm();

 	// if the point strays outside the sphere, generate a vector
 	// that points back toward the center of the sphere, and add
 	// it to the direction vector. the point's path will gently
 	// curve back around.

 	if (p.length() > this.MAX_RADIUS) {
		d.copy(p).norm().neg().mul(0.5);
		f.add(d);
	}
}

However, if I draw the mesh, I get a rather anemic-looking thing.

Now, I could increase the number of iterations, and that’s what I tried at first, but it’s a problem for two reasons. First, I want to be able to generate this in real time when the game starts up, so too many iterations means the web page will be unresponsive for more than a few seconds. Not good. The second issue is the sheer size of the object in video memory. Running in a web browser is lovely, but it keeps me mindful of the fact that I’m sharing resources with loads of other programs. I want to keep GL objects as small as possible.

There’s also a slight aesthetic problem. It’s a random tangle of stuff. I’d prefer that it look more like a product of Nature and less like a discarded shoelace.

Enter symmetry. I generate my anemic tangle as specified, but at display time, I rotate the mesh through eight successive angles and draw each one.

// rotor is just a wrapper around a quaternion.
// see https://github.com/wordsaretoys/soar/blob/master/rotator.js
// and check out the freeRotor object
this.rotor.rotation.set(0, 0, 0, 1);
this.rotor.turn(0, 0, 0);

// SYMMETRY is set to 8
for (i = 0, r = SOAR.PIMUL2 / this.SYMMETRY; i < this.SYMMETRY; i++) {
	gl.uniformMatrix4fv(shader.rotations, false, this.rotor.matrix.rotations);
	this.mesh.draw();
	this.rotor.turn(0, r, 0);
}

The vertex shader only has to multiply one more matrix, and GPUs are great at that kind of stuff.

attribute vec3 position;
attribute vec2 texturec;

uniform mat4 projector;
uniform mat4 modelview;
uniform mat4 rotations;

varying vec2 uv;

void main(void) {
	gl_Position = projector * modelview * rotations * vec4(position, 1.0);
	uv = texturec;
}

Now, I have a thick tumbleweed. There’s a performance penalty, of course, but it doesn’t seem to have affected my frame rate. As a bonus, some lovely patterns arise from the chaos.

Symmetries occur all over the natural world, and they’re a useful way of giving the appearance of structure. (Not to mention the appearance of doing more work than you actually did.)

random thoughts on the last thing I did

Easy Does It either took two months or one to complete, depending on whether you count the time spent thumb-twiddling, waiting for an idea to take root. All I knew at the start was that it would take place in a cave (which was ultimately translated to “an underground ruin”). If you trawl through my blog archives you’ll also find a load of posts concerning googly-eyed NPCs called “paddlers” that never made it into the game. (Not this one, at least.) Some ideas take. Some don’t. I never know which is which until it’s all over.

Every time I had an idea, I drew up a design document for it. I found these useful, not as requirements or contracts, but as arguments. Poor arguments tended to vanish.

I wish I’d started writing the script earlier. I left it for the last week of development, and wound up with a set of one-liners that start repeating early on. Does it matter to the game? Possibly. If I’d started earlier, I could have given each ghost a history and a story to tell, or at least something original to say.

Originally, the game ended in one of three ways. You accumulated enough will to leave the ruins on your own, or enough luck to find some powerful artifact that Easy had missed, or enough money to set yourself up for a while. On the last day of development, I decided this didn’t really work for me, because it didn’t address Easy, and it seemed so oddly triumphant for a game that’s all about the little people of the RPG world. So, that’s why the ending is what it is.

easy does it

If you’ve ever wanted to spend some time creeping through a vast underground ruin in search of dead people to argue with, have I got a game for you.

I explained the general idea behind Easy Does It in the post just below this one, but to reiterate: the game was inspired by my Skyrim experience of running into ruins to kill people and take their treasure. I could just imagine them lying there dead wondering, what the hell was that all about? And I could just as easily imagine hiring some dope to come in after me and tidy the place up. You know, burn the bodies, clean up the broken furniture, make sure the ghosts (and their gods) don’t harbor any grudges.

You, my friend, may just be the dope hero I’ve been looking for.

Enjoy.

easy does it: a first look

My latest game Easy Does It is a dungeon crawl. Sort of. With a twist. Here’s the blurb:

“Easy, the fabled adventurer, leaves a path of devastation through a cavern as he crushes his enemies and steals their treasures. He’s got no time to make amends; that’s your job. Calm the ghosts of his victims, dispose of their bodies, appease the gods—and make a little money. Very little money.”

I came up with the idea after a Skyrim play-through. I spent a lot of time charging into caves, killing anyone who happened to live there, and taking their stuff. Skyrim justifies this by having nearly every cave-dwelling NPC attack you on sight, but it still feels wrong. Where’s the button for oh hi, I just wandered in, nice décor, I’ll see myself out? Mm, can’t find it. Guess I have to kill again.

After a while, I felt like some kind of medieval sociopath. It’s not hard to start seeing every protagonist in every RPG in that light.

In Easy Does It, you follow one such remorseless killer, like a pilot fish hanging around a shark hoping to catch a few bits of food. Easy tends to drop a few moldy coins here and there, possibly by mistake.

He also leaves the bodies of those he’s slaughtered laying around.

He drapes a cloth over them because it’s easier than modeling a full human form. (Okay, it’s a bit of a cheat.) Your job is to cremate the bodies, which hides the evidence of his crimes.

However, before you can deal with the bodies, you’ve got to deal with the ghosts.

You have no weapons beyond words. Make excuses, flatter their misty forms, do a little victim-blaming, whatever. Get them to stop whispering damnation to you before you lose your nerve and flee the cave. Once a ghost has broken off, you can incinerate the body, which also releases the ghost into the afterlife. Job well done, and on to the next bend of the cave.

There’s a map key in case you get lost, though each section of the cave is pretty small. You have to collect wood (from smashed treasure chests) and spilled lamp oil to cremate bodies, and if you don’t have enough, you’ll have to leave the ghost wandering the cave forever. The gods don’t look kindly on this. A “grace bar” tells you how you fare in the divine balance. Go high enough and you’re free of Easy forever. Go low enough—and you enter a different sort of eternity.

The game is browser-based, free to play, and should be available in early April. Stay tuned.

I love my brick

Had a bit of an oh, shit moment late last night. I’ve been having problems with my PC not coming out of sleep mode properly. My motherboard, an Asus M2A-VM, seemed to be the culprit, so I decided to upgrade its BIOS.

Yeah, that’s usually the beginning of a good oh, shit PC story.

The upgrade process bricked my PC. No display, no drive activity, not one single, solitary beep from the POST. I had to have done something wrong. More to the point, I had to undo it.

Now, modern Asus motherboards have this “crash-free” BIOS thing where you’re allowed a do-over after a failed upgrade. Put a known good BIOS image on a drive and boot the PC, and some lingering ghost of sanity on the motherboard will pull it down and re-flash the BIOS. I found a new image on the Internet, as it wasn’t on the recovery disk where it was supposed to be.

I haven’t had a floppy drive in almost a decade, but I tried putting the image on a USB drive, which didn’t do anything, then burning it to a DVD, which also did nothing. A light came on the DVD, but that was all.

Frantic, I scoured the Internet for answers, and came across a HardOCP thread I can’t link to because it was on another PC and it was late and I’ve forgotten how the hell I found the thread in the first place. But the gist of the thread was, if you’re upgrading an Asus motherboard and it fails, try pulling out the memory and replacing it with one single stick.

What the hell? I had nothing to lose except more sleep. I yanked out all the RAM, put in a single 512MB stick, and it fucking started right up.

I’m fuzzy on the details, but it has to do with memory voltages, which are settable on Asus motherboards. Most BIOSes default this to Auto, which is fine until an upgrade changes what the default voltage actually is. To support four sticks of RAM, I had to lock it to 2.1V instead of whatever low-ass default the upgraded BIOS thought it should be. What I found funny was that the upgrade didn’t actually fail, it succeeded, and that was the problem. If it had failed, it probably would have been easier to diagnose and fix.

So, that’s what I did last night instead of sleeping. Still, it could have been worse.

Did I ever mention how much I love the Internet?

the good, the bad, and the tragic

I’ve spent the last few days reworking how I generate caves. That canvas-based map object I mentioned in the last entry really helped me focus on the algorithms, rather than the mechanics of getting data into the map. I’m able to create distinct structures by drunk-walking around a set of randomly-chosen points.

I create each path by making incremental steps and doing a fillRect at each point to plot a small square with a miniscule alpha.

// closure function for drawing a meandering path
function drawPath(x, y, tx, ty) {
	var dx, dy, d;
	do {

		dx = rng.get() - rng.get();
		dy = rng.get() - rng.get();
		d = Math.sqrt(dx * dx + dy * dy);
		x += 0.5 * dx / d;
		y += 0.5 * dy / d;

		dx = tx - x;
		dy = ty - y;
		d = Math.sqrt(dx * dx + dy * dy);
		x += 0.05 * dx / d;
		y += 0.05 * dy / d;

		x = SOAR.clamp(x, 0, l);
		y = SOAR.clamp(y, 0, l);

		map.context.fillRect(x - 2, y - 2, 4, 4);
	} while (Math.abs(dx) > 1 || Math.abs(dy) > 1);
}

...

// generate paths
map.context.fillStyle = "rgba(255, 0, 0, 0.05)";
for	(i = 1, il = this.area.length; i < il; i++) {
	drawPath(this.area[i - 1].x, this.area[i - 1].y,
		this.area[i].x, this.area[i].y);
}

Premultiplied alpha insures that I’ll get a nice cumulative effect as the rectangles overlap each other. Instead of the sheer walls I often get with noise algorithms, I can get tunnels, and other natural-looking structures.

The canvas performs alpha blending and color accumulation much faster than if I tried to do it in raw Javascript. It’s a win-win, except when something like this happens.

Oh, dear. Good luck getting through that.

I’m still trying to figure out how to prevent this. A rare occurance, but it only has to happen once to bring a game to a halt. If I make the alpha too large, I wind up with dull flat cave floors instead of exciting curvy tunnels. If I make the rectangle size too big, I’ll get vast open spaces with little suspense about what’s around the next corner.

And this in fact leads to the sole issue I have with using the canvas to generate maps. I have to call getImageData to see the actual pixel data, and the object it returns doesn’t track canvas changes, so if I wanted to use it to fix paths I’d have to generate additional data objects. Wasteful!

Naturally, I could operate on the data object itself, checking and correcting each path by flipping its bits, though that makes me wonder why I’m using the canvas in the first place. I will exhaust other options first.

UPDATE: Worked out a nice solution. In the path drawing loop, I draw a filled circle with a heavier alpha (0.25) and a radius of 2 every time the distance between the current (x, y) and the last time I drew my circle exceeds 1. It looks like this.

dx = lx - x;
dy = ly - y;
d = Math.sqrt(dx * dx + dy * dy);
if (d > 1) {
	f = map.context.fillStyle;
	map.context.fillStyle = "rgba(255, 0, 0, 0.25)";
	map.context.beginPath();
	map.context.arc(x, y, 2, 0, SOAR.PIMUL2, false);
	map.context.fill();
	map.context.fillStyle = f;
	lx = x;
	ly = y;
}

Stick that in just after the start of the do..while loop. It insures that the path between points is always open just enough to squeeze through.

the healer’s cave (closed for renovations)

 I’ve created a new object that should be going into Soar soon. It’s a twist on the Noise2D object I use for generating heightmaps. Instead of interpolating across an array of random numbers, I interpolate across a Canvas.

Why is this a big deal? Well, I tend to use the Noise2D object to generate caves–the setting for my current game, as a matter of fact–and I wanted to generate a cave as a set of chambers. I started by creating a Noise2D object, then modifying the underlying source array to generate specific features. Want a wall? Change a couple of cells of the array to 1. How about an opening in a wall? Set a cell to 0.

Soon, I was trying to implement the whole map this way, and thinking: it would be great if I could just use drawing functions to do this. Need a room? Draw a rectangle, or a circle. A passage? Draw a line.

Then I remembered that Canvas has (a) drawing functions and (b) getImageData which allows you to retrieve whatever you’ve drawn as a bitmap. An hour later, I had my new Canvasser object all coded up.

/**
	provides an interpolated 2D function over a canvas

	adapted from the 2D noise function. draw over the
	context as usual, call the map() function to make
	a bitmap of the canvas, then call get() with the
	channel number.

	@namespace EASY
	@class canvasser
**/

EASY.canvasser = {

	RED: 0,
	GREEN: 1,
	BLUE: 2,

	/**
		create the canvasser object

		@method create
		@param ampl maximum amplitude of function
		@param width width of canvas
		@param xscale scale of output in x
		@param height height of canvas (defaults to width)
		@param yscale scale of output in y (defaults to xscale)
		@return new object
	**/

	create: function(ampl, width, xscale, height, yscale) {
		var o = Object.create(EASY.canvasser);
		o.canvas = document.createElement("canvas");
		o.context = o.canvas.getContext("2d");

		o.canvas.width = width;
		o.canvas.height = height || width;

		o.scale = {
			x: xscale,
			y: yscale || xscale
		};

		o.interpolate = SOAR.interpolator.cosine;
		o.amplitude = ampl;

		return o;
	},

	/**
		generate an addressable map from the canvas

		you must call this function before using any of the
		get functions, but AFTER drawing on the canvas.

		@method build
		@x left of area to map (defaults to 0)
		@y top of area to map (defaults to 0)
		@w width of area to map (defaults to canvas width)
		@h height of area to map (defaults to canvas height)
	**/

	build: function(x, y, w, h) {
		this.map = this.context.getImageData(
			x || 0,
			y || 0,
			w || this.canvas.width,
			h || this.canvas.height
		);
	},

	/**
		get the function value at (x, y)

		@method get
		@param c channel to address (0, 1, 2)
		@param x any real number
		@param y any real number
		@return value of function at (x, y)
	**/

	get: function(c, x, y) {
		var xf = this.scale.x * Math.abs(x);
		var xi = Math.floor(xf);
		var mux = xf - xi;

		var yf = this.scale.y * Math.abs(y);
		var yi = Math.floor(yf);
		var muy = yf - yi;

		var xi0 = xi % this.map.width;
		var yi0 = yi % this.map.height;
		var xi1 = (xi + 1) % this.map.width;
		var yi1 = (yi + 1) % this.map.height;

		var v1, v2, v3, v4;
		var i1, i2;

		v1 = this.map.data[4 * (xi0 + yi0 * this.map.width) + c] / 256;
		v2 = this.map.data[4 * (xi0 + yi1 * this.map.width) + c] / 256;
		i1 = this.interpolate(v1, v2, muy);

		v3 = this.map.data[4 * (xi1 + yi0 * this.map.width) + c] / 256;
		v4 = this.map.data[4 * (xi1 + yi1 * this.map.width) + c] / 256;
		i2 = this.interpolate(v3, v4, muy);

		return this.amplitude * this.interpolate(i1, i2, mux);
	}
};

 

It works quite nicely. A simple and obvious adaptation, but hey, I’m learning.

Last entry, I mentioned that I’d be working on game mechanics, and I have done. Yesterday, however, I threw away most of what I’d worked out. Why?

The game was to be called The Healer’s Cave, and it was a folk medicine RPG. The player would run around the cave collecting and mixing ingredients according to herbal folklore, and go to the cave entrances to diagnose and treat incoming patients. The RPG elements included various skills (identifying ingredients, preparation, diagnosis, etc.) that improve with use, allowing the player to level up, and a trust mechanism that describes your relationship with the game world.

I dropped it once I realized two things. One, that the identification, collection, and mixing of ingredients rips off generic fantasy RPG alchemy mechanics. This isn’t bad in itself, as most games reimplement stuff other games do. The more interesting half of the game, in which you have to diagnose and treat a patient using folklore, would have made up for it. Lacking time to generate human 3D models, I decided to implement that more interesting half as a set of screens and dialogs.

So: the really interesting part of the game wouldn’t have had anything to do with the game environment at all. That’s point two, and that’s where I said, well, maybe I can think of something else.

just redecorating the cave

The paddlers are swimming around the cave, glaring at everything with their googly eyes. I’ve altered the cave colors and made the space a little roomier.

The NPC manager I wrote this weekend handles tens of thousands of paddlers while maintaining a framerate of nearly 60 fps, so I’m pretty happy about that. (Naturally, the secret is not to display all of them at the same time, and I do a lot of GL resource swapping  to keep the GPU memory size sane.)

Next week, I tackle the game mechanism.

paddlers ahoy

Here’s a couple of improvements to the creatures I’m now calling “paddlers”. I’ve altered the skin texture generator to provide greater variation across the skin itself. Eye spots are also in evidence now.

They’re a bit cute, I think.