6
Nathaniel Baird 6/21/2016 Velo Write Up While working on past projects, specifically TRIAD, I found the task of designing intelligent AI intriguing and rewarding. One feature of this work that I felt most intimidated by was path finding. Unity has a built in “nav-mesh” feature which I used for AI path finding in both TRIAD and my zombie survival game. While working on these games I found myself frustrated with the system on multiple occasions. While the benefits of a convenient, readymade system were undeniable, characters would occasionally slide on the floor or rotate unpredictably. I decided that it would be worthwhile to attempt to design a path finding system from the ground up. I needed to start simple. Track cycling racing takes place on a banked oval called a velodrome. Using my personal experience with track cycling I attempted to create a moderately realistic simulation of the sort of behavior found in the sport. This behavior includes locating oneself as close to the inside of the track as possible (as it is the shortest distance to the finish line), passing other racers on their

Velo Write Up

Embed Size (px)

Citation preview

Page 1: Velo Write Up

Nathaniel Baird

6/21/2016

Velo Write Up

While working on past projects, specifically TRIAD, I found the task of designing

intelligent AI intriguing and rewarding. One feature of this work that I felt most intimidated by

was path finding. Unity has a built in “nav-mesh” feature which I used for AI path finding in

both TRIAD and my zombie survival game. While working on these games I found myself

frustrated with the system on multiple occasions. While the benefits of a convenient, readymade

system were undeniable, characters would occasionally slide on the floor or rotate unpredictably.

I decided that it would be worthwhile to attempt to design a path finding system from the ground

up. I needed to start simple. Track cycling racing takes place on a banked oval called a

velodrome. Using my personal experience with track cycling I attempted to create a moderately

realistic simulation of the sort of behavior found in the sport. This behavior includes locating

oneself as close to the inside of the track as possible (as it is the shortest distance to the finish

line), passing other racers on their outside, drafting other racers to maintain one’s stamina and

avoiding physical contact with other racers.

Originally I planned to assign each racer a ring of nodes that expanded and retracted to

reflect their trajectory around the track. If the radius of one ring approximated the radius of

another the two racers would be in danger of colliding. The racer further back would expand its

ring, providing it a path around the outside of the racer in front. However I quickly realized that a

more efficient node-based method would involve generating several concentric rings, called

‘lanes’, which each racer would adhere to at any given moment. I opted for a total of 5 lanes. The

following is the code I used to generate lane 2, which is comprised of 94 nodes. 94 nodes turned

Page 2: Velo Write Up

out to be the lowest amount required to properly execute the code responsible for determining

where racers are relative to their opponents (the space between each node is roughly 1 bike

length).

lane2 = new Vector3[94]; zVal = -8.75f; upperCurveAdd = 0; lowerCurveAdd = 0; startX += 1; for (int i = 0; i < lane2.Length; i++) { if (i < 20) { zVal += 2.1875f; lane2[i] = new Vector3(startX, 0, zVal);

} if (i >= 20 && i < 35) { upperCurveAdd -= 12f; Vector3 dir = Vector3.Normalize(upperCurve.transform.position - lane2[19]); dir.x *= -1; dir = Quaternion.AngleAxis(upperCurveAdd, Vector3.up) * dir; lane2[i] = upperCurve.transform.position + (dir * startX); } if (i >= 35 && i < 67) { zVal -= 2.1875f; lane2[i] = new Vector3(-startX, 0, zVal); } if (i >= 67 && i < 82) { lowerCurveAdd -= 12f; Vector3 dir = Vector3.Normalize(lowerCurve.transform.position - lane2[66]); dir.x *= -1; dir = Quaternion.AngleAxis(lowerCurveAdd, Vector3.up) * dir; lane2[i] = lowerCurve.transform.position + (dir * startX); } if (i >= 82) { zVal += 2.1875f; lane2[i] = new Vector3(startX, 0, zVal); } }

The lane is entirely two-dimensional with no concern whatsoever for the y-value (or

height) of the nodes. Rather than worry about the geometry of a banked oval I established the y-

value of each racer by capturing the location of the track mesh below. The racer’s y-value, then,

equals the y-value at which a raycast intersects the track.

Page 3: Velo Write Up

With this system in place racers were capable of traveling around the track in their

respective lanes. The next task was to create systems for determining when racers should adjust

their speed or change lanes.

A central game manager keeps track of every racer in an array. Periodically racers run

through the array and check the nodes and lanes that other racers occupy. The first component in

the check determines whether another racer in the same lane occupies a node slightly larger than

the one currently occupied by the racer (a node is occupied if it is the node which the racer is

currently traveling toward). If the racer determines that they are blocked it does a check on the

lane above. If the lane is clear the racer moves up for a pass, if it too is occupied the racer

decreases its speed until it matches the racer in front. If the racer is not blocked in front (or is

blocked by a racer going the same speed or faster) the racer checks the lane below, and if safe

transitions into it. The following is the code which checks to see if a racer has room to move up a

lane.

IEnumerator CheckUp() { yield return new WaitForSeconds(timer); bool front = false; bool safe = true;

for (int i = 0; i < racerCount; i++)//check if racer is above { racerA = gameManager.GetComponent<RacerPositions>().racers[i]; if (racerA.GetComponent<TrackRingPathing>().lane == GetComponent<TrackRingPathing>().lane + 1) { if (racerA.GetComponent<TrackRingPathing>().nodeInArray >= currentNode && racerA.GetComponent<TrackRingPathing>().nodeInArray - 1 < currentNode) { safe = false; } if (racerA.GetComponent<TrackRingPathing>().nodeInArray <= currentNode && racerA.GetComponent<TrackRingPathing>().nodeInArray + 1 > currentNode) { safe = false; } } } if (safe == true) { GetComponent<TrackRingPathing>().MoveUp(); }

Page 4: Velo Write Up

StartCoroutine("Choose"); }

I then added additional conditions under which racers can adjust their speed. Racers are

initiated with randomly generated sprint and attack speeds which determine their maximum

speeds in different circumstances. I implemented a stamina value which decreases gradually so

long as the racer is not drafting another racer. If the stamina is low enough the racer’s speed

begins to decrease. This gives races an ebb and flow; racers which get an early lead stand a good

chance of falling behind later in the race. On the final lap racers accelerate towards their

sprinting value. This system added new potential for the racers to make “mistakes”. For example,

if a racer is not initially boxed in but then suddenly is they may not have enough time to slow

down and will crash into the racer in front of him. I did not consider these features undesirable,

since in track cycling (a sport in which racers have no breaks) these sorts of scenarios actually

occur.

The final task was to add a human racer into the pack. The player is not ‘on rails’ in the

same way the AIs are. I devised a script that checks with the game manager frequently and

approximates the lane and node the racer would be at if they were on rails. The result is a system

in which the AI rarely cuts into the player and is capable of drafting off of him. Occasionally the

AI will invade the player’s space slightly due to the overlap in time between the various script’s

cycles (occasionally a script will not approximate the player’s location soon enough for the racer

to react to being blocked). I consider this a fine side effect of my programming, and it is one I

could easily tighten up by speeding up the rate of processing. But bike racing is a messy sport

and racers make mistakes, so I left it as it currently is.