I'm going to show you how to combine the Pathfinding behaviour with a line of sight engine to create enemies which have some basic AI; they will wander around when not alerted, and once they have line of sight of the player they will pursue him. If the line of sight is broken for long enough, they will revert back to their wandering state. This tutorial is done for an isometric game, but it can also be applied to top down games.
You can grab the example .capx project here
Creating our assets
Ok, we're going to need a few things before we can start any visual scripting. What we will need is:
* An enemy sprite
* A player sprite
* A line of sight sprite
* Some environment sprites (walls, floor, door)
* A pathfinding 'helper' sprite that prevents clipping issues when the enemy sprites are moving.
* Two status icons; one to represent when the enemy is alerted, and another to represent when the enemy has line of sight with the player.
You can create these whatever way and size that you wish, but for ease of creation I have created mine in Construct 2's image editor. Below is an example of the environment sprites I created. I'm sticking to a 32x32 grid for this example.
From left to right they are: floor, unwalkable area (space inside the walls), wall panel, door.
And here they are in use:
For the status icons, I've kept it simple:
For the player and enemy sprites, I've made them 24px wide and 48px tall for this example (I've put it beside the floor tile to give you an idea of how big the sprite should be):
Now, the enemy sprite will need a few image points for anchoring the status icons and the line of sight. The origin for the enemy sprite should be set around 2/3 of the way down the sprite (for this sprite it is at Y:32). This will stop the sprite hovering unrealistically over the walls/unwalkable areas. The alertPoint and losPoint image points are for anchoring the status icons. The eyes image point is where the line of sight sprite will anchor; I've put it as close to where the enemy's eyes would be in real life.
For the line of sight sprite, all you need to do is create a 1x1 sized sprite as we will be stretching it out between the enemy and the player sprite at runtime. Make sure that you set the origin of the line of sight sprite to the left side of the sprite, otherwise you will experience strange behaviour from the line (it won't stretch between the enemy and player properly):
Don't forget to name your sprites descriptively as this greatly helps when you're scripting and finding bugs.
Time to get moving
Right, so we've got our sprites created. They look awesome, but don't do anything yet.
First off, give your player sprite the '8 Direction' behaviour, and set the values to the ones shown here:
Next, give the unwalkable area and door sprites the 'Solid' behaviour. This is purely to make these objects impassible to the player. The enemy sprites use a different method to tell them where they can and cannot walk. The reason for this is that we will be including an additional obstacle that isn't solid (our pathfinding helper):
If you run the layout now, you can use the keys to move the player character. Fabulous! But when he collides with the door nothing happens, so we'll get that sorted out now.
Dive into the event sheet for the layout and do the following:
Hurray, now when you collide with a door, it will disappear and the player can continue on through
This is where it gets a bit complicated, but we'll be taking this one step at a time so don't worry.
Give the enemy sprite the 'Pathfinding' behaviour and give it the following settings:
After you've done this, make the enemy sprite a container with the following sprites added into it:
With this set up, we will first deal with the wandering schedule for the enemy. This is straightforward to do, as all it really involves is the enemy's pathfinding behaviour picking a random point on the layout, seeing if it can move there; if it can then it will move to that point, if it can't it will find another point which it can move to. As a prerequisite to what we will be doing later, give the enemy sprite the following instance variables (I will explain these later in the tutorial):
Go back into the event sheet and create the following events:
This tells the enemy sprite what it can't walk across/through, otherwise it'd be ghosting all over the place through walls and doors.
This set of events tells the enemy sprite what to do when it is not alerted (ie: wandering).
If you run the layout now your enemy will wander around aimlessly, avoiding obstacles and completely ignoring the player. If you have put rooms into the layout, and the player has opened the door into some of them, the enemy will not move into the newly opened rooms. This is because the enemy needs to rebuild its obstacle map. You should run this rebuilding call every time a door is opened:
Now, your enemies will get an updated obstacle map every time a door is opened, and will move a bit more lucidly around the environment.
This part is completely optional, but I would recommend that you at least consider the principle of it as it proved to be decent solution to the problem of enemy sprites cutting corners, overlapping obstacle sprites that they shouldn't be overlapping. Through the use of invisible helper sprites, you can easily tweak the specific routes that the enemy sprite can take. This acts to prevent them from overlapping obstacle sprites:
My helper sprites are in green, and can be toggled visible/invisible by pressing H in the example .CAPX
Line of sight
Now for the juicy stuff!
For our line of sight engine, we will be attaching a line of sight sprite to an enemy's 'eyes' and then stretch this sprite towards the X and Y point of the player sprite. When this sprite is not colliding with any environment obstacles, then the enemy's hasLineOfSight boolean will be true and he will pursue our player. When the enemy loses line of sight with the player, a cooldown is initiated, and once this cooldown duration has passed the enemy will stop pursuing and go back to wandering. The reason I've included a cooldown is to prevent the enemy from immediately stopping as soon as they lose sight of the player; instead they will pursue for a few seconds more before giving up.
Let's do this! For the first section (setting up the line of sight sprite) create the following events:
The first event will delete our master instance of the line of sight sprite. Protip: Construct 2 requires at least one instance of anything you'll be spawning to already exist. So make sure you have one of every sprite in the layout (use the margins to hide them).
The second event block achieves this result when you run the layout:
Fantastic! We now have a line of sight attached to our enemy, and it traces a straight line between our player and our enemy.
Next up, we want to define what happens when the enemy has not got line of sight with the player:
In this event, if the line of sight sprite is overlapping any of the obstacles, then the enemy's hasLineOfSight boolean is set to false and the enemy is not refreshing its 'alert' status. Once the line of sight sprite is not overlapping any of the obstacles the enemy's hasLineOfSight boolean is set to true, it's alertCooldown variable is set to 5 (meaning it will continue to pursue for 5 seconds after it loses line of sight, and it's alerted boolean is set to true.
These boolean states are necessary, as they allow us to control the actions of the enemy based on whether it has line of sight or not, and what to do when it is and isn't alerted. With this in mind, we are now going to tell the enemy what to do when its 'alerted' boolean is true:
For cleanliness sake, combine the two "Enemy is Alerted; Every 1 Second" event blocks.
With this done, when you run the layout now your enemies will pursue the player when they have line of sight, and will lose interest and wander off when they lose line of sight for more than 5 seconds.
Lastly, we can create status icons that show us what state an enemy is currently in:
This will dynamically show/hide the icons depending on what boolean states the enemy is in:
This enemy is both alerted and has line of sight.
This enemy is alerted, but does not have line of sight.
I hope this tutorial proves useful, it took me a few days to get everything working as I wanted it to.
With the boolean states, you can easily make the enemy attack when it has line of sight (like in the example .CAPX I have included with this tutorial.
If you come across any inconsistencies in the tutorial, or need something explained in greater detail, either comment here or shoot me a PM and I will be happy to help you out!