Pathfinding with line of sight

Favourite 179 favourites
Tutorial written by WranglerOriginally published on 19th, February 2013 - 5 revisions

Introduction

Hello all!

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.

Basic environment spritesFrom left to right they are: floor, unwalkable area (space inside the walls), wall panel, door.

And here they are in use:

Isometric environment exampleFor 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.

The Player

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

The Enemy

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.
Open full size image

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!

Basics

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.

Setting states

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.

Status Icons

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.

Closing thoughts

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!

Wrangler.png]

Unlock your full gamedev potential

Upgrade to the Personal Edition of Construct 2, it has way more features and won't holding back from making money and using your full creativity like the free edition does. It's a one off payment and all Construct 2 editor updates are free for life!

View deals

Plus, it's got a lot of additional features that will help you save time and make more impressive games!

Congratulations on finishing this tutorial!

Did you learn a lot from it? Share it now with your friends!

Comments

7
SergioRM 6,969 rep

Thank you! I really liked your tutorial, and I learned something new

Tuesday, February 19, 2013 at 9:02:38 PM
2
Wrangler 8,780 rep

@SergioRM I'm glad you liked it :) thank you for the kind words.

Wednesday, February 20, 2013 at 11:10:15 AM
2
danny 6,018 rep

Thanks Wrangler this is awesome. Good luck with Dungeon Buster.

Thursday, February 21, 2013 at 2:29:42 AM
0
DEEPA K SINGH 2,504 rep

its the best tutorial so far:)...best of luck:)

Thursday, February 21, 2013 at 12:15:56 PM
6
Wrangler 8,780 rep

Folks, @ramones has pointed out a few errors in my example capx and as a result I've altered and then reuploaded it. Please download the new capx!

Thursday, February 21, 2013 at 11:07:59 PM
2
Gamer 3,345 rep

Perfect ! Marvelous !
Thanks , one pf the greatest tutorials I have ever seen ;)

Friday, February 22, 2013 at 7:35:46 PM
2
EyeHawk 7,762 rep

Great work!

Sunday, February 24, 2013 at 3:36:46 AM
3
What kid 2,417 rep

good, very good

Sunday, February 24, 2013 at 5:58:06 AM
2
Johnny Fronthole 2,228 rep

I'm sure there is probably an obvious solution to this problem, but I can't see it.

I've spawned the LOS lines at the proper image point on the enemies; they follow the player at the proper angle; they are the proper width; however, that width stretches equidistant from the "eyes" origin point of the enemies, i.e., ending half-way between the player and the enemy sprites.

I've checked my event sheet against the tut's and it is identical. And I am using a 1px sprite for the los line.

Any help fixing this will be appreciated.

P.S. Very helpful and well constructed tutorial. Thanks for the effort!

Sunday, February 24, 2013 at 5:20:10 PM
3
Wrangler 8,780 rep

Johnny, have you tried setting the origin of your LOS line sprite to the left side? I think I remember coming across this issue before and that solved it.

Monday, February 25, 2013 at 12:26:24 AM
3
Wrangler 8,780 rep

Yes, that was the problem. I've updated the tutorial to include a mention so as others don't have the same issue.

Monday, February 25, 2013 at 12:42:04 AM
2
robbym 5,103 rep

Great tutorial! thanks for making it.

Monday, February 25, 2013 at 12:15:15 PM
3
Cereal Killa 2,600 rep

Great tutorial! I'm sure that the 'lines of code' could be reduced but for the sake of clarity everything is laid out beautifully and is easier for beginners like me to understand.

One thing that possibly isn't needed (and correct me if I'm wrong), is when alertCooldown = 0 you Set hasLineOfSight to False, but for alertCooldown to reach 0, hasLineOfSight would already be False (and would have been False for the past 5 seconds).

Also, is there any reason that the Enemy Is alerted; System Every 1 seconds: Enemy Find path to (Player.X , Player.Y),
and the Enemy Is alerted; System Every 1.0 seconds: Enemy Subtract 1 from alertCooldown, arguments are separated?
Surely they could be combined.

Thanks in advance for clarifying :D

Monday, February 25, 2013 at 9:58:43 PM
2
Wrangler 8,780 rep

You're right on both counts!
I'll correct the tutorial now.

Monday, February 25, 2013 at 10:48:37 PM
5
robbym 5,103 rep

Be aware, if you put rotate speed to 0 the object will not start moving along the path.

I just found that out :D

Tuesday, February 26, 2013 at 12:32:20 PM

Leave a comment

Everyone is welcome to leave their thoughts! Register a new account or login.