Quantcast
What was you're answer out of curiosity? BaseClass::StaticFunction() -> DerivedClass::StaticFunction()? namespace conflict?

My name is Michael Kofman and
I am a Developer, a Designer, an Architect, an Entrepreneur, an Educator and a Student.

Graduated Full Sail University with a Bachelors of Science in Game Development with a life long background in IT and Web Design.

Roadent Rampage


Click the image above to see a game trailer

Roadent Rampage took place during the last five months at Full Sail University. The development schedule was broken down into a two month design and planning stage, and the final three months comprised the production of the game. This game was developed by a team of five individuals who were selected randomly from a class of sixteen. I played the role of Project Lead on this project; my responsibilities were to maintain open lines of communication, make sure each member remains on task, and be responsible for meeting project deadlines.


Technical Responsibilities
Game Framework
Resource Manager
Event System
Character Stackable State Machine
Artificial Intelligence for Single Player & bots
Navigation Meshes
Hamster Animations
Debug Tools

Screen Shots

Team

From left: Jon Faugeaux, Tom Rader (top), Casey Flach (bottom), Michael Sigsbey

Level Concept

I drew all the concept art for the levels we designed. This top down view of the hamster cage level. This image was later used to model out the level in Maya as well as the Nav Mesh. Which was later refined by our artist.

Getting to know the team

Only having worked with Casey Flach before, most of us were unfamiliar with each other. In order to bring the team together I organized a small volleyball tourney. Originally I planned on getting the other teams in our studio involved, but sadly its hard to get Nerds organized for physical activity on a hot summer day.

Agile Development

During production the team met at my apartment where we began at 9 am sharp and performed a 10-15 minute review of everyone’s progress and what everyone plans to get done by the end of the day. Each task was intended to be completed with-in 8 hours, thereby seeing a constant flow of stickies and steady progress.

AI Architecture

The entire AI architecture for Roadent Rampage was developed in only three weeks. This included pathfinding, a very high level behavior tree that could navigate the level and shoot at the player, as well as a perception layer that kept track of game objects in the scene and performed periodic line of sight tests to avoid cheating.

Maya NavMesh Exporter

This is an MLL file I created for Maya 2008 in order to export my Navigation Mesh and load it as XML. Click here to download.




Code Snippets

Planning Positional Goals & Steering
Due to how we prioritized the multiplayer element of Roadent Rampage over single player. I ended up tackling the AI when production was almost in Beta. Despite the amount of planning done before tackling the AI bots, it was by far the most ambitious AI I’ve tackled at that time. Their were three distinct challenges to be solved. The AI bots needed to resemble that of human players in both behavior and their knowledge of the world. Second was getting the bots to navigate the arena in a believable way. Finally the AI had to be able to jump platform to platform.
After several failed attempts at using strictly steering behaviors for navigating the world, and trying various different types of whiskers, the collision avoidance was simply not good enough. I spent a few days attempting to generate a NavMesh dynamically using AI Wisdoms for reference it proved to be a very daunting challenge that Alex Champandard advised to not pursue given my time limitations. I proceeded to model the NavMesh by hand, making several adjustments such as getting rid of long skinny strips that were common near the edges. of geometry. This method provided me with several other advantages such as the solution to jumping platforms. My having the navigation mesh stretch from platform to platform, bots would attempt to navigate over the ledges naturally. Sadly after leaving the ground the AI had a tendency to fall straight down and not quite make it over the ledge. I requested the artists to add slight bevels to the edges of the platforms which made jumping for both human players and bots a much easier endeavor.
The code snippet below demonstrates how using A* and a little clever path following allowed the AI to navigate to its desired target in a smooth and natural way. This ended up working very well, but I would love to hear any feedback you (the reader) may have on the subject. Please contact me directly at michael.kofman@straystudios.com.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void Planning::DefineCurrentGoal()
{
	// Based on character's percieved characters
	unsigned int size = m_pAgent->m_pPerception->GetCharacters().size();
 
	// Iterate through each and determine a new target
	for (unsigned int i = 0; i < size; ++i)
	{
		if(m_pAgent->m_pPerception->GetCharacters()[i]->m_bExists)
		{
			// Perceived Objects in the original implimentation were meant to include both Characters, and power ups
			// Due to the number of game objects in the scene this was a none-issue
			Character * pChar = reinterpret_cast<Character *>(m_pAgent->m_pPerception->GetCharacters()[i]->m_Object);
 
			// If the character grounded, update target orientation and velocity
			// This was the use of simple steering behaviors such as arrive at, and look at
			if (pChar->IsStateActive(GROUND))
			{
				m_Goal.position = pChar->GetPosition();
				Vec3 dir = m_Goal.position - m_pAgent->GetCharacter()->GetPosition();
				m_Goal.orientation_xz = atan2(-dir.x, -dir.z);
				m_Goal.velocity = pChar->GetVelocity();
 
				// We do not want to only propel the character forward but not affect the physics simulation and collision
				m_Goal.velocity.y = 0.0f;
			}
			else
			{
				m_Goal = m_pAgent->m_Steering;
			}
		}
	}
 
	// Two function calls to the NavMesh class which returns the index of the traingle the current target goal is located in
	int triStartID = m_pAgent->m_pPerception->GetNavigation()->GetTriangle(m_pAgent->GetCharacter()->GetPosition());
	int triGoalID = m_pAgent->m_pPerception->GetNavigation()->GetTriangle(m_Goal.position);
	if(triStartID == -1 || triGoalID == -1)
		return;
 
	// Perform an A* search through a NavMesh lookup table
	// The output returns a reverse order waypoints through the nav mesh
	// We performed this output because it made it much easier to debug the process
	vector<Vec3> output = m_pAgent->m_pPerception->GetNavigation()->AStar(triStartID, triGoalID);
	if(output.size() > 2)	// If distance was more than one waypoint away
	{
		Vec3 dir = output[output.size() - 2] - m_pAgent->GetCharacter()->GetPosition();
		m_Goal.position = output[output.size() - 2];
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
void Reaction::ActuateSteering()
{
	const Kinematic *currentGoal = &m_pAgent->m_pPlanning->GetGoal();
	const Kinematic *actor = &m_pAgent->m_Steering;
 
	// vector from current position to current goal
	Vec3 position2goal = currentGoal->position; 
	position2goal -= actor->position;
 
	const float distance2 = (position2goal.x*position2goal.x + 
							position2goal.y*position2goal.y + 
							position2goal.z*position2goal.z);
	const float maxAcceleration2 = m_fMaxAcceleration * m_fMaxAcceleration;
 
	if (distance2 > 0)
	{
		const float d = sqrtf(distance2);
		position2goal *= (1.0f/d); // position2goal is now unit magnitude
 
		// vParallel and vNormal are component vectors of the
		// actor's velocity where vParallel is in the direction
		// from the actor towards the goal, and vNormal is normal
		// to vParallel.
		const float vP = actor->velocity.Dot(position2goal);
		Vec3 vParallel = position2goal;
		vParallel *= vP;
		Vec3 vNormal = actor->velocity; 
		vNormal -= vParallel;
 
		// Apply centripetal linear equal to -vNormal
		m_Steer.linear = vNormal;
		m_Steer.linear *= -1;
 
		// Calculate the magnitude of propelling linear, after centripetal
		// linear is subtracted from maxAcceleration.
		const float vN2 = (vNormal.x*vNormal.x + 
							vNormal.y*vNormal.y + 
							vNormal.z*vNormal.z);
		const float fP2 = maxAcceleration2 - vN2;
 
		// If the centripetal takes all our acceleration budget,
		// then just crop it.
		if (fP2 < 0) 
		{
			m_Steer.linear *= m_fMaxAcceleration / sqrtf(vN2);
		}
		else 
		{
			// Calculate the braking/acceleration distance
			const float gP = currentGoal->velocity.Dot(position2goal);
			const float brakingDistance = (d * m_fMaxAcceleration * 2 + vP*vP - gP*gP) / m_fMaxAcceleration / 4;
 
 
			// apply a smoothing function around the speed summit
			position2goal *= Squash(d - brakingDistance, sqrtf(fP2), m_fSharpness);
			m_Steer.linear += position2goal;
		}
	}
	else
	{   // if we are on target then adjust speed to match goal speed.
		m_Steer.linear = currentGoal->velocity;
		m_Steer.linear -= actor->velocity;
		float linear2 = (m_Steer.linear.x*m_Steer.linear.x +
							m_Steer.linear.y*m_Steer.linear.y +
							m_Steer.linear.z*m_Steer.linear.z);
 
		// If the linear is too big then crop it.
		if (linear2 > maxAcceleration2)
			m_Steer.linear *= m_fMaxAcceleration / sqrtf(linear2);
	}
 
	// Check if we are not breaking the speed limit
	if (m_pAgent->m_fSpeed > m_fMaxSpeed)
	{
		const float dp = m_Steer.linear.Dot(actor->velocity);
		if (dp > 0)
		{
			// remove the forward-pointing component of steer linear
			m_Steer.linear -= actor->velocity * (dp / m_pAgent->m_fSpeed2);
		}
	}
 
	// HACK: WOOHOO
	/*Vec3 newLinear = (*position2goal.Normalize());
	newLinear.x *= 5.0f;
	newLinear.z *= 5.0f;
	newLinear.y = 0.0f;
	m_Steer.linear = newLinear;*/
 
	float distance = position2goal.Length();
 
	Vec3 newLinear = (*position2goal.Normalize());
	newLinear.y = 0.0f;
 
	newLinear = actor->velocity + newLinear;
	if (newLinear.Length() < 20)//m_pAgent->GetCharacter()->GetDefaultSpeed())
	{
		m_Steer.linear = newLinear;
	}
	else
	{
		const float vP = actor->velocity.Dot(position2goal);
		Vec3 vParallel = position2goal;
		vParallel *= vP;
		Vec3 vNormal = actor->velocity; 
		vNormal -= vParallel;
		const float vN2 = (vNormal.x*vNormal.x + 
			vNormal.y*vNormal.y + 
			vNormal.z*vNormal.z);
 
		m_Steer.linear *= m_fMaxAcceleration / sqrtf(vN2);
	}
 
	// Calculate the angle we need to turn to point us in the desired
	// direction.
	const float dAngle = SmallestTurn(actor->orientation_xz,
		currentGoal->orientation_xz);
 
	// Calculate the angle we will turn through when accelerating to our
	// target rotation.
	float brakingAngle = (fabsf(dAngle) * m_fMaxAngular * 2 +
		actor->rotation * actor->rotation -
		currentGoal->rotation * currentGoal->rotation) /
		m_fMaxAngular / 4;
 
	if(dAngle < 0)
		brakingAngle *= -1;
 
	// apply a smoothing function around the speed summit
	m_Steer.angular_y = Squash(dAngle - brakingAngle, m_fMaxAngular, m_fAngularSharpness);
 
	// If we are rotating faster than we currently are allowed and
	// the new angular will make us accelerate even faster, then
	// set the angular to zero.
	if(fabsf(actor->rotation) >= m_fMaxRotation && actor->rotation * m_Steer.angular_y > 0)
	{
		m_Steer.angular_y = 0;
	}
 
	// Calculate the angle we need to turn to point us in the desired
	// direction.
	const float dAngle_2 = SmallestTurn(actor->orientation_yz,
		currentGoal->orientation_yz);
 
	// Calculate the angle we will turn through when accelerating to our
	// target rotation.
	brakingAngle = (fabsf(dAngle_2) * m_fMaxAngular * 2 +
		actor->rotation * actor->rotation -
		currentGoal->rotation * currentGoal->rotation) /
		m_fMaxAngular / 4;
 
	if(dAngle_2 < 0)
		brakingAngle *= -1;
 
	// apply a smoothing function around the speed summit
	m_Steer.angular_x = Squash(dAngle_2 - brakingAngle, m_fMaxAngular, m_fAngularSharpness);
 
	m_Steer.lookAt = currentGoal->lookAt;
 
	// If we are rotating faster than we currently are allowed and
	// the new angular will make us accelerate even faster, then
	// set the angular to zero.
	if(fabsf(actor->rotation) >= m_fMaxRotation && actor->rotation * m_Steer.angular_x > 0)
	{
		m_Steer.angular_x = 0;
	}
}