Learning UnrealScript Part 4 — Building an AI Class

This post is long overdue, mainly because I was unsure of what direction to take the AI we’re trying to make. As my programming professor always says, “define your goals before you start coding”. Turns out, he’s right.

Before we get back to MyBot.uc, let’s digress for a moment and learn more about the class tree. When we last touched upon this subject, we learned that UnrealScript builds upon functionality using this tree, where each successive branch does more and more things. However, it also helps to look at each of the main branches and how they will affect what you code; from UI scenes and HUDs to vehicles, each job will require you to focus on different parts of the tree.

First, let’s go all the way to the Core. In this folder is the mother of all the classes: Object.uc. It has only the most basic functionality for an UnrealScript class. Will you ever use it? Yes — I extended Object to make a save system. In general, if your class is so abstract that it’s not even a part of the game anymore, you could probably extend Object and get away with it.

No matter what you’re doing, there are a few classes that you’re almost guaranteed to use (Game, Pawn, and PlayerController). The reason for this is that these classes define pretty much everything else. In Game, for example, you specify what HUD, GRI, PRI, on-screen messages, and bot classes to use. In Pawn you control the Inventory, animations, and skeletal meshes. In PlayerController you control the input, camera, and (interestingly) achievements. Mess with any of these things, and you’re going to have to create a custom Game, Pawn, or PlayerController to tell UDK that you made a change. Otherwise, it won’t work.

Beyond that, it’s pretty easy to see where you have to put your code. Making a new HUD? Extend UTHUD or GFxMoviePlayer. Making a new weapon? Extend UTWeapon. Vehicle? UTVehicle, of course. How about a new gametype? Duh, extend UTGame!

One tool that’s been indispensable to me is Codekisk. This brilliant site is basically a code dump of the entire \Development directory, only organized with hyperlinks and descriptions and a whole load of other great goodies. The code is a bit dated (as far as I know, it’s still circa UT3), but even then 90% of the functions haven’t changed. When I’m coding and I have no idea where to put a function, I just hop on over to Codekisk and jump around until I find the answer. Three words: CHECK IT OUT!

Alright, I’d say it’s time to get back into MyBot.uc. So far we’ve modified the AI to be extremely aggressive, but I wouldn’t say that’s a huge improvement over anything already in UDK. I’m not quite sure we’re ready to do anything truly fancy, so how about…oh I don’t know, an AI that annoyingly follows the player around?

First, clear out all that aggressive nonsense we had in there last time; we’re starting fresh for this one. Now before we do anything, we’re going to have to understand two new elements of UnrealScript: events and states.

An event is basically a function, except instead of saying function [type] [name] ( [parameters] ) {}, you say event [type] [name] ( [parameters] ){}. The big difference between an event and a function is that an event is time-independent. For example, the event PostLogin() in UTGame.uc is called after the login process is completed. But how long does this login process take? Two seconds? Ten seconds? A minute? If PostLogin() had been a function instead, it may very well have been just “ignored”. UDK would have said, “Well, here we are at PostLogin() but the login isn’t done. Oh well, too bad. *skip* ” Events solve this problem, because now UDK says, “Well, here we are at PostLogin() but the login isn’t done. I’ll wait till it is and come back then.”

States are even cooler. To declare a state, all you do is say state [name] {}. The name should be some sort of action, like “running” or “idle”. What’s cool about states is that they modify other functions. To understand this let’s take an example. Suppose we define a function bool CanReload() {}, and we want it to return true if it’s possible to reload but false if the bot is roadie-running. We could have a special variable called bInRoadie that states whether the bot is roadie-running, and then negate that in CanReload(). But suppose we had a whole bunch of these functions that don’t work during roadie-running (for example, you can’t jump or wall-hang or shoot while roadie-ing)?

A much better solution would be to define a state Roadie {}. In this state, you’d say function  bool CanReload() {return false;}. Now, when the bot is roadie-running and it checks to see if it can reload, it will check the Roadie version of CanReload() and see that it can’t. As you can see, you can have as many versions of CanReload() as there are states, and each one can do a different thing.

So how does this help us make an annoying AI? Well, what we want to do first is control the movement of the AI since we want it to be following the player around. To make sure this works it helps to reset the movement physics of the bot like so:

event Possess(Pawn inPawn, bool bVehicleTransition)
{
super.Possess(inPawn, bVehicleTransition);
Pawn.SetMovementPhysics();
}

How would you know to do this, having never worked with the AI system before? It’s called the UDK Forums 😛 Seriously, they help!

Alright, next we need to define a base state for the AI to be in when they start the match. To do this we use the keyword “auto” before the word “state”, which says “automatically put the bot in this state”. In this case the state we want to start with is Idle:

auto state Idle
{
event SeePlayer(Pawn Seen)
{
super.SeePlayer(Seen);
Target = Seen;
GotoState('Following');
}
Begin:
}

Let’s break this down. The “Begin:” keyword tells the state what to start doing when the bot is in this state. Obviously we want the bot to do absolutely nothing, so Begin: sits all by itself at the end. Then we have the event SeePlayer(). This is an event defined all the way back in Engine.Controller that is called after a bot sees the player (in other words, you). Once the bot sees you, we want it to start following you, hence the call to GotoState(‘Following’). This tells the bot to jump to the Following state. But what’s this “Target” junk? Well, define this global variable:

var Actor Target;

Now the bot has some added functionality: it will keep track of the pawn it’s following! Now it’s time to make the bot follow you:

state Following
{
Begin:
MoveToward(Target, Target, 128);
goto 'Begin';
}

Simple, right? We simply move toward the target a tiny bit, find the target again, and move toward it in an infinite loop. In this way the bot always keeps track of where you are, even if you turn or double back.

And now for the moment of truth…recompile and test out. How’s this new AI? I’d say it…

SUCKS O_o. Really. Bad. For one thing, it doesn’t really shoot at you. It also stops following you once it loses sight of you; go behind a wall and it’s all over for our friend, the AI. Obviously we need to make some SERIOUS improvements here. But hey, it’s a start, and we picked up some valuable knowledge of events and states along the way! Next up, we’ll begin to look at some nifty functions in a whole bunch of classes, as well as refine our current “following AI” system.

Previous: Your First Class
Next: Understanding the World

13 thoughts on “Learning UnrealScript Part 4 — Building an AI Class

  1. Pingback: Learning UnrealScript Part 3 – Your First Class | WillyG Productions

  2. Pingback: Learning UnrealScript Part 5 — Understanding the World | WillyG Productions

  3. Hey
    I’ve put the code in the MyBot.uc file but when I spawn the Bot in UDK and use MyBot as the controller it doesnt follow me at all, it just stands there. Everything compiles correctly, give me no error, and I have changed the game type to Mygame , but still the bot just stands there. Do you know how to fix this?
    Thanks

    • Hmm not sure what’s going on…if it just stands there that means at least you’ve managed to get the bot to be MyBot. Try to add a `log() statement in the event SeePlayer() and see whether or not that function is actually being called. If not, you may have to extend from a simpler class like UDKBot or AIController instead.

      • Hey
        I tried putting `log() in the code, but now it won’t compile. I’m probably doing something wrong, is this what you meant? :
        event SeePlayer(Pawn Seen)
        {
        `log();
        super.SeePlayer(Seen);
        Target = Seen;
        GotoState(‘Following’);
        }

    • a `log() must have something in it, such as `log(“Hello world!”); What this will do is print this string in the log files in \UDKGame\Logs. This is useful for telling whether or not the code actually reached that point…so for example if you put `log(“Inside See Player”); and you see “Inside See Player” in the log file, you’ll know it actually called SeePlayer(). Hope that helps!

      • This is what i’ve done:
        auto state Idle
        {
        event SeePlayer(Pawn Seen)
        {

        super.SeePlayer(Seen);
        Target = Seen;
        GotoState(‘Following’);
        `log(“See player”);

        }

        but I do not see “See player” in the logs, everything compiles correctly and I change the gametype to mygame in UDK but the AI still just stands there. I change the controller class to MyBot and the Pawn class to UTPawn, not sure what I’m doing wrong.

    • Well…the log will never get called because you put it after GotoState(‘Following’);…so the code will jump to the Following state before logging anything.

      Try email me the full code you’re using to willyg302@gmail.com, I will take a look at it and see if I can spot anything.

  4. If I wanted to create a completely new game with not link to Unreal Tournament, wouldn’t I have to extend UDKPawn instead of UTPawn and UDKController instead of UTController etc.

  5. This design is steller! You definitely know how to
    keep a reader entertained. Between your wit and your videos, I was
    almost moved to start my own blog (well, almost…HaHa!) Great job.
    I really enjoyed what you had to say, and more than that, how you presented it.

    Too cool!

Leave a comment