Gaming Your Way

May contain nuts.

New Test Level and Goon AI

Fuck funky headlines. So I said it.

Today's post is all about ... the new test level and new goon AI (mhm, maybe back to funky headlines?).

Let's start with an image:

The old test level with two elevators and some searchable objects (grey boxes).

While this worked well enough the random / percentage based goon AI it didn't look good on the new test level. One reason might have been the way I've dealt with entering/exiting the map. In short the goon wanders around until he reached the number of steps he should do and then takes the next exit he comes across. For the smaller test level this was OK, but with the much wider new level this revealed some problems. Due to the random nature, goons sometimes would just walk back and forth between a door and the elevator until it reached the required steps to exits again.

This made me rethink the way the goon walks around and I settled on the idea that it would (and does, comes to that) look better if the goon has a predefined path to follow. 


The new test level, also shown is the potential path I want the goon to follow and the new elements.

Of course this added some more things to think about, most obvious is that the goon needs a path to follow once an entry and an exit have been assigned.

As Unity comes with a nice built in NavMesh, I tried to get away with that...

... and as soon as that was in, it showed that this won't work "out of the box".

To make that story short: it works just fine as long as you can work with a continuous path, but the different floors and the elevators proved to require a lot more work than I was willing to put into. One of the "easy", but tedious solutions were using Off-Mesh Links, so the NavMesh Agent would know how to reach the different floors. As far as I found out (prove me wrong) there's currently no way to create Off-Mesh Links on the fly via code.
Creating those by hand in the editor would have made a brave man cry (I gave up after a few). For each elevator, one link between the left and the right side and for every floor AND every side, so for a 3 floor elevator: 15 Off-Mesh Links (if I counted right). 

Erm. No.

Time for plan B.

Plan B was easy, just create nodes for my a* graph based pathfinder. 

Well, creating nodes was easy, as I already had a lists of all doors, elevators and the other stuff. 

Connecting these is a different story (in a way that makes sense). Until now the map elements (which the goon could interact with) only stored the current "block" (needed later when blocks will be combined to build a bigger level) and I really didn't want to add a "connected to" list or a "floor".

So the (nasty) solution was to read out the coords and store these in a PointInt3D (my simplified int Vector3 version) by multiplying the position by 10 and reducing the y value to the floor (ie. 0, 1, ...). 

For instance an elevator creates X nodes (yellow dots, one for each floor): like [0,0,0], [0,1,0] and [0,2,0]. While I'm at it, I also connect these (so the floors are connected, too).
Then I looped over all floors and and created a temp list of the elements per floor, this list would then be sorted using the x value and viola ... all nodes per floor in order from left to right. Now simply connect Node[i] and Node[i + 1] and the floor is connected. As I had created the links between floor earlier, creating the complete graph (red lines) was a no-brainer.

Of course it isn't quite as easy as this, just look at the map and notice the green line marked "No! No! No!". I fixed this by .... well let's just say it is a dirty trick...

The path (blue line) is created by a simple "Pathfinder.GetPath(StartNode, EndNode)".

How the goons deal with that is for the next post, as this one is already long enough.

-- Olli


Sibling Rivalry and How to Stop It

Having three goons running around, but only a limited number of elevators is calling for trouble. So in order to make that work ("Only one goon per elevator, please"), we had to come up with something really clever (well, almost).


The first obvious option might have been to make the goons talk to each other and let them decide which of them will enter the elevator. Not the easiest solution and there would have been a lot of back and forth between the goons just to make sure they talk about the same elevator.

The second option was a central piece of code that sorts goons and elevators, group the ones belonging together and then decides which one will enter the elevator (not a piece of cake either).

Luckily, option three is just what we need, it's almost clean, it is simple and if it fails it sorts out itself after a while.
Last post I mentioned that the goons look ahead and "decide" what to do with the target (doors, walls and elevators [and player]), so we use this to lock the elevator as well.
I added a simple flag ("claimedBy") to the elevator code (which makes them move up and down) and that was it (OK, not quite, but mostly).

With this in place, only a few things needed to be added to the goon's AI:
If a goon "sees" the elevator and the elevator is not currently claimed, the goon claims it.
If the goon decides to turn away from the elevator after he claimed it, the elevator is released. If the elevator is already claimed by another goon, ignore it and check again next step. If the goon is killed, check if the elevator needs to be released.
That's it.

Let's code these shiny UI icons and make them work, too.

-- Olli

Go left. NO! Not that left, the other left!

Let's start with a screenshot of the recent game prototype:

The testbed for the goon's AI.

Even though it's only 2D, there's a lot going on to let the goons walk around the map on their own. Handling their normal way is easy enough, it is just looking ahead 2m and then decide what to do next (mostly broken up into multiple random chances).

If the goon sees a door, it is set as next target and he start walking into that direction (although there's a check every now and then to see if the player can be shot). After he reaches the door there aresome things to decide:

  • Have we reached the max number of steps (90% chance of using the door to exit the stage)
  • if not, there's 10% chance of an early exit
  • ... and a 5% chance of turnung around
  • otherwise pick the next target
Doors are easy.

Elevators on the other hand trigger a lot more possible actions (heavily simplified):

  • Is the elevator on the same floor? (and is it empty?)
    • Enter, pass or turn around
  • otherwise:
    • is the elevator coming towards our floor (90% waiting for it)
When the goon is finally in the elevator there are still a lot of things to check:

  • are we heading towards the player's floor? (keep going until we reach him)
  • no? (chance of riding another floor or exiting the elevator)
  • are we at the top or the bottom (90% chance of getting off, but which way?)
  • ...

Here's an image of an early draft for the goon AI:

And the first playmaker based version (I since moved that over to code)

The code version is in a way easier to maintain than the playmaker version, but also less easy to track when you want to know what's going on.

And with this, I need to get back to the goons ...
--Olli

How the MTR car AI works ...

... or should, once I find the time to finish that slog. 

I must admit it was one of my worst ideas to take a stab at a racing game (and sell it to a client). The only luck so far is that it's a "when it's done" deal, so there's no deadline on this except my own wish to get it done and collect some cash for the client version.

The basic idea to do a "quick" little racing game wasn't that bad, but I really, really underestimated the amount of work I had (or rather still have) to poor into it. One of the most time eating tasks still is writing the car AI, which completely didn't appear on my radar until it was way to late.
I've written my share of AI code so far, so I thought this one wouldn't be much different. I think I was wrong with that.

The main big different with what I have written so far is that character can move freely, if something blocks the way, turn left or right and continue moving until you can resume the old path (or just run the a* again to find a new way to the player). For cars this doesn't quite work this way (now that's a surprise).

So cars need to use throttle to speed up and breaks to slow down you need to use steering angles to change direction and you must obey the limits (otherwise it'll looks like cheating). Moving the car along the track is quite easy if you have a fixed track, just draw a spline and let the car move along it. Too bad MTR has a built in track editor, so we need to construct the path for every track. (I covered this in an earlier post).


Waypoints for the AI.

Trouble enters the equation when we add other cars to the track and we cannot follow the waypoints anymore if some other car blocks the path. I calculate the throttle / steering values in steps, blending the values in the end (and then blend the desired values with the current ones).

First step is looking at the waypoints ahead and decide which lane is the shortest (using floating waypoints), then the desired throttle is calculated based on the distance between the last waypoint we've passed and the next. The shorter the distance the slower the car should go, I use 100% for the max distance and 75% for the smallest distance). 

Next step is deciding what angle we turn the wheels in order to head towards the next waypoint and if the angle is greater than the allowed one, we reduce speed further.

After this I have a basic throttle value (say 90%) and a steering value (say 20%, of the max steering angle).

 


Damn obstacles.

Now it gets a bit more complicated, we need to look for obstacles that might block the path we've just selected. The image above shows the problem, the red line shows the desired next spot we want to reach, the yellow line shows the current throttle, with the dark blue line showing the max throttle (but that's only for debugging). Important is the cyan line that is shown at the back of the green car, as it shows where we would hit the green car if we would follow the current path.

Right now, if this happens I offset the waypoint until we can pass the green car (adjusting steering to -20%), but only if there is enough room (there are also checks to see if we can pass on the left or right side of the obstacle), if we cannot pass we need to reduce speed to stay behind it.

Now just blend the values and pass them to the car and repeat this every time we hit a waypoint or another car is close enough to overtake.

If I ever get that video grabber to run I'll post a video next week.

You can't make an omelette without breaking an egg

(from: Some Like It Hot)

Even though the title is a quote from a movie, it also is very true for development. As mentioned last post I was about to rewrite (again) parts of the game to make it easier to maintain and debug.

One of the bigger changes is that I moved states from code to a visual finite state machine (FSM). Honestly it was one of the better ideas I had since I started with a game I have virtually no "knowledge" of (because I really don't like racing games). As a result I killed about 2/3 of the code in my "RaceController", reducing it to (currently) ~300 lines of code.


Early version of the RaceController FSM.

The final version is a bit more complex, though. To be true this doesn't count in the code needed for the FSM, but *my.* code is a lot shorter now.

For the AI code however it is something different and I found it a fine line between using the FSM and handle things through code. I had to decide whether to make "Reached waypoint, what now?" a simple method call or a state. The AI is done in code now as most events coming in just alter target values that are dealt with in the main loop of the AI.

In any case this makes the AI a tad more robust when dealing with corners, speed and lanes. For example there is a value that keeps track of the lane the car is on (0 being the inner lane, 1 the outer and 0.5 the middle of the road). When the AI decides it wants to change the lane from 0 to 1 (because in corners the inner lane is faster) it sets the fTargetLane to 1 instead of fLane (the variable that holds the current lane we're on). Before easing the lane value the AI can now check if there is room to change the lane (another car blocking the way) and delay easing. Same goes for speed and steering.

Easing between current and target value also allows to add some "personality" to the AI, but that's stuff for a later post.

"All right, driver, once around the park, slowly, and keep your eyes on the road."
(again, Some Like It Hot)

-- Oliver / nGFX 

New rules ...

It seems that I'm now a proud member of the Iron Blogger Community, in my personal case it's http://www.ironbloggerruhr.de/. The idea is to write at least one post per week or you have to pay a fee (which is used to buy beer when we meet once a while).

Announcing that I'm now a member of an Iron Blogger "chapter" counts as a post for that week, but that would be a bit lame.

I'd like to post some progress on the MTR racing game and as a surprise ... I even have to report some progress - sort of.

I'm not sure if I mentioned how the AI in MTR works, but I guess I might repeat myself to get to the point.
When I posted the last time the AI was using waypoints to move along the track and not, say, a fixed spline (which would be oh-so-much easier). I use waypoints, because there is a track editor build in and players can build their own tracks, if fact I do some blending between waypoints so it's nearly a spline.

Anyway, the trick is to actually hit the waypoints when moving along the track, because sometimes you're coming a bit to fast down a ramp and the "hit waypoint" radius is just the few digits to small and so you missed your target waypoint. Detecting that is quite easy - if the allowed angle between driving direction and car/waypoint is too big, we simple jump on to the next waypoint in list and continue driving. The problem is: what do we do if for some odd reason the car turns a bit too much before it hit the waypoint (crashed into some other car maybe) - in this case it all can go berzerk faster as you can mumble "fuck". 
And then there are the other cars, having 4 cars chasing the same waypoint always ends up in a crash and then in missed waypoints ... the obvious solution is to prevent crashes, but that doesn't always work out like planed, too.

The first change I made, was to add 2 more waypoints and so having "lanes" on which the cars drive - this worked better. I created waypoints on the left, right and center of the road and give the cars a "lane" (ie: 0,1,2) on which the want to stay. As far as you want that all is good, but once you want to change lanes things get complicated again.

The current version uses a "floating" waypoint, it goes from the left side of the road to the right side and the AI cars use a float (0-1.0f) to see where they want to hit the wayoint (this also allows to use a "desiredLane" value and blend the current lane with the desired one over time). Now the AI cars cannot miss the waypoint any more, they just can miss their desired lane).

Phew, a lot of words, time for an image.

This is what I've been working on for the last couple of weeks (and prevented me to do any real work on MTR): animating and rendering a lot of small animations showing smoke and fire (sorry no further explanation just yet).

And with this, see you all next week.

nGFX

So how does the NPC AI in Outpost:Swarm work ?

Now Outpost:Swarm is live I thought it may be an idea to explain how I did your in-game partners AI.

If you've ever read up on Boids you'll know they have 3 simple rules,

Separation

Alignment

Cohesion

And all the examples you'll see are bird like objects flying around, maybe towards your mouse pointer, maybe avoiding obstacles. All seems simple enough. Adding them to a real game however quite a bit trickier.

For separation we have to ensure the NPC is avoiding both the player and all the baddies. Firstly we find the distance to the players sprite using a simple

distance=dx*dx + dy*dy;

Like you would in your usual circle to circle tests. If we're too close then we need to repel the NPC from the player, via:

tmpPoint1.x+=dx+dx;

tmpPoint1.y+=dy+dy;


Where tmpPoint1 is just a new Point(0,0);

That's part 1 of the test done, the second is checking against all the baddies, and there can be a load at any one time. What I did was use a flip flop, every even frame we get a list of all the possible neighbours ( Baddies which are close enough to care about, if they're on the other side of the screen then we can skip them ), every odd frame we do exactly the same distance check as we did above.


Finally we divide our Point value,


tmpPoint1.x/=speedDivisor;

tmpPoint1.y/=speedDivisor;

( private var speedDivisor:Number=20; )

 

This keeps the values within a respectable range so we don't move too fast.

Outpost:Swarm

The next rule is alignment. Lucky for us we don't care about that in this case, we're not creating a flock of birds or swarm of insects, we just want one guy to look fairly smart and follow his friend around.

Cohesion in this case means following. We do another distance check to the player, but this time with a greater radius ( We want them close and for the NPC not to lose sight of the player, but we don't want them virtually kissing. That's planned for the sequel, Outpost:Date&Fuck ).

var dx:Number=bodyXPos-targetX;

var dy:Number=bodyYPos-targetY;

var distance:Number=dx*dx + dy*dy;

if(distance<3000){

  tmpPoint2.x=tmpPoint2.y=0;

  return tmpPoint2;

}

 

tmpPoint2.x=(targetX-bodyXPos)/speedDivisor;

tmpPoint2.y=(targetY-bodyYPos)/speedDivisor;


Note the use of targetX/Y, rather than PlayerX/Y, we'll come back to that at the end of my presentation. As you can see, it's pretty much the same thing as before, and something I'm sure most of you have done with your circle to circle checks.


Right, we've got two Points after running our rules, time to calculate the NPC's velocity.


velocity.x=rule1V.x+rule2V.x;

velocity.y=rule1V.y+rule2V.y;


Then we do a quick test to make sure the velocity in both directions isn't greater than the max speed we've set for the NPC, we don't want him outrunning the player ( Or actually, not to outrun the player enough that people will notice ).

The next part was the tricky one, we can move him around fine, but what about the walls ? It turned out to be surprisingly simple. We use good ol' tile based checks. If there's a wall on the horizontal of the NPC we set velocity.x=0; and then the same with the y. 

We're finally there, we just finish off with

sprite.x+=velocity.x;

sprite.y+=velocity.y;


Cool, and that's how you can use Boids in real life ( The same principle handles the baddie movement ).
 
Let's test that out in game. Excellent it works, and if I manage to lose the NPC behind a wall then... oh tits, he'll just sit there. That's not a great look for a hard as nails space guy with a big ass gun.
 
Check this level map out,  

AI path

Within each level we lay down a path for the AI. Every couple of frames we look to see if the NPC can "see" the player using a line of sight. If he can't it means we've ditched him behind a wall.
When that happens we fire out 4 rays from the NPC until it hits one of those path tiles ( Remember at the heart of the game is a tile engine running alongside the purely physics based one ). Once we've done that we can easily find out which is the nearest part of the path, we change the TargetX/Y ( From above ) to our tile and the same code that moves him towards the player moves him towards the path.
All the time we're moving towards the path we're doing our line of sight checks to the player, if we spot him again we break off from the path and go back to following the player.
 
It's not fool proof, if he gets lost then he's only going to make his way to the path and then not do anything really clever, but because the levels are small enough you should soon bump into him again to get him following the player. Also, he doesn't really need to be that smart when he's off on his own, we just want to create the illusion that he's not a dumb arse, he doesn't need to be Stephen Hawking ( Perhaps not the best example when talking about movement ). Finally, we're still using Flash, we don't have all the CPU time in the world to do something really fantastic.
 
And that's how we move the NPC.
 
Squize.
 
PS. Wow, looks like I've got about 40 fonts / sizes in this, nasty, but it took enough time to write this up, I'm going to swallow it looking slightly ugly.