Ryan Badour

Code Samples

Take a look below for a couple examples of my best moments and get a feel for how I engineer code. Most of these examples come from a game framework I've been working on for a few years now. If your interested to inspect the code with more context go here and have a look around.

Infinite Scrolling Background and Tiles

Several months ago while upgrading my personal game framework I was interested in building a game with infinite scrolling. Right away I decided to build a simple reusable class that covers the infinite scrolling concept but was also open to modification for more ideas. Below is an example of how I used a class called RepeatFillRenderer in order to create a decent looking starry background.

This version of RepeatFillRenderer takes a Renderer instance to repeat within an area. In this case the starry background is the same size as the area to cover so it's used to make it appear seemless as it scrolls.

private override function _render(canvas:Canvas2D):Void
{
	if (this.renderer == null)
		return;

	var temp = new Canvas2D("temp", this.sourceWidth, this.sourceHeight);
	this.renderer.render(temp);

	var xRepeat = method == Horizontal || method == Both;
	var yRepeat = method == Vertical || method == Both;

	var xCount = xRepeat ? Math.ceil(this.fillWidth/this.sourceWidth)+1 : 1;
	var yCount = yRepeat ? Math.ceil(this.fillHeight/this.sourceHeight)+1 : 1;
	var left = this.offset.x;
	if (xRepeat)
	{
		left = (this.offset.x % this.sourceWidth);
		if (this.offset.x > 0)
			left = (this.offset.x % this.sourceWidth) - this.sourceWidth;
	}

	var startTop = this.offset.y;
	if (yRepeat)
	{
		startTop = (this.offset.y % this.sourceHeight);
		if (this.offset.y > 0)
			startTop = (this.offset.y % this.sourceHeight) - this.sourceHeight;
	}

	for (i in 0...xCount)
	{
		var top = startTop;
		for (j in 0...yCount)
		{
			canvas.ctx.save();
			canvas.ctx.translate(left, top);
			canvas.renderCanvas(temp);
			canvas.ctx.restore();

			top += this.sourceHeight;
		}
		left += this.sourceWidth;
	}
}
this.canvases = new Array();
this.layers = new ParallaxRendererList(new RenderState(),
	function (i:Int, renderer:StaticRepeatFillRenderer, offset:Vector2, canvas:Canvas2D) {
		renderer.offset = offset;
	}
);

for (i in 0...parallaxes.length)
{
	var canvas = new Canvas2D("stars", width, height);

	// Each layer is a RepeatFillRenderer so it can infinite scrolled
	var repeater = new RepeatFillRenderer(canvas, width, height, RepeatFillMethod.Both, width, height);
	this.canvases[i] = canvas;
	this.layers.addLayer(repeater, parallaxes[i]);
}

// Generate stars on each layer
for (s in 0...count)
{
	var si = Std.random(sizes.length);
	var li = Std.random(this.layers.numLayers);
	var canvas = this.canvases[li];

	canvas.ctx.save();
	var rad = sizes[si];
	var x = Math.random() * width;
	var y = Math.random() * height;
	var grad = canvas.ctx.createRadialGradient(x, y, 0, x, y, rad);
	grad.addColorStop(0, Color.White.rgba);
	grad.addColorStop(1, Color.Black.rgba);
	canvas.ctx.fillStyle = grad;
	canvas.ctx.beginPath();
	canvas.ctx.arc(x, y, rad, 0, Math.PI*2, false);
	canvas.ctx.fill();
	canvas.ctx.restore();
}

Eventually it became obvious that with code like this it would be easy to write a tile level rendering library. So I wrote some integration for the Tiled Map editor, some classes to manage it's data, and generic renderers that can take any tile map as long as it assumes the same interface. At the core the tile code uses a RepeatFillRenderer instance to draw each tile's texture to the canvas.

What's not shown in this demo is the Tiled xml file being loaded, parsed and converted into the tile map definition instances so that this renderer has the interface it needs. If your curious about how that stuff works look at the files in the tiles/tmx directory of Titanium-Reindeer.

The old RepeatFillRenderer class was given a new method "renderTile" which is intended to be overriden by child classes and a new class "StaticRepeatFillRenderer" was created to handle the special case of needing to render a single renderer repeated. The StarsView was refactored to using the static renderer.

private function renderTile(x:Int, y:Int, canvas:Canvas2D):Void
{
}

private override function _render(canvas:Canvas2D):Void
{
	var xRepeat = method == Horizontal || method == Both;
	var yRepeat = method == Vertical || method == Both;

	var xCount = xRepeat ? Math.ceil(this.fillWidth/this.sourceWidth)+1 : 1;
	var yCount = yRepeat ? Math.ceil(this.fillHeight/this.sourceHeight)+1 : 1;
	var left = this.offset.x;
	if (xRepeat)
	{
		left %= this.sourceWidth;
		if (this.offset.x > 0)
			left -= this.sourceWidth;
	}

	var startTop = this.offset.y;
	if (yRepeat)
	{
		startTop %= this.sourceHeight;
		if (this.offset.y > 0)
			startTop -= this.sourceHeight;
	}

	for (i in 0...xCount)
	{
		var top = startTop;
		for (j in 0...yCount)
		{
			canvas.ctx.save();
			canvas.ctx.translate(left, top);
			this.renderTile(i, j, canvas);
			canvas.ctx.restore();

			top += this.sourceHeight;
		}
		left += this.sourceWidth;
	}
}
package titanium_reindeer.rendering;

import titanium_reindeer.rendering.RepeatFillRenderer;

class StaticRepeatFillRenderer extends RepeatFillRenderer
{
	public var renderer:IRenderer;
	public var cached:Canvas2D;

	public function new(renderer:IRenderer, width:Int, height:Int, method:RepeatFillMethod, ?sWidth:Int, ?sHeight:Int)
	{
		super(width, height, method, sWidth, sHeight);

		this.renderer = renderer;
	}

	private override function _render(canvas:Canvas2D):Void
	{
		this.cached = new Canvas2D('static_cache', this.sourceWidth, this.sourceHeight);
		this.renderer.render(this.cached);

		super._render(canvas);
	}

	private override function renderTile(x:Int, y:Int, canvas:Canvas2D):Void
	{
		return canvas.renderCanvas(this.cached);
	}
}
package titanium_reindeer.rendering.tiles;

import titanium_reindeer.tiles.*;
import titanium_reindeer.rendering.RepeatFillRenderer;

/**
 * A basic implemention of how to render all of the tiles of a given tile map using a given tile
 * renderer. There is an assumed relationship between the tile indices provided by the tile map and
 * the tile indices the tile renderer is capable of rendering.
 */
class TileMapRenderer extends RepeatFillRenderer
{
	/**
	 * A definition instance which tells the renderer how big tiles are.
	 */
	public var definition:TileMapDefinition;

	/**
	 * The object queried for a tile id at a given tile position (x, y).
	 */
	public var tileMap:ITileMap;

	/**
	 * The object for rendering one tile based on a given tile Id.
	 */
	public var tileRenderer:ITileRenderer;

	public function new(width:Int, height:Int, definition:TileMapDefinition, tileMap:ITileMap, tileRenderer:ITileRenderer)
	{
		super(width, height, RepeatFillMethod.Both, definition.tileWidth, definition.tileHeight);

		this.definition = definition;
		this.tileMap = tileMap;
		this.tileRenderer = tileRenderer;
	}

	/**
	 * Render the tile at position x, y. This method is called when render for each portion of the
	 * total space.
	 */
	private override function renderTile(x:Int, y:Int, canvas:Canvas2D):Void
	{
		// Add the offset to obtain the x and y of the tile relative to the tile map.
		x += Math.floor(this.offset.x / this.sourceWidth);
		y += Math.floor(this.offset.y / this.sourceHeight);

		var tileIndex = this.tileMap.getTileIndex(x, y);
		this.tileRenderer.render(canvas, tileIndex);
	}
}

Writing up more samples...