Hello, everyone! I got bored tonight and decided that I'd make another scripting tutorial.
Plystire's AI Scripting Tutorial
Let's start by brainstorming. (As with all creations, this is the best place to start)
Most people want to simply make AI but don't know where to start, so let's start by imagining what we want our AI to do.
I think a smart AI should be able to patrol an area via waypoints, shoot at the player if it sees them, and follow the player once it has already started shooting. Also, I believe a smart AI won't stand there taking hits all day, so I want it to strafe around randomly so that it'll be harder for the player to get a hit in. I bet a further target would be a harder to hit target, so I don't want the AI to get very close to the player.
Alright, brainstorming over with for now, let's make a list! (Because I know you all love lists, am I right?
)
Things our AI should do:
- Patrol Waypoints
- Start shooting player if it sees them
- Strafe randomly if close enough to player
- Follow player around, but don't get too close.
That sure SOUNDS easy, but let's start scripting and find out how easy it's really going to be.
STEP #1: Patrolling Waypoints
Alrighty, first thing on the list is to make it patrol waypoints.
Let's take a look into the manual and see what scripting commands we can find that are related to waypoints.
.
.
Conditions:
Quote: "WAYPOINTSTATE=X
is true when the waypoint state value equals X.
State 0 means the entity has not yet started following
waypoints
State 1 means the entity is looking for the nearest waypoint
marker to start from
State 2 means the entity is following a waypoint line to its
current waypoint marker
State 3 means the entity has reached the waypoint marker and
needs to decide what to do
State 4 means the entity has reached the waypoint marker and
it splits off in more than one other direction
State 5 means the entity has reached the very end of the
current waypoint structure and requires a decision to be made.
A state of 999 means the entity has been placed in zero-
waypoint mode where the entity simply ignores waypoints."
Wow, that's a lot to read, but the more detailed the description the better, as I always say.
Actions:
Quote: "WAYPOINTSTART
instructs the entity to find the closest waypoint
WAYPOINTSTOP
stops the entity following waypoints
WAYPOINTREVERSE
makes the entity reverse course and go the other way
WAYPOINTNEXT
instructs the entity to find the next waypoint
WAYPOINTPREV
instructs the entity to find the previous waypoint
WAYPOINTRANDOM
instructs the entity choose a random waypoint direction"
Awesome! We got lots of actions to choose from, so this will make it easier on us.
I think we have enough information now to start coding our Waypoint Patrolling section of the script.
To start off, let's think of how we can mesh these commands together to get our AI doing what it should be doing.
When it first spawns it should begin it's patrol. We know that when it first spawns, it isn't following a waypoint, right? So, then we can safely assume that our AI's "waypointstate" is going to be 0, according to what the manual says.
With that in mind, we can come up with this piece of code:
:waypointstate=0:waypointstart
Deng, that was easy.
Now that he's on his way to the nearest waypoint, his "waypointstate" should be equal to 1. That's fine, we don't want him to be doing anything different while he's already walking.... WAIT! Walking? We need to make him walk, too! Otherwise, he'll just glide along his merry way.
Let's find us some animation commands to make our AI look like he is walking.
The manual gives us lots of commands to deal with animating, but only one in particular will do what we want here:
Quote: "ANIMATE=X
automatically play animation X.
In regards to characters provided, there are a number of
animations built in which can be played. These are:
0 = Spawn
1 = Idle
2 = Move Slow
3 = Strafe Left
4 = Strafe Right
5 = Move Fast
6 = Reload Weapon (or Toss)
10 = Climb
11 = Impact Front
12 = Bounce Front
13 = Get Up Front
14 = Impact Back
15 = Bounce Back
16 = Get Up Back
17 = Impact Left
18 = Bounce Left
20 = Impact Right
21 = Bounce Right
31 = Crouched Idle
32 = Crouched Move Slow (same)
33 = Crouched Strafe Left (same)
34 = Crouched Strafe Right (same)
35 = Crouched Move Fast (same)
36 = Crouched Reload Weapon (or toss)
40 = Freeform Idle
41 = Freeform Move
50 = Weapon Spawn
51 = Weapon Idle
52 = Weapon Move Slow
53 = Weapon Strafe Left
54 = Weapon Strafe Right
55 = Weapon Move Fast
56 = Weapon Reload Weapon (or Toss)
57 = Weapon NEW Climb
61 = Weapon Impact Front
62 = Weapon Bounce Front
63 = Weapon Get Up Front
64 = Weapon Impact Back
65 = Weapon Bounce Back
66 = Weapon Get Up Back
67 = Weapon Impact Left
68 = Weapon Bounce Left
70 = Weapon Impact Right
71 = Weapon Bounce Right
81 = Weapon Crouched Idle
82 = Weapon Crouched Move Slow (same)
83 = Weapon Crouched Strafe Left (same)
84 = Weapon Crouched Strafe Right (same)
85 = Weapon Crouched Move Fast (same)
86 = Weapon Crouched Reload Weapon (or toss)
90 = Weapon Freeform Idle
91 = Weapon Freeform Move"
Whoa! That's some heavy stuff goin on right there! But, we got what we wanted. A single command to automatically play (and loop) any animation that the entity has!
Hmmm, looking in the list, animation number 52 (Move Slow) looks like what we want, so let's put it in.
:waypointstate=0:waypointstart,animate=52
Awesome, now our Ai will have a walking animation while he goes to his first waypoint!
Now, once he gets there, what is he going to do? And how can we tell if he's there or not?
Well, the "waypointstate" should be equal to 3 if it reached the waypoint. So, once it gets there let's have it move to the next one in line. And, while we're at it, let's make him animate the walk also.
:waypointstate=0:waypointstart,animate=52
:waypointstate=3:waypointnext,animate=52
Alright, that takes care of that! Now our Ai should be finding waypoints and following them until the end. Speaking of end, we gotta make sure the AI knows what to do when it gets there!
If the AI is at the end of the waypoints, the "waypointstate" should be equal to 5, right? Welp, once it does equal 5, let's have it reverse direction and go back.
:waypointstate=0:waypointstart,animate=52
:waypointstate=3:waypointnext,animate=52
:waypointstate=5:waypointreverse,animate=52
There, that should do it. However, this will only work if our AI is following a stream of waypoints that don't have multiple paths to take. Let's make our AI follow a random waypoint if it comes to a fork in the road... so to speak.
If it finds a branch, then the "waypointstate" will be equal to 4, according to what the manual says:
:waypointstate=0:waypointstart,animate=52
:waypointstate=3:waypointnext,animate=52
:waypointstate=4:waypointrandom,animate=52
:waypointstate=5:waypointreverse,animate=52
Okay, that looks like it'll handle pretty much everything we want it to.
What's next on the list?
Step #2: Shoot Player if Seen
Now that our AI is a Waypoint walking fool, let's have it shoot the player if it sees them.
As per our protocol, let us consult the manual for any useful commands!
.
.
Conditions:
Quote: "PLRCANBESEEN
is true when player can be seen
PLRCANNOTBESEEN
is true when player cannot be seen "
Actions:
Quote: "ROTATETOPLR
rotate the entity to face the player
SHOOTPLR
runs the internal FPI script SHOOT specified in character properties"
Okay, looks like we got our work cut out for us here. But, not to fear! Your faithful leader will get us through this!
I gotta explain a couple things here. Firstly, to keep our scripts clean, we don't want to pile ALL of our AI's code into one script. You'll notice that the action "shootplr" will call up a different script. We'll code that script as well, and it will handle the chasing and shooting of the player. Right now, we need to determine if we should start using that script or not. With that said, HERE WE GO!
First up, we gotta find out if the AI can see the player. We can use the "plrcanbeseen" condition for that. Then, if it CAN see the player we want it to rotate to the player and start shooting!
However, our AI is currently following waypoints, so we need to tell him to stop, and also to stop doing it's walk animation! Well, according to our list of animations, animation number 51 (Idle) is the one we're looking for.
Sounds like a plan, so let's script it!
As per proper scripting, we don't want everything to happen at the same time, so we need to break it up into states:
:state=0,plrcanbeseen:state=1
:state=1:rotatetoplr,waypointstop,animate=51,shootplr,state=2
Okay, so now our AI knows that if it sees the player it should stop walking the waypoints, play the Idle animation, and run it's "Shoot" script, which is given to it in it's properties.
Alright! Now, we'll assume that our AI has spotted the player is now running it's "Shoot" script, so now we have to make it!
Step #3: The Shoot Script, Following and Firing
Let's open up another Notepad (or your preferred FPI editting tool
) and start to work on our Shoot script.
In this script, we already know that the player has been spotted and we should have already rotated to face the player.
What should happen now?
Well, we need to take some situations into account, and for this, we're gonna make a list:
- The Player is far away
- The Player is very close
- The Player is within desired range
- The Player disappeared from view
Okay, let's look at our first situation. If the player is far away, we want to get into range. I think the range of our AI should be between 4 segments away and 5 segments away, translating into a distance of 400 to 500.
If the player is further than 500 units away, we should move in closer.
Coupling with this situation we have the other situation that the player is very close to us. So, if the player is closer than 400 units, we're gonna want to back up.
Remember, when making the AI move, it has to animate correctly as well. During combat a nice run seems like a good animation, so animation number 55 (Move Fast) would be nice for this.
Consulting the manual we come up with some commands to help us out here:
Conditions:
Quote: "PLRDISTWITHIN=X
is true when player is within X units
PLRDISTFURTHER=X
is true when player is further than X units"
Actions:
Quote: "MOVEFORE=X
moves the entity forward by X units
MOVEBACK=X
move the entity back by X units"
Now, let's start our script before our heads get too full of ideas and we start to forget things.
:state=0:state=1
:state=1:rotatetoplr
:state=1,plrdistfurther=500:movefore=4,animate=55
:state=1,plrdistwithin=400:moveback=4,animate=55
Alright, now our AI can move forward and back up if necessary. But as of right now, our AI doesn't know how to STOP moving, so let's put that in there and satisfy situation 3 where the AI is within the desired range.
:state=0:state=1
:state=1:rotatetoplr
:state=1,plrdistfurther=500:movefore=4,animate=55
:state=1,plrdistwithin=400:moveback=4,animate=55
:state=1,plrdistwithin=500,plrdistfurther=400:animate=51
Okay, now, our AI will do the Idle animation if it's in the desired range.
With that in place, let's look at our last situation, where the player disappears from the AI's view. We don't want our AI to try shooting through the wall, so let's look in the manual and see what we can find.
.
.
Actions:
Quote: "FOLLOWPLR
follows the players trail if one exists"
Wow, that seems rather specific to our needs, doesn't it?
We want the AI to follow the player if the player can't be seen, and once it finds the player again, to resume what it was previously doing. We'll want to make the following part of our AI a seperate state.
We already have an action to check if the player disappeared from view, so let's code this:
:state=0:state=1
:state=1:rotatetoplr
:state=1,plrdistfurther=500:movefore=4,animate=55
:state=1,plrdistwithin=400:moveback=4,animate=55
:state=1,plrdistwithin=500,plrdistfurther=400:animate=51
:state=1,plrcannotbeseen:state=2
:state=2:followplr
:state=2,plrcanbeseen:state=1
Okay, now that we have our AI following the player around and dauntlessly staring them in the eyes, let's have it start shooting.
Again, we must consult the mighty manual for advice:
Conditions:
Quote: "PLRINGUNSIGHT=X
is true when an entity has the player is gun sights [and X equals 1]"
Actions:
Quote: "USEWEAPON
fires any weapon half by an entity in the direction of the target"
Well, there we have it. If the player is in gun sight, we'll use our weapon! That sounds pretty simple. Point and click, really.
Remember, we only want to shoot when we have the player in sight, so this will go into state 1.
:state=0:state=1
:state=1:rotatetoplr
:state=1,plrdistfurther=500:movefore=4,animate=55
:state=1,plrdistwithin=400:moveback=4,animate=55
:state=1,plrdistwithin=500,plrdistfurther=400:animate=51
:state=1,plrcannotbeseen:state=2
:state=1,plringunsight=1:useweapon
:state=2:followplr
:state=2,plrcanbeseen:state=1
And there we have it. Our AI will now shoot the player until it runs out of ammo.
.
.
Ammo? Awwww ****, we gotta make sure that our AI reloads properly!
Okay, manual time!~
Conditions:
Quote: "IFWEAPON=X
is true when the weapon being used by entity is ready and X is one"
Actions:
Quote: "RELOADWEAPON
reloads the weapon from the entities stock of infinite ammo"
That condition sure seems cryptic, but to me it looks like we can check if the weapon is NOT ready (meaning out of ammo) but checking it against zero. Then, if our weapon isn't ready, we gotta reload!
Also, we'll need to make sure that we animate our AI to reload it's weapon. Animation 56 (Reload) is perfect.
Now, remember that reloading is instantaneous for AI, and that's not very fair to the player, so we'll need to animate this one ourselves instead of autoplaying it.
Here's what the manual gives us about animating manually:
Conditions:
Quote: "FRAMEATEND=X
is true when animation X is at an end
FRAMEATSTART=X
is true when animation X is at the beginning"
Actions:
Quote: "SETFRAME=X
set animation X start frame
INCFRAME=X
increment another frame in animation X"
This one is going to be a little more complicated than our auto-playing action. Oh well, it's for a good result, right? RIGHT!
First things first, we'll want to check if our AI is out of ammo, then we'll want to set up a "Reload State" that the AI can go to when it runs out of ammo. In that state, we'll show the reload animation and after it's finished we'll return to our shooting state.
Right before moving to our reload state, we want to start up our reload animation, then once we get to the reload state, we'll want to increment through the frames until we get to the end. I'm going to make state 10 be our reload state.
And we're off!
:state=0:state=1
:state=1:rotatetoplr
:state=1,plrdistfurther=500:movefore=4,animate=55
:state=1,plrdistwithin=400:moveback=4,animate=55
:state=1,plrdistwithin=500,plrdistfurther=400:animate=51
:state=1,plrcannotbeseen:state=2
:state=1,ifweapon=0:state=10,setframe=56
:state=1,plringunsight=1:useweapon
:state=2:followplr
:state=2,plrcanbeseen:state=1
:state=10:incframe=56
:state=10,frameatend=56:animate=51,reloadweapon,state=1
Okay, now our Ai will show a reload animation when reloading, and return to his normal state when he's done.
Step #4: Strafing
Recall that we also wanted our AI to strafe around so that it'll be harder for our player to hit them.
Manual saaaaaays:
Actions:
Quote: "CHOOSESTRAFE
randomly selects a strafe direction (ie left/right/forward)
STRAFE
perform the previously chosen strafe to avoid player shots"
If only these commands worked as promised this would be cake! Unfortunately for us, the choosestrafe command doesn't seem to act quite right with the strafe command.
Allow to rephrase the "Strafe" command:
Quote: "STRAFE=X
Will force the entity to strafe in the direction specified by X degrees from the forward position.
Negative X will be towards the entities Left, while a Positive X value will be towards the entities Right"
So, if you gave Strafe a value of "90" it would strafe directly to the Right.
Also, the strafe command doesn't animate the entity with the proper strafe, so we'll need to do that ourselves as well.
This look like it could take some work!
First, we'll want to set up a way to tell if we're in range of the player, then if we are, we'll want to choose a random direction to strafe in. After strafing, we'll want to check our distance again.
Hmmmm, let's think about how we can set this up.
Okay, i got it! If our Ai is within range, we'll go to a "Strafe State" (we'll use state 5 for this). In this state, we will randomly choose a strafing direction by using the following condition I found in the manual:
Quote: "RANDOM=X
is true when a random value between 0 and X is equal to one"
So, basically, this condition will randomly be true. The higher the value of X, the less likely it will be true.
I'm going to use a 50/50 chance here and go with "random=1".
If our condition returns true, we'll want to go to a state specificly designed for that strafing direction, and set up the animation for it as well.
We also want to continue shooting the player and rotating to them while strafing. This means we'll have to check for reloads, as well as if the player is in gunsight.
Now, let's start scripting:
:state=0:state=1
:state=1:rotatetoplr
:state=1,plrdistfurther=500:movefore=4,animate=55
:state=1,plrdistwithin=400:moveback=4,animate=55
:state=1,plrdistwithin=500,plrdistfurther=400:animate=51,state=5
:state=1,plrcannotbeseen:state=2
:state=1,ifweapon=0:state=10,setframe=56
:state=1,plringunsight=1:useweapon
:state=2:followplr
:state=2,plrcanbeseen:state=1
:state=5,random=1:state=6,setframe=54
:state=5:state=7,setframe=53
:state=6:incframe=54,strafe=90,rotatetoplr
:state=6,ifweapon=0:state=10,setframe=56
:state=6,plringunsight=1:useweapon
:state=6,frameatend=54:state=1,animate=51
:state=7:incframe=53,strafe=-90,rotatetoplr
:state=7,ifweapon=0:state=10,setframe=56
:state=7,plringunsight=1:useweapon
:state=7,frameatend=53:state=1,animate=51
:state=10:incframe=56
:state=10,frameatend=56:animate=51,reloadweapon,state=1
Wow, that sure added on a lot of code!
Alrighty, so our AI can strafe, shoot, follow, and walk waypoints like a reigning champ!
After all that work, we finally have something to show for it! Two whole scripts that will make a semi-smart AI.
For quick reference here they are:
Final Main AI Script
:waypointstate=0:waypointstart,animate=52
:waypointstate=3:waypointnext,animate=52
:waypointstate=4:waypointrandom,animate=52
:waypointstate=5:waypointreverse,animate=52
:state=0,plrcanbeseen:state=1
:state=1:rotatetoplr,waypointstop,animate=51,shootplr,state=2
Final Shoot AI Script
:state=0:state=1
:state=1:rotatetoplr
:state=1,plrdistfurther=500:movefore=4,animate=55
:state=1,plrdistwithin=400:moveback=4,animate=55
:state=1,plrdistwithin=500,plrdistfurther=400:animate=51,state=5
:state=1,plrcannotbeseen:state=2
:state=1,ifweapon=0:state=10,setframe=56
:state=1,plringunsight=1:useweapon
:state=2:followplr
:state=2,plrcanbeseen:state=1
:state=5,random=1:state=6,setframe=54
:state=5:state=7,setframe=53
:state=6:incframe=54,strafe=90,rotatetoplr
:state=6,ifweapon=0:state=10,setframe=56
:state=6,plringunsight=1:useweapon
:state=6,frameatend=54:state=1,animate=51
:state=7:incframe=53,strafe=-90,rotatetoplr
:state=7,ifweapon=0:state=10,setframe=56
:state=7,plringunsight=1:useweapon
:state=7,frameatend=53:state=1,animate=51
:state=10:incframe=56
:state=10,frameatend=56:animate=51,reloadweapon,state=1
There we have it. A very complicated process broken down... and we at last have something to show for it!
With this knowledge, you can probably go out and make your own, better AI (which you probably should, since it will make your games more unique)
This is your host, Plystire, signing off!
*kzzzzzzzzt!*
The one and only,
~PlystirE~