in development

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.