Programmer

Apple ][e computers arrived at home when I was little and I learned Applesoft BASIC well enough to make small games, always experimenting. I went to RIT for Computer Science before switching to Film/Video/Animation and continuing with some object-oriented graduate courses.
To make ends meet I began teaching C, C++, and Java at Rochester Business Institute. I spent 14 years there before moving on to Finger Lakes Community College where I now teach programming courses for a living. My favorite programming projects are always games.
Space Barrage, my first complete game made for a competition was a cross platform (Windows and Mac) OpenGL game coded in C++. It was 2004 and I relied on low-level code from SDL (Simple DirectMedia Layer) and glAArg and found myself successfully applying object-oriented design principles to a complete game for the first time.

//Copyright (c) 2004 Aaron Sullivan. All rights reserved.
#ifndef SB_GAMEPHASE_H
#define SB_GAMEPHASE_H

#include "sb_Game.h"
#include "sb_global.h"

class sb_GamePhase
{
public:
	sb_GamePhase( sb_GamePtr gameToUse );
	virtual ~sb_GamePhase() = 0;
	
	virtual void start() = 0;
	virtual void pause() = 0;
	virtual void end() = 0;
	virtual void reset() = 0;
	virtual void processEvent( SDL_Event &event ) = 0;
	virtual void animate() = 0;
	virtual void draw() = 0;
	int isDone();
	
protected:
	int done;
	sb_GamePtr game;
};

typedef sb_GamePhase* sb_GamePhasePtr;

#endif
sb_Phase, the abstract base class above, was created so that every gameplay mode (phase) could be defined in a subclass as demonstrated in the implementation file for sb_Phase_Battle, below. The main game loop simply invokes the required functions through a sb_Phase pointer that points to an object representing the current gameplay mode. Switching between game modes was as easy as pointing to a different object — an object of a class derived from sb_Phase, naturally. Download Complete Source

//Copyright (c) 2004 Aaron Sullivan. All rights reserved.
#include "sb_Phase_Battle.h"
#include "iostream.h"
#include "sb_Missile.h"

sb_Phase_Battle::sb_Phase_Battle( sb_GamePtr gameToUse) 
: sb_GamePhase( gameToUse ) //call sb_GamePhase constructor
{
	game = gameToUse;
	reset();
}

sb_Phase_Battle::~sb_Phase_Battle()
{
	
}

void sb_Phase_Battle::start()
{
	done = false;
	time = 0;
	secondsLeft = totalSeconds;
	
	//title variables
	titleX = 12.0f, titleY = 745.0f;
	titleAlpha = 0.8f;
	titleScaleX = 10.0f;
	titleScaleY = 2.0f;
	titleGlow = 0.0f;
	
	//phase specific
	battling = false;
	waitingForMissiles = false;
	
	game->musicBox.play(kMusic_battle);
	
	//start keeping track of Ticks passed
	game->updateTicksPassed();
}

void sb_Phase_Battle::pause()
{
	game->musicBox.pauseToggle();
}

void sb_Phase_Battle::end()
{
	done = true;
}

void sb_Phase_Battle::reset()
{
	totalSeconds = 15;
	//start();
}

void sb_Phase_Battle::processEvent( SDL_Event &event )
{
	switch (event.type)
	{
		//pointer position comes from game object ( which has its own processEvent function )
		case SDL_MOUSEBUTTONDOWN:
			if (battling)
			{
				int i;
				for (i=0; i <= game->indexOfLastGun; i++)
				{
					if (game->gun[i].isActive() && game->gun[i].isReloading() == false)
					{
						if (game->playerMissiles.launch(game->gun[i].getX(), game->gun[i].getY(), 
														game->gun[i].getRotation(), 
														game->pointerX, game->pointerY, 0))
						{
							game->noisemaker.play(kFX_playerMissile);
							game->gun[i].reload(); //start the delayed reload
							break;
						}
					}
					
				}
				if (i > game->indexOfLastGun) //if there were no guns available to fire... or no missiles available
				{
					game->noisemaker.play(kFX_click); //audio que for failed shot
				}
			}
			
			/* uncomment if you want opening animation to be skipable
			else if(time > 200)
		{
				battling = true;
				timeBattleStarted = time;
				bargesShowing = 5;
		}
			*/
			break;
		case SDL_MOUSEBUTTONUP:
			break;
		case SDL_KEYDOWN:
			if (event.key.keysym.sym == SDLK_SPACE)
			{
				//skip animated intro? 
			}
			//'F' fades out music
			if (event.key.keysym.sym == SDLK_f)
			{
				game->musicBox.fadeOut();
			}
			//'M' toggles music on and off
			if (event.key.keysym.sym==SDLK_m)
			{
				if(game->musicBox.isPlaying())
				{
					game->musicBox.stop();
				}
				else
				{
					game->musicBox.play();
				}
			}
			break;
		case SDL_KEYUP:
			break;
		default:
			break;
	}
}

void sb_Phase_Battle::animate()
{
	if (done) return; //necessary so that last gun placed does not move
	
	int i;
	Uint32 ticksPassed;
	game->updateTicksPassed();
	ticksPassed = game->getTicksPassed();
	
	//Time based events
	time = time + ticksPassed;
	
	Uint32 startTime, endTime;
	float transitionTime; //will hold amount 
	
	for (i=0; i <= game->indexOfLastGun; i++)
	{
		game->gun[i].animate( ticksPassed ); 
	}
	
	game->playerMissiles.animate( ticksPassed );
	if (game->playerMissiles.explodeSound())
	{
		game->noisemaker.play(kFX_missileBlast);
	}
	
	if (time > 500 && battling == false && waitingForMissiles == false)
	{
		battling = true;
		game->destroyers.go();
		timeBattleStarted = 500;
	}
	
	if (battling)
	{
		//animate the title of the phase
		//"stretch in"
		startTime = timeBattleStarted; endTime = timeBattleStarted+500; transitionTime = (float)(endTime - startTime - 1);
		if (time > startTime && time < endTime)
		{
			//the total amount to change (over whole time) is divided total time of transition and multiplied by how many ticks passed 
			//then that portion (which is the fraction needed for THIS update) is subtracted from the current value
			titleScaleX -= 9.0f/transitionTime*ticksPassed;
		}
		if (time > endTime) titleScaleX = 1.0f; //take care of inaccuracy of method
		
		//glow the title
		startTime = timeBattleStarted+500; endTime = timeBattleStarted+1000; transitionTime = (float)(endTime - startTime - 1);
		if (time > startTime && time < endTime)
		{
			if (titleGlow == 0.0f)
			{
				titleGlow = 1.0f;
			}
			titleGlow -= 1.0/transitionTime*ticksPassed;
		}
		if (time > endTime) titleGlow = 0.0f;
		
		//raise and vanish title
		startTime = timeBattleStarted+2500; endTime = timeBattleStarted+3000; transitionTime = (float)(endTime - startTime - 1);
		if (time > startTime && time < endTime)
		{
			titleY -= 100.0f/transitionTime*ticksPassed; 
			titleAlpha -= 0.8f/transitionTime*ticksPassed;
			if (titleAlpha < 0.0f) titleAlpha = 0.0f;
			titleScaleY += 10.0f/transitionTime*ticksPassed;
		}
		if (time >= endTime) {
			titleY = 0.0;
			titleAlpha = 0.0f;
			titleScaleY = 4.0f;
		}
		
		//timer
		//*this phase is ended when the timer (secondsLeft) runs out
		secondsLeft = totalSeconds - (time-timeBattleStarted)/1000; //time/1000 turns milliseconds into seconds
		if (secondsLeft < 0)
		{
			secondsLeft = 0;
			battling = false;
			timeBattleEnded = time;
			waitingForMissiles = true;
			game->destroyers.stop();
			game->musicBox.fadeOut();
		}
		
		//destroyers
		game->destroyers.animate( ticksPassed );
		
	}
	
	sb_Circle dCircle;
	int dIndex;
	dCircle = game->destroyers.getNextCollisionCircle(dIndex); //dIndex is updated
	while (dIndex > -1)
	{
		//if any player missiles are exploding on this destroyer damage it
		if (game->playerMissiles.anyCollisionWith(dCircle))
		{
			game->destroyers.damage(dIndex);
			game->noisemaker.play(kFX_missileHit);
		}
		//if this destroyer is over a vacant spot, it means they have come into close range of
		//the barges and the game is over
		if (game->grid.getSectorValue((int)(dCircle.getX() / game->grid.getSectorSize()), (int)(dCircle.getY() / game->grid.getSectorSize()))
			!= kST_outOfRange)
		{
			game->gameover = true;
			end();
		}
		dCircle = game->destroyers.getNextCollisionCircle(dIndex); //dIndex is updated
	}
	
	//if all the destroyers are gone, end wave
	if (battling && game->destroyers.allInactive())
	{
		secondsLeft = 0;
		battling = false;
		timeBattleEnded = time;
		waitingForMissiles = true;
		game->destroyers.stop();
		game->musicBox.fadeOut();
	}
	
	//we wait for the missiles to finish their path
	if (waitingForMissiles)
	{
		game->destroyers.animate( ticksPassed );
		if (game->playerMissiles.allInactive() && game->destroyers.isStopped())
		{
			waitingForMissiles = false;
			//if we are on or past the third stage there are no more new enemy reinforcements... so we'll move on to the next stage
			if (game->destroyers.allInactive() && game->currentWave >= 3)
			{
				game->newWave = true; //let the newWave phase take care of advancing us to the next Stage
				waitingForMissiles = true;
			}
			//on to the next wave
			end();
		}
	}
}

void sb_Phase_Battle::draw()
{
	glMatrixMode(GL_MODELVIEW); //we select the Model matrix to do our drawing
	
	glBindTexture(GL_TEXTURE_2D, glAA_texture[0]);
	game->grid.displayShields();
	
	int i;
	for (i=0; i < game->totalBarges; i++)
	{
		game->barge[i].draw();
	}
	
	for (i=0; i <= game->indexOfLastGun; i++)
	{
		game->gun[i].draw();
	}
	
	game->playerMissiles.draw();
	
	if (battling)
	{
		drawTitle();
		drawTimer();
		drawPointer();
	}
	
	game->destroyers.draw();
}

void sb_Phase_Battle::drawTimer()
{
	glAAFlush();
	glLoadIdentity();
	glBindTexture(GL_TEXTURE_2D, glAA_texture[0]);
	vf_setpenscale(3.5f, 3.5f);
	vf_setpenloc( 10.0f, 690.0f );
	glAALineWidth(3);
	glAAColor4f(1.0f, 1.0f, 1.0f, 0.5f);
	if (secondsLeft >= 10)
	{
		vecString("%d", secondsLeft);
	}
	else
	{
		vecString("0%d", secondsLeft);
	}
}

void sb_Phase_Battle::drawTitle()
{
	vf_setpentrack(8.0f, 0.0f);
	if (titleGlow > 0.0f)
	{	
		glAAFlush();
		glLoadIdentity();
		glBindTexture(GL_TEXTURE_2D, glAA_texture[2]);
		vf_setpenscale(titleScaleX, titleScaleY);
		vf_setpenloc( titleX, titleY );
		glAALineWidth(10);
		glAAColor4f(1.0f, 1.0f, 1.0f, titleGlow);
		vecString("OPEN FIRE!");
	}
	
	glAAFlush();
	glLoadIdentity();
	glBindTexture(GL_TEXTURE_2D, glAA_texture[0]);
	vf_setpenscale(titleScaleX, titleScaleY);
	vf_setpenloc( titleX, titleY );
	glAALineWidth(3);
	glAAColor4f(1.0f, 1.0f, 1.0f, titleAlpha);
	vecString("OPEN FIRE!");
}

void sb_Phase_Battle::drawPointer()
{
	glAAFlush();
	glLoadIdentity();
	glTranslatef(game->pointerX, game->pointerY, 0.0f);
	glAALineWidth(2);
	glAAColor4f(1.0f, 1.0f, 0.0f, 0.6f);
	glAABegin(GL_LINE_STRIP);
	glAAVertex2f(0.0f,0.0f);
	glAAVertex2f(0.0f,15.0f);
	glAAVertex2f(4.0f,10.0f);
	glAAVertex2f(12.0f,12.0f);
	glAAVertex2f(0.0f,0.0f);
	glAAEnd();
	glAAColor4f(1.0f, 1.0f, 0.0f, 0.2f);
}
For a longer term contest I created Snowball, a fully 3D game using OpenGL. It was a more data intensive game with a level-editor and 3D stages. In my previous project I relied on my own management of arrays but I decided to embrace the C++ Standard Template Library vectors for more flexibility. Download Complete Source

#ifndef SN_PUSHABLEBLOCKCOLLECTION
#define SN_PUSHABLEBLOCKCOLLECTION

#include "sn_PushableBlock.h"
#include <string>
using namespace std;

class sn_PushableBlockCollection
{
public:
	void init( sn_3DmodelCollection * p3DModelCollection, sn_MovementArray * pMA, vector<sn_StageElement*> * pSE );
	void clear();
	int load(const string &filename);
	int save(const string &filename);
	sn_PushableBlock* addNew( float x, float y, float z, int blockType);
	void updateMovementDeltas();
	bool eraseArea( float x, float y, float z);
	
	void animate( unsigned int ticksPassed );
	void dropTest(); //test for new situation in which blocks should fall
	
	vector<sn_PushableBlock*>::iterator findCollidingBlock( sn_Collider &c);
	vector<sn_PushableBlock*>::iterator findCollidingBlock( vector<sn_PushableBlock*>::iterator &pBlock);
	
	vector<sn_PushableBlock*>::iterator getBeginItr();
	vector<sn_PushableBlock*>::iterator getEndItr();
	
private:
	vector<sn_PushableBlock*> blocks;
	sn_3DmodelCollection * pModels;
	sn_MovementArray * pMovementArray;
	vector<sn_StageElement*> * hStageElements;
	
	vector<string>modelFilenames;
	vector<string>movementMapFilenames;
};

#endif
My next large project was to take advantage of the new iPhone App Store announced in 2008. I decided to port Snowball, my 3D OpenGL game, and soon discovered the joys and tribulations of developing for a new platform. OpenGL ES forced me in new directions but even more daunting was learning the shifting beta Cocoa Frameworks and the Objective-C language.

//
//  ST_Button_StageSlider.m
//  SnowballTouch
//
//  Created by Aaron Sullivan on 8/30/08.
//  Copyright 2009 Buzzabit Entertainment. All rights reserved.
//

#import "ST_Button_StageSlider.h"
#import "ST_GameManager.h"
#import "ST_Prefs.h"

#define kActualTrackDistance 400
#define kStageNumYOffset 0

@implementation ST_Button_StageSlider

@synthesize stageMax, stageStart, gameNumber, x, y, xOrigin, yOrigin, pressed, state;

-(id)initWithStageManager:(ST_StageManager*)sm Game:(int)g X:(float)ix Y:(float)iy {
	self = [super init];
	if (self != nil) {
		gameNumber = g;
		stageManager = sm;
		stageStart = stageMax = [ST_Prefs getStageForGame:gameNumber];
		x = ix;
		y = iy;
		xOrigin = ix;
		yOrigin = iy;
		trackStartY = iy - kActualTrackDistance/2;
		trackDistPerStage = kActualTrackDistance / stageManager.totalStages;
		trackStageMaxY = trackStartY + trackDistPerStage * stageMax;
		const float trackOffset = 8.0;
		const float trackBossStageY = trackStartY + trackDistPerStage * stageManager.bossStage;
		
		label_slider = [[ST_Label alloc] initWithText:@"" Rect:CGRectMake(20, 20, 90, 440)];
		[label_slider show];
		button_track = [[ST_Button alloc] initWithTexture:@"GUI_stageProgressBar_396x16.png" X:ix-trackOffset Y:iy-10.0 Width:16 Height:kActualTrackDistance + 60.0];
		button_player = [[ST_Button alloc] initWithTexture:@"GUI_playerIcon_128x128.png" X:ix+trackOffset+5 Y:trackStageMaxY Width:64 Height:64 ];
		snmX = ix + 42;
		snmY = iy + kStageNumYOffset;
		button_triangle = [[ST_Button alloc] initWithTexture:@"GUI_triangle_64x64.png" X:ix-trackOffset-3 Y:trackStageMaxY Width:64 Height:64 ];
		button_x = [[ST_Button alloc] initWithTexture:@"GUI_goalX_64x64.png" X:ix-trackOffset Y:trackBossStageY Width:64 Height:64 ];
		
		[button_track setAlpha:1.0];
		[button_player setAlpha:1.0];
		[button_triangle setAlpha:1.0];
		[button_x setAlpha:1.0];
		
		prevPoint.x = xOrigin;
		prevPoint.y = yOrigin;
		pressed = NO;
		
		stageNumModel = [[ST_Model alloc] initAsSquareWithAnimatedTexture:@"texture_num.png" withFrameBoundsX:5 Y:2];

		//set up details label with info on current stage
		label_details = nil;
		[self initDetailLabel];
		
		positionAnimator = [[ST_AnimatorQ3 alloc] init];
	}
	return self;
}

-(void)dealloc {
	[button_track release];
	[button_player release];
	[button_triangle release];
	[button_x release];
	
	[label_slider release];
	[label_details release];
	
	[positionAnimator release];
	[stageNumModel release];
	
	[super dealloc];
}

-(BOOL)pressAttemptAtX:(int)px Y:(int)py {
	if ([button_player pressAttemptAtX:px Y:py] == YES) {
		state = kst_button_stageSlider_state_playerDrag;
		pressed = YES;
		return YES;
	}
	else {
		pressed = NO;
		return NO;
	}
}

-(BOOL)dragAttemptAtX:(float)dx Y:(float)dy {
	if (state == kst_button_stageSlider_state_playerDrag) {
		float newY = dy;
		if (newY > trackStageMaxY)
			newY = trackStageMaxY;
		else if (newY < trackStartY) 
			newY = trackStartY;
		[button_player forcePositionX:button_player.x Y:newY];
		stageStart = (button_player.y - trackStartY)/trackDistPerStage;
		pressed = YES;
		return YES;
	}
	else {
		pressed = NO;
		return NO;
	}
}

-(void)letGo {
	pressed = NO;
	if (state == kst_button_stageSlider_state_playerDrag) {
		//figure out stage based on new position of playerIcon
		stageStart = (button_player.y - trackStartY)/trackDistPerStage;
		//snap the position of the icon to the area where that stage should be represented
		[button_player letGo];
		[button_player forcePositionX:button_player.x Y:trackStartY + stageStart*trackDistPerStage];
		state = kst_button_stageSlider_state_still;
		
		//set up details button
		[self initDetailLabel];
		
	}
}

-(void)draw {
	[label_slider draw];
	
	//draw the whole layout
	[label_details draw];
	[button_track draw];
	[button_x draw];
	[button_triangle draw];
	[button_player draw];
	//draw the stage number over the player button
	glPushMatrix();
	int frame1 = stageStart % 10;
	glPushMatrix();
	[ST_Model guiTransformX:snmX Y:button_player.y-13.0 W:32.0 H:16.0];
	[stageNumModel setTextureFrame:frame1];
	[stageNumModel drawAlpha:YES ReverseX:NO Y:YES DepthTest:NO];
	glPopMatrix();
	if (stageStart >= 10) {
		int frame10 = stageStart / 10;
		glPushMatrix();
		[ST_Model guiTransformX:snmX Y:button_player.y-26.0 W:32.0 H:16.0];
		[stageNumModel setTextureFrame:frame10];
		[stageNumModel drawAlpha:YES ReverseX:NO Y:YES DepthTest:NO];
		glPopMatrix();
	}
	glPopMatrix();
}

-(void)animate {
	[button_player animate];
	[label_details animate];
	[label_slider animate];
}

-(void)updateButtonPositions {
	trackStartY = yOrigin - kActualTrackDistance/2;
	trackStageMaxY = trackStartY + trackDistPerStage * stageMax;
	const float trackStageStartY = trackStartY + trackDistPerStage * stageStart;
	const float trackOffset = 8.0;
	[button_player forcePositionX:xOrigin+trackOffset+5.0 Y:trackStageStartY];
	[button_triangle forcePositionX:xOrigin-trackOffset-3.0 Y:trackStageMaxY];
}

-(void)initDetailLabel {
	if (label_details != nil) {
		[label_details release];
	}
	//get info from current selected stage (stageStart)
	
	gameNumber = [ST_GameManager instance].currentGameFile;
	int bestTime = [ST_Prefs getTimeForStage:stageStart
					  InGame:gameNumber];
	int totalStageTime = [ST_Prefs getTotalTimeForStage:stageStart 
						     InGame:gameNumber];
	int bestSteps = [ST_Prefs getStepsForStage:stageStart 
				            InGame:gameNumber];
	int bHours = bestTime / 3600;
	int bMinutes = bestTime / 60 - bHours * 60;
	int bSeconds = bestTime - (bHours * 3600 + bMinutes * 60);
	
	int tHours = totalStageTime / 3600;
	int tMinutes = totalStageTime / 60 - tHours * 60;
	int tSeconds = totalStageTime - (tHours * 3600 + tMinutes * 60);
	
	label_details = [[ST_Label alloc] initWithText:[NSString stringWithFormat:
							@"Stage %d\n%@\n\n          Best Steps: %d\n          Best Time:  %02d:%02d:%02d\n          Total Time: %02d:%02d:%02d"
							, stageStart
							, [stageManager getStageNameForStageNumber:stageStart]
							, bHours, bMinutes, bSeconds
							, bestSteps
							, tHours, tMinutes, tSeconds]
							Rect:CGRectMake(140.0, 20.0, 160, 320)];
	[label_details show];
}

@end		

Though largely feature complete, I couldn't keep up with the rapidly changing platform alone so it was never released. Even so, I found tremendous reward in exploring all the possibilities of the curious multitouch screen and accelerometer. (video)

I finally released an iPhone game on the App Store at the end of 2013 by collaborating with a partner and using Unity. Below is a C# script I wrote (several times) that implements the ambitious control scheme for the game.


/*
 * InputSB
 * Attach to a gameObject to expose properties
 * A layer named InterfaceSurface is needed for this to work
 * Usage:
 * 
 *   Check for state using static methods GetButtonDown(0), GetButtonDown(1), GetDragging()
 * After confifming state you can check public state variables (all static):
 *   pointerPosition (like mousePosition)
 *   surfacePointerPosition (position of pointer on interface surface)
 *   dragDelta (change in position since last update while dragging)
 *   surfaceDragDelta (like dragDelta but on a 3D surface which is great for "map dragging")
*/
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public enum InputState
{ 
	mainGame,
	none 
}

public enum ButtonIDs
{
	down1,
	down2,
	endTimer,
	pause,
	camera
}

public class InputSB : MonoSingleton<InputSB> {
	
	public Vector2 fingerOffset = new Vector2(0.35f,0.15f); //fraction of an inch
	public Camera interfaceCamera = null;
	public GameObject interfaceSurface = null;
	public List<Rect> guiRectList = new List<Rect>(); //use to add a mask so we can ignore some clicks
	public Vector3 AxisUsedOn2DPlane = new Vector3(1f,1f,0f); // put a 1 in 2 axis and a 0 in the other
	public float accelerateMultiplier = 0.5f;


	//public state variables
	public static Vector2 pointerPosition = new Vector2(-1f,-1f);
	public static Vector2 rawPosition = new Vector2(-1f,-1f);
	public static Vector2 dragDelta = new Vector2(0f,0f);
	public static Vector3 surfaceClickPosition = new Vector3(-10001f, -10001f, -10001f);
	public static Vector3 surfaceDragDelta = new Vector3(0f,0f,0f);
	public InputState inputState = InputState.mainGame;
	
	public Vector3 cornerBottomLeft;
	public Vector3 cornerTopLeft;
	public Vector3 cornerTopRight;
	public Vector3 cornerBottomRight;
	
	//internal tracking
	private int firstTouchID = -1;
	private int secondTouchID = -1;
	private static bool downTouch = false;
	private static bool upTouch = false;
	private static bool down1 = false;
	private static bool down2 = false;
	private static bool pause = false;
	private static bool endTimer = false;
	private static bool camera = false;
	private Vector2 prevPosition;
	public bool left = false;
	private Vector3 surfacePrevClickPosition;
	private int interfaceSurfaceLayer;
	private delegate void InputStateDelegate();
	private InputStateDelegate currentStateDelegate;

	private bool gamePaused = false;

	
	// Use this for initialization
	public override void Init ()
	{		
		//if no camera is provided we use the main
		if (interfaceCamera == null) {
			interfaceCamera = Camera.main;
		}
		
		//if no drag surface is supplied, create one
		//A layer named InterfaceSurface is needed for this to work
		interfaceSurfaceLayer = LayerMask.NameToLayer("InterfaceSurface");
		//if the layer exists we can create the surface or set the provided one to the correct layer
		if (interfaceSurfaceLayer != -1) {
			if (interfaceSurface == null) {
				interfaceSurface = new GameObject("InterfaceSurface");
				interfaceSurface.AddComponent<BoxCollider>();
				interfaceSurface.transform.parent = gameObject.transform;
				interfaceSurface.transform.localScale = new Vector3(AxisUsedOn2DPlane.x * 10000f,AxisUsedOn2DPlane.y * 10000f,AxisUsedOn2DPlane.z * 10000f);
				interfaceSurface.layer = interfaceSurfaceLayer;
			}
			else {
				interfaceSurface.layer = interfaceSurfaceLayer;
			}
		}
		else {
			print ("InputSB: A Layer named InterfaceSurface is needed for InputCX to function");
		}
		
		cornerBottomLeft = getSurfacePosition(new Vector2(0f,0f));
//		print ("bottom left: " + cornerBottomLeft);
		cornerTopLeft = getSurfacePosition (new Vector2(0f,Screen.height));
//		print ("top left: " + cornerTopLeft);
		cornerTopRight = getSurfacePosition(new Vector2(Screen.width, Screen.height));
//		print ("top right: " + cornerTopRight);
		cornerBottomRight = getSurfacePosition (new Vector2(Screen.width, 0f));
//		print ("bottom right: " + cornerBottomRight);

		//FinishClick(); //resets all the values including setting our state delegate
		currentStateDelegate = MainGame;
		inputState = InputState.mainGame;
	}

	void OnEnable() {
		MenuManager.OnGamePause += OnGamePause;
		MenuManager.OnGameUnPause += OnGameUnPause;
	}
	
	void OnDisable() {
		MenuManager.OnGamePause -= OnGamePause;
		MenuManager.OnGameUnPause -= OnGameUnPause;
	}
	
	public void OnLevelWasLoaded(){
		if (interfaceCamera == null) {
			interfaceCamera = Camera.main;
		}
		
		cornerBottomLeft = getSurfacePosition(new Vector2(0f,0f));
//		print ("bottom left: " + cornerBottomLeft);
		cornerTopLeft = getSurfacePosition (new Vector2(0f,Screen.height));
//		print ("top left: " + cornerTopLeft);
		cornerTopRight = getSurfacePosition(new Vector2(Screen.width, Screen.height));
//		print ("top right: " + cornerTopRight);
		cornerBottomRight = getSurfacePosition (new Vector2(Screen.width, 0f));
//		print ("bottom right: " + cornerBottomRight);
	}
	
	public GameObject GetInterfaceSurface() {
		return interfaceSurface;
	}
	
	// Update is called once per frame
	void Update ()
	{
		currentStateDelegate();
	}

	private void registerButtonDown(Touch t) {
		// if we're paused, the only second touch we care about is unpausing
		if((Application.loadedLevel == (int)Levels.Battle || Application.loadedLevel == (int)Levels.Tutorial) && MenuManager.instance.paused){
			if(TouchButtonsManager.instance.WithinPauseButton(getSurfacePosition(t.position)))
				pause = true;
			return;
		}

		if (TouchButtonsManager.instance.WithinPrimaryButton(getSurfacePosition(t.position))){// t.position.y < Screen.height * 0.5) {
			down1 = true;
		}
		else if(TouchButtonsManager.instance.WithinSecondaryButton(getSurfacePosition(t.position))){
			down2 = true;
		}
		else if(TouchButtonsManager.instance.WithinPauseButton(getSurfacePosition(t.position))){
			pause = true;
		}
		else if(TouchButtonsManager.instance.WithinEndTimerButton(getSurfacePosition(t.position))){
			endTimer = true;
		}
		else if(TouchButtonsManager.instance.WithinCameraButton(getSurfacePosition(t.position))){
			camera = true;
		}
	}
	
	
	public static bool GetStartTouching() {
		return downTouch;
	}
	
	public static bool GetEndTouching() {
		return upTouch;
	}
	
	public static bool GetDragging() {
		if (dragDelta.x == 0f && dragDelta.y == 0f) return false;
		else return true;
	}

	public static float GetDragAmount(){
		if(!GetDragging()) return 0.0f;

		return dragDelta.magnitude;
	}

	public static bool GetButtonDown( int button ) {
		if (button == (int)ButtonIDs.down1 && down1) {return true;}
		else if (button == (int)ButtonIDs.down2 && down2) {return true;}
		else if (button == (int)ButtonIDs.endTimer && endTimer) {return true;}
		else if (button == (int)ButtonIDs.pause && pause) {return true;}
		else if (button == (int)ButtonIDs.camera && camera) {return true;}
		else return false;
	}

	public bool GetTouching(){
		return firstTouchID != -1;
	}
	
	//Input states:
	//MainGame
	private void MainGame() {
		downTouch = false; // if there was a down event we erase it now
		down1 = false;
		down2 = false;
		pause = false;
		endTimer = false;
		camera = false;
		upTouch = false;
		dragDelta = new Vector2(0f,0f);
		//up1 = false;
		//up2 = false;
		bool t1Held = false;
		bool t2Held = false;
		
		surfaceDragDelta = surfaceClickPosition - surfacePrevClickPosition;

		//if game is paused let regular gui inputs work only (for options menu)
		if (gamePaused) return;

		Touch t1 = new Touch();
		Touch t2 = new Touch();
		foreach (Touch t in Input.touches) {
			if (t.fingerId == firstTouchID) {
				t1 = t;
				t1Held = true;
			}
			if (t.fingerId == secondTouchID) {
				t2 = t;
				t2Held = true;
			}
		}
		
		//if we already have a touchID for the first touch
		if (t1Held) {
			if (t1.phase == TouchPhase.Moved) {
				setPointerWithOffset (t1.position);
				dragDelta = t1.deltaPosition;
				SetSurfaceClickPosition();
			}
			else if (t1.phase == TouchPhase.Ended || t1.phase == TouchPhase.Canceled) {
				firstTouchID = -1;
				dragDelta = new Vector2(0f,0f);
				upTouch = true;
			}
		}
		//if we already have a touchID for the second touch
		if (t2Held) {
			if (t2.phase == TouchPhase.Ended || t2.phase == TouchPhase.Canceled) {
				secondTouchID = -1;
			}
		}
		
		int count = Input.touchCount;
		
		//if touches
		if (count == 1) {
			Touch t = Input.GetTouch(0);
			if (t.phase == TouchPhase.Began) {
				registerDownTouch(t.position); 
				firstTouchID = t.fingerId;
				setPointerWithOffset(t.position);
				SetSurfaceClickPosition();
			}
		}
		else if (count == 2) {
			Touch touch1 = Input.GetTouch(0);
			Touch touch2 = Input.GetTouch(1);
			if (touch1.phase == TouchPhase.Began 
				&& touch2.phase == TouchPhase.Began) {
				//both of these came down at once
				//we'll just choose 1 to be the pointer
				registerDownTouch(touch1.position); 
				firstTouchID = touch1.fingerId;
				setPointerWithOffset(touch1.position);
				SetSurfaceClickPosition();
				secondTouchID = touch2.fingerId;
				registerButtonDown(touch2);
			}
			else {
				if (t1Held) {
					foreach (Touch t in Input.touches) {
						if (t.fingerId != firstTouchID && t.phase == TouchPhase.Began) {
							registerButtonDown(t);
							if (secondTouchID == -1) secondTouchID = t.fingerId;
						}
					}
				}
			}
		}
		else if (count > 2) {
			//if we are already holding down our first finger
			//we can register any other touches
			if (t1Held) {
				foreach (Touch t in Input.touches) {
					if (t.fingerId != firstTouchID && t.phase == TouchPhase.Began) {
						registerButtonDown(t);
						if (secondTouchID == -1) secondTouchID = t.fingerId;
					}
				}
			}
			
		}
		//deal with possible mouse input
		else {
			if (Input.GetAxis("Mouse X") != 0.0 || Input.GetAxis ("Mouse Y") != 0.0 ) {
				prevPosition = pointerPosition;		
				pointerPosition = Input.mousePosition;
				rawPosition = pointerPosition;
				SetSurfaceClickPosition();
			}
			if (Input.GetMouseButtonDown ((int)ButtonIDs.down1)) {
				down1 = true;
				prevPosition = pointerPosition;
				pointerPosition = Input.mousePosition;
				rawPosition = pointerPosition;
			}
			else if (Input.GetMouseButtonDown((int)ButtonIDs.down2)){
				down2 = true;
				prevPosition = pointerPosition;
				pointerPosition = Input.mousePosition;
				rawPosition = pointerPosition;
			}
			else if(Input.GetMouseButtonDown((int)ButtonIDs.endTimer)){
				endTimer = true;
				prevPosition = pointerPosition;
				pointerPosition = Input.mousePosition;
				rawPosition = pointerPosition;

			}
		}
	}
	
	//Input State: None
	private void None() {
	}
	
	private void registerDownTouch(Vector2 screenPos) {
		downTouch = true;
		left = (screenPos.x < Screen.width * 0.5);
	}
	
	private void setPointerWithOffset(Vector2 screenPos) {
		prevPosition = pointerPosition;
		pointerPosition = screenPos;
		rawPosition = pointerPosition;
		if(Application.loadedLevel == (int)Levels.Battle || Application.loadedLevel == (int)Levels.Tutorial){
			//offset depending on how far across screen we are
			//float percentAcross = 1f - screenPos.x / Screen.width;
			//float newX = screenPos.x + (fingerOffset.x * 2f * percentAcross - fingerOffset.x);
			float offsetX = fingerOffset.x * Screen.dpi;
			float offsetY = fingerOffset.y * Screen.dpi;
			float newX, newY;
			if (PlayerDataTracker.instance.fastPointOn) {
				//accelerated 
				//if we start from the right we subtract the amount of "accelerated" x
				if (!left) {				
					newX = (screenPos.x - ((Screen.width - screenPos.x) * accelerateMultiplier)) -  offsetX;
				}
				else {
					newX = (screenPos.x + (screenPos.x * accelerateMultiplier)) + offsetX;
				}
				newY = (screenPos.y + (screenPos.y * accelerateMultiplier)) + offsetY;
			}
			else {
				if (left) {
					newX = screenPos.x + offsetX;
				}
				else {
					newX = screenPos.x - offsetX;
				}
				newY = screenPos.y + offsetY;
			}
			pointerPosition = new Vector2(newX, newY);
		}
	}
	
	//SetSurfaceClickPosition() 
	//clickPosition needs to be set immediately before calling this
	//it casts onto a surface in order to get an accurate drag distance on a surface
	private void SetSurfaceClickPosition() {
		if(interfaceCamera == null)
			interfaceCamera = Camera.main;
		if (interfaceSurface) {
			RaycastHit hit;
			int layerMask = 1 << interfaceSurfaceLayer; //bitshift in layer number to raycast against it only
			Physics.Raycast(interfaceCamera.ScreenPointToRay(pointerPosition), out hit, 10000.0f, layerMask);
			if (hit.transform != null) {
				surfaceClickPosition = hit.point;
			}
			//we have to calculate the previous point on the geometry from the prev 2D point
			//because the ground has shifted under the camera and if we kept using the 3D point of the surface
			//it would immediately delta right back to where we were in a stutter (proven)
			Physics.Raycast(interfaceCamera.ScreenPointToRay(prevPosition), out hit, 10000.0f, layerMask);
			if (hit.transform != null) {
				surfacePrevClickPosition = hit.point;
			}
		}
	}

	public Vector3 getSurfacePosition(Vector2 screenPosition) {
		if(interfaceCamera == null)
			interfaceCamera = Camera.main;
		if (interfaceSurface) {
			RaycastHit hit;
			int layerMask = 1 << interfaceSurfaceLayer; //bitshift in layer number to raycast against it only
			Physics.Raycast(interfaceCamera.ScreenPointToRay(screenPosition), out hit, 10000.0f, layerMask);
			if (hit.transform != null) {
				return hit.point;
			}
			else return new Vector3(0f,0f,0f);
		}
		else return new Vector3(0f,0f,0f);
	}

	//update internal status when game pause event occurs
	public void OnGamePause() {
		gamePaused = true;
	}
	public void OnGameUnPause() {
		gamePaused = false;
	}
}