The well-written first part of Gorkan's three-part tutorial about adding AI to your game's enemies/guards, like patrolling, following the hero, and alerting other guards.
This is one of the longest RM2K tutorials that I have seen on GW.
RPGMaker AI Basics : Pathfinding (1/3)
By Gorkan
Well, this is my first tutorial on GW, so I just hope my explanations are clear enough and it’ll be useful for some of you. As I’m not a native English speaker, please excuse bad grammar or weird sentences, but I think it’s roughly understandable ;)
As you may have guessed, it revolves around how to create a good AI pathfinding with RPGMaker 2000, so that enemies in A-RPG may behave in a more elaborated way than the common « Step toward hero » movement.
Since this whole thing started as a reply to Lothion’s tutorial here : http://www.gamingw.net/tutorials/695 , I may make a reference to his method later, although I’ll explain it again (and modify it slightly to suit my needs). My tutorial being quite long as it is, I won’t detail the process as much as him, so I suggest taking a look at his, if you haven’t yet, before reading further.
This is a moderately advanced tutorial using a great deal of variables and fork conditions (and a few rarely-used functions too), therefore I strongly advise reading it later if you don’t have a good knowledge of event commands yet.
Here we go :
I. Pathfinding Basics
First of all, you may wonder what use exactly is pathfinding in your common RM2K A-RPG. Well, actually it has many uses : you can have an enemy patrol an area, run toward the hero as soon as he notices him, and flee back to the barracks in order to alert other guards when he is wounded.
It may be easy to do without pathfinding for a single enemy, but you’ll have to do a different script for each guard and each map. What I’m trying to do is a single general script that can be used without any alteration an unlimited number of times, regardless of what the map looks like (that is, in the previous example, where the hero, enemy and barracks are) or of the number of enemies.
Naturally, before being able to do this, there is much preliminary work to do. To begin with, something that isn’t too complex but will be very useful later : having a NPC walk straight toward another event.
Here are several diagrams which I’ll refer to in my explanations :
(they represent two rooms connected by a narrow passage)
The first step is having the enemy (the red dot) walk to the blue square when there’s no obstacle on his way. This can be achieved with Lothion’s method (I won’t explain it in detail, if you don’t know how to do the following, read his tutorial first) :
Store the enemy’s and the blue square’s XY coordinates in variables
If enemyY > squareY, then enemy steps up once (since, in RM2K, 0,0 is the upper left tile of the map)
If enemyY < squareY, then enemy steps down once
If enemyX > squareX, then enemy steps left once
If enemyX < squareX, then enemy steps right once
And repeat until enemy X and Y = square X and Y (use multiple fork conditions, that shouldn’t be too big a problem for someone used to RM2K)
As Logan mentionned in a reply to Lothion, this works very well, but only if there is no obstacle between the enemy and his objective (it will work only in diagram #1), else the enemy gets stuck as soon as he reaches the obstacle, and stays there doing nothing (actually, I wonder whether it can be avoided by simply ticking the « Ignore if can’t move » case – although I didn’t test it so I don’t know – so all the following explanations to part II could be totally useless, but I also made use of them to introduce some functions, so I put them anyway ; if you want, you can jump directly to part II, but I don’t suggest it)
Therefore the next step is to have the enemy avoid basic obstacles on his path, such as the very ugly black dot on diagram #2, which is supposed to represent a kind of column or something.
In order to do that, you’ll need a very useful though underused event command : Set Ground ID (GetTerrainID in RM2K3, I think). This little problem will serve as an introduction to this function, which we shall use again later.
In the Database, under the Chipset tab, you can see that in « Edit Mode/ Terrain Type » a number ranging from 1 to 10 is assigned to each tile of the chipsets. Obviously, this number corresponds to the terrain type of the tile, 1 being Grass, 2 : Forest, 3 : Desert, and so on.
This wouldn’t be of much use in our case, weren’t it for the Set Ground ID function : this event command stores in a variable the terrain type of a tile. What is the point, you may wonder ? In fact, the major problem with obstacles in pathfinding is that, in RPG Maker 2K, there’s no easy way to recognize them from non-blocking tiles if the obstacles aren’t events (and it’s really painful to have to cover every single wall tile with an event in order to recognize it). With Set Ground ID, you only have to give all obstacles the same Terrain Type (one that won’t be used by anything else in your map, let’s say « SnowForest » or « Sea :Beach » in a dungeon) and then you can know whether a tile is or isn’t an obstacle.
Here’s how to implement it in your pathfinding :
In the previous event (Lothion’s method), you should have fork conditions like this
Fork Optn : Varbl[EnemyX]-V[SquareX]Less (enemyX < squareX)
Set Chara’s Movement : Enemy, Right (enemy steps right)
End Case
Before the Move command, you must insert a SetGroundID that checks the Terrain Type of the tile which coordinates are enemyY and enemyX+1 (set another variable to enemyX and then just add 1, you can name it the way you want but I’ll use « enemyX+1 » or « enemyX-1 » as names so that my explanations are clearer)
By doing so, you check the tile that is next to the enemy, to the right. Then add a Fork Condition with condition « [Variable : stored Terrain Type] different from [Obstacle Terrain Type number] » and put the move event in the fork condition. Repeat for every direction, and now if the enemy must go to the upper left corner of a room but there’s an obstacle to his left, the enemy will step up until he can go left.
Ta-daa, you have an enemy who can find his path in the situations of diagrams #2 to 4 (I’ll explain later why it doesn’t work in diagrams #5 and 6, though you can guess why by yourself).
In fact, it should work perfectly in diagrams #3 and 4, but not necessarily in the one with the column. Why ? Because most obstacles of that kind (trees, crates and so on) are in the Upper Chip, not the Lower one, and thus don’t have a Terrain Type. There are two possible solutions to this problem. The easiest one implies that the chipset you use for your dungeons (or wherever you may want to place an ABS system) isn’t too crowded : you must copy the tile you use for the floors, so that in your chipset you have two same-looking floor tiles. One will be a non-blocking tile, and the other an obstacle, then you must use the former to do the floors and the latter under all Upper Mode obstacles (only the tiles that are supposed to block, not the ones that are above the character).
There may be some problems of continuity in the way the floor looks but nevertheless you should be able to do something that looks good, with a bit of imagination (don’t forget that this special floor will only block the enemies, not the hero).
The other way to do it implies to place an event on every Upper Chip obstacle (a bit long and tedious, but there are much less obstacles to « eventize » than if you had to do it for the Lower Chip too). Anyway, you can use this method or not, but you’ll have to include the following anyway since it will also serve to make sure the enemy can avoid other events (even mobile ones such as other enemies). It’ll also introduce another very useful function : SetEventID. As you may have guessed, this command stores in a variable the ID of the event located on one tile, and returns 0 if there’s no event.
At this point, your fork condition should look like this :
Fork Optn : Varbl[EnemyX]-V[SquareX]Less (enemyX < squareX)
Fork Optn : Varbl[GroundID]- 10 Other (terrain type different from obstacle)
Set Chara’s Movement : Enemy, Right (enemy steps right)
End Case
End Case
In the Fork Condition with the Terrain Type, before the Move event, you must now add a SetEventID that will check the tile [enemyY, enemyX+1]. The problem is that some event block and some don’t. Which is why you’ll need a little trick : if the SetEventID returns 0, move the enemy, and if it doesn’t, store the enemyX in a variable, try to have the enemy step right (tick the « Ignore if can’t be moved » case, I believe it should work), insert a 0.1 or 0.2 second wait (so that he has time to move) and then compare the new enemyX with the old one. If it’s the same, the event blocks the enemy, so he must go up or down. The problem is that if this « trick » works, then perhaps like I said before this whole thing wasn’t necessary in order to have an efficient pathfinding (and if it doesn’t work, you’ll have no choice but having the enemy avoid any kind of event, even if it doesn’t block as such) – sorry but I’m not really sure on that point, my numerous tests of this specific case gave unclear results.
Anyway, the point of these explanations was really to show the uses of the SetEventID and SetGroundID functions, since they’ll be crucial later.
II. Introducing the concept of pathgrid
1) The rail
As I said before, the previous methods will work only in some cases : situations like diagrams #1 to 4 won’t cause problems, but the last ones will : the way it’s done, the enemy will try to have the same X coordinate as the blue square, thus he won’t be able to walk round the wall in #5, and in #6 he will step left once for each step down, and end up blocked though he wasn’t to begin with.
In order to solve this problem, there are two different methods I named the rail and the checkpoints (well, I’m not paid to invent great names … in fact I’m not paid at all, sigh). It is possible to use the rail without the checkpoints in very simple cases, it’s also possible to use the checkpoints without the rail, but I’ll explain both, and combine them for an optimally efficient pathfinding method.
As the subtitle may lead you to guess, I’ll begin with the rail method, because it’s the easiest one to understand and apply. In fact, if you read the first part, you already know almost everything you need in order to create a rail-type pathfinding. Remember the same-looking floor tiles trick ? Well, it’s almost the same, but this time the second tile isn’t used as an obstacle (and mustn’t be of the Obstacle Terrain Type, else this method won’t work at all – which means that if you want to use both the rail and the other trick, you’ll need three identical tiles ; since the latter is optional and the rail is necessary, if you really need an extra tile, you can use the rail alone).
If you used Terrain Type 10 for the obstacles, you can assign 9 (or any other that won’t be used anywhere else in your map) to this special rail tile. Then, you must use it to create a one-tile wide path that passes through the whole map, avoids all obstacles and leads directly to the objective (in our example, the blue square).
As the name implies, it will act as a rail for the enemy and guide him to the square. If you use only a rail and no checkpoint, the rail will have to pass in each room long the walls that are nearest to the blue square : using the method seen in part I, the enemy will first go in the general direction of the square, regardless of where he is at first, so if the rail is next to the nearest wall to the square, the enemy will eventually step on it. When he does (you can check by a SetGroundID at the coordinates of the enemy) the « autopilot » is activated.
Before I explain how to do the latter, here are some new diagrams showing the rail (green line) and the path of the monster (orange and yellow dots)
.
As you can see, he always reaches the square, although the way he does is a bit odd in diagram #9. In fact it wasn’t necessary to have the rail go to the upper left corner, it could have stopped where it borders the lower wall of the upper room, at the same X coordinate than the square, since even an enemy starting from the upper left corner would end up there anyway (and in fact, not only isn’t it necessary, but it also creates problems)
Now, the rail itself : when the enemy steps on it, this triggers a switch used as condition for another page of the « enemy » event (or you can use a fork condition with a cycle instead if you prefer, I assume you are able to manage an event properly ;) ). When the enemy is in « rail mode », check with a SetGroundID the four tiles behind, ahead, left and right of him (coordinates : enemyX-1 and enemyY+1 ; enemyX-1 and enemyY-1 and so on). If there’s only one adjacent rail tile, just have the enemy step on it and activate a switch corresponding to the direction in which he stepped (if there’s only one rail tile and it’s to the right, step right and activate a « Right » switch – or use a variable if you prefer), then when you use SetGroundID again, don’t go to the left if the « Right » switch is activated so that the enemy doesn’t go back where he came from (don’t forget to deactivate the switch activated at step1 before activating another one at step2) ; that way, he’ll follow the rail automatically till he reaches the blue square.
If there are two possible directions when the enemy steps on the rail, now you have a problem. The only way to chose in which direction to go with the « rail only » method is to go in the direction that seems to bring closer to the square and hope it’ll be the good one. It works in diagram #7 for example : squareY > enemyY when the enemy steps on the rail, so he goes down ; but it won’t work in #8 and #9 : they have the same X than the square, and they must chose between right and left. This shows that the rail wasn’t planned carefully enough in these diagrams : you can see here
what it should have been. The enemies of diagrams #8 and #9 would then step on the rail at a point where there’s only one possible direction. But what if the enemy stepped one tile to the right of the dead end ? Well he’d first step left because enemyX > squareX, then reach the dead end where all direction switches are deactivated (only check these switches when there are two possible directions), and then step right, activating the « Right » switch and going in the right (ahem) direction.
It also works with more than two rooms, but you have to plan carefully :
The only places where there may be problems are the two orange corners on this diagram, which is why you may want to SetGroundIDize the four adjacent tiles rather than the one where the enemy stands during the « rail search » (and if the enemy ends up on one of the two dark brown internal corner tiles, where he’ll have two possible tiles where to step on the rail, he must always step on the tile that is the farther from the blue square, here step left rather than down : that way he’ll be sure to choose the good direction once he is in autopilot mode – I know it’s unclear but I assure you it works)
Now you have a system that works very well, but only in simple situations (no multiple paths with junctions or mobile targets). Still, it enables you to do some good AI programming (with this method it’s very easy to create a stealth scene with patrolling enemies who step toward the hero if they hear a noise, wait a moment, and return to their patrol if he doesn’t show up – you’ll just have to write the event once, and then copy/past and place on different patrol rails ; be imaginative !), and there are many other things you can do with SetGroundID (another almost never used command is the Change Chipset function, but if you create several same looking chipsets with different Terrain Type attributions, you can do quite elaborate AI programming)
Well, I first planned to finish part II here and save part III for another time, but the second section of part II is already more than 2000 words long, and far from being finished, so I shall initially present you with this first third (sorry for the odd segmenting), and the second and third ones will be delivered as soon as possible (my exams are coming soon, so it'll have to wait a bit).