“HEY, YOU! LET’S BATTLE!” ADDING A LINE OF SIGHT MECHANIC TO YOUR NPCS 12

1

Stats

403 visits, 452 views

Tools

License

This tutorial is licensed under CC BY 4.0. Please refer to the license text if you wish to reuse, share or remix the content contained within this tutorial.

Published on 25 Aug, 2021.

The first thing we need to do in this project is to add some new functionality to our NPCs. To the NPC Base family, you’ll need to add the Line of Sight behavior. Within the behavior’s properties, set Obstacles to Custom. You can leave the range and cone of view properties as they are. The family also needs a new number instance variable – RayHitID. This instance variable will be used to tell the NPC what object has intersected its Line of Sight.

To the NPC Sprites family, you’ll need to add the Persist behavior – this means that when switching layouts, any instance variables will be preserved. This way, your NPCs will be exactly where you left them when you come back to the layout. However, you’ll also need to add a new variable to the NPC Sprites family – a Boolean variable called CanBattle. Otherwise, your poor player will end up in an endless loop of being spotted and challenged by the NPC!

Next, you’ll need to add two global variables. A string variable called CurrentLayout which we’ll use to store the name of the layout the player is on, and a Boolean variable called BattleInProgress – this allows us to tell the game whether or not the ‘battle scene’ is currently being displayed.

Given that we have a variable called CurrentLayout that would suggest that we’ll be using more than one layout. Which is true! And we need to add a new one, our ‘battle scene’. Create a new layout, (which won’t need an event sheet) and call it BattleLayout. On this new layout, add a button object and give it the text ‘Go Back’. This will just be used to send the player back to the first layout as part of this proof of concept.

When your NPC spots the player, we need something to show that they’ve been seen. So add a new Sprite object called ‘Exclamation’. Well, you can call it whatever you like, it’s just a quickly drawn speech bubble!

And that’s all of the new objects, variables and behaviors! In the next section, we’ll start adding the new logic required to make this mechanic work. RAYCASTING & PLAYER DETECTION

As always, we’re going to start by adding this mechanic to the simplest object type – in this case, the static NPC. This NPC does not move unless interacted with by the player so there should be almost no competing mechanics when we add this new one.

INITIALISATION

Before we start that however, we need to add a few initialisation events. These will run on the start of the layout and set a few things up.

CONDITION

System ▶︎ On Start of Layout

ACTION

System ▶︎ Set CurrentLayout to LayoutName

SUB-EVENT ACTION

Player ▶︎ Set solid collision filter to “NPC” Exclusive

We can also add the event for our ‘Go Back’ button into this group as it doesn’t quite fit anywhere else:

CONDITION

Button ▶︎ On Clicked

ACTION

System ▶︎ Go to layout “GameLayout”

ADDING OBSTACLES TO LINE OF SIGHT

Next, we need to assign some obstacles to our ‘Custom’ line of sight system. This can actually be done in the overarching NPC group as we can use one of the families to apply this logic to all of the NPC types.

CONDITION

System ▶︎ On Start of Layout

ACTION

NPC_Sprite ▶︎ Line of Sight ▶︎ Add obstacle Player

NPC_Sprite ▶︎ Line of Sight ▶︎ Add obstacle Wall

NPC_Sprite ▶︎ Line of Sight ▶︎ Add obstacle NPC_Base

NEW STATES FOR THE NPC

In order to make this system work, the NPC needs a few more states adding to it – Wait, Challenge and StartFight. Some of these will be quite similar to existing states, but it’s easier to have these to allow us to better differentiate what the NPC is doing.

The Challenge state is essentially the same as the pre-existing Walk state:

CONDITION

System ▶︎ For each Static_NPC_Base

Static_NPC_Sprite ▶︎ State = “Challenge”

ACTION

Static_NPC_Sprite ▶︎ Set animation to “Walk” & Self.Direction (play from beginning)

Static_NPC_Base ▶︎ Set TileMovement Enabled

SUB-EVENT CONDITION

Static_NPC_Sprite ▶︎ Direction = “Down”

SUB-EVENT ACTION

Static_NPC_Base ▶︎ Simulate TileMovement pressing Down

As with previous states, add in the same sub-events for Up, Right and Left.

The two states StartFight and Wait are the same in all but name, a quality they also share with the existing Idle state:

CONDITION

System ▶︎ For each Static_NPC_Base

Static_NPC_Sprite ▶︎ State = “Wait”

ACTION

Static_NPC_Sprite ▶︎ Set animation to “Idle” (play from beginning)

Static_NPC_Sprite ▶︎ Set animation speed to 0

SUB-EVENT CONDITION

Static_NPC_Sprite ▶︎ Direction = “Down”

SUB-EVENT ACTION

Static_NPC_Sprite ▶︎ Set animation frame to 0

Again, add in the sub-events for the remaining directions, making sure that they match up with the correct frames. If you didn’t want to use these similar states in your project, you could probably achieve a similar outcome using instance variables.

One more thing to do, to make sure we don’t lose any functionality of our NPCs is to tweak the existing Talking mechanic – where the player interacts with an NPC to trigger an action (like a dialogue) but they don’t want to battle. The only thing you need to do here is adding an extra condition to the event block:

CONDITION

Static_NPC_Sprite ▶︎ Is NOT CanBattle

And this will then allow us to trigger the opacity functions that are acting as a dialogue placeholder. It also means that the ‘battleable’ NPCs can have something to say after you’ve fought them.

Now. Onto the ‘Hey you!’ part of this tutorial.

CASTING RAYS

As you’ve already seen in setting up their states, the NPCs are designed to have an instance variable containing their direction so they’d always record which way they were moving or facing. This variable is now going to be used again so that the NPC can cast a Line of Sight ray in the direction that they’re currently facing. We are also going to use the new CanBattle Boolean to define which NPCs will be using this logic.

CONDITION

Static_NPC_Sprite ▶︎ Is CanBattle

SUB-EVENT CONDITION

Static_NPC_Sprite ▶︎ Direction = “Right”

SUB-EVENT ACTION

Static_NPC_Base ▶︎ Cast ray from (Self.X+8, Self.Y+8) to (Self.X+88, Self.Y+8) (use collision cells)

You can then repeat this for the remaining three directions. Given that this project is laid out in a grid, with the origin in the top left corner (0,0) we have to add the 8 to the X and Y coordinates to have the NPC cast their ray from the centre of the object. In the second coordinate, (where we’re casting the ray to) adding 80 to get +88 is just an arbitrary number, equivalent to 5 squares on the grid. You can play around with this number to achieve the effect you want.

ISSUING THE CHALLENGE

In the initialisation event block, we added several objects to the Obstacles list for the Line of Sight behavior attached to our NPC Bases. This means that more than just the player object is capable of intersecting the ray cast by the NPC and registering as such. So, we need to add a way of defining what intersects the ray, and only triggering the mechanic if that object is the player. Step forward the RayHitID variable we added to the NPC_Base family. Using this variable in the following events, we can trigger the mechanic based on what intersected the NPC’s line of sight:

CONDITION

Static_NPC_Base ▶︎ Ray intersected

ACTION

Static_NPC_Base ▶︎ Set RayHitID to Static_NPC_Base.LineOfSight.HitUID

SUB-EVENT CONDITION

Static_NPC_Base ▶︎ RayHitID = Player.UID

System ▶︎ Trigger once

SUB-EVENT ACTION

Static_NPC_Sprite ▶︎ Set animation speed to 0

Static_NPC_Sprite ▶︎ Set InterruptedState to Self.State

Static_NPC_Sprite ▶︎ Set InterruptedDirection to Self.Direction

System ▶︎ Create object Exclamation on layer 0 at (Static_NPC_Sprite.X, Static_NPC_Sprite.Y-16)

Functions ▶︎ Call LOSWalk {NPCUid: Static_NPC_Sprite.UID)

Player ▶︎ Set State to “Talking”

This is all well and good, but currently calling LOSWalk won’t actually do anything, seeing as the function doesn’t exist. So, let’s build that and another function we’ll be using shortly. For LOSWalk:

ON FUNCTION LOSWALK – NUMBER PARAMETER NPCUID

CONDITION

NPC_Sprites ▶︎ Pick instance with UID NPCUid

ACTION

NPC_Sprites ▶︎ Set State to “Wait”

System ▶︎ Wait 1 seconds

Exclamation ▶︎ Destroy

System ▶︎ Set State to “Challenge”

And for our other function, NPCFight:

ON FUNCTION NPCFIGHT – NUMBER PARAMETER NPCUID

CONDITION

NPC_Sprites ▶︎ Pick instance with UID NPCUid

ACTION

System ▶︎ Wait 2 seconds

System ▶︎ Signal “EndInteraction”

System ▶︎ Go to layout “BattleLayout”

Right, now we have those in place, we can go back to the Challenge mechanic. The LOSWalk function eventually sets the NPC’s state to Challenge so we need to define what to do when that happens:

CONDITION

Static_NPC_Sprite ▶︎ State = “Challenge”

SUB-EVENT CONDITION

Static_NPC_Sprite ▶︎ Is overlapping Player at offset (-8,0)

OR

Static_NPC_Sprite ▶︎ Is overlapping Player at offset (8,0)

OR

Static_NPC_Sprite ▶︎ Is overlapping Player at offset (0,8)

OR

Static_NPC_Sprite ▶︎ Is overlapping Player at offset (0,-8)

SUB-EVENT ACTION

Static_NPC_Sprite ▶︎ Set State to “StartFight”

And finally, it’s time to trigger the layout change, which only happens when the NPC has the CanBattle Boolean set to true and is in the “StartFight” state:

CONDITION

Static_NPC_Sprite ▶︎ State = “StartFight”

Static_NPC_Sprite ▶︎ Is CanBattle

ACTION

Functions ▶︎ Call NPCFight (NPDUid: Static_NPC_Sprite.UID)

Static_NPC_Sprite ▶︎ Set CanBattle to False

And that’s that – the game should now switch to the second layout and you’ve already added the events which allow the player to return to the first layout. But, something’s not quite right still.

FIXING THE PLAYER

As it stands, the NPC can spot the player, and trigger the required action, you can still see and control the player on the Battle layout, which is not what we want! So, we need to do some tweaking to the player events.

The first thing to do is make the player invisible and uncontrollable in the BattleLayout – the last thing you need in the middle of a fight is a little person wandering about, getting in the way! This will use another On start of layout event:

CONDITION

System ▶︎ On start of layout

SUB-EVENT CONDITION

System ▶︎ CurrentLayout = “BattleLayout”

SUB-EVENT ACTION

Player ▶︎ Set visibility Invisble

System ▶︎ Set group “Player States” Deactivated

System ▶︎ Set group “Player Interactions” Deactivated

But when we come back from the BattleLayout, then the player needs to be seen and controllable once again:

SUB-EVENT CONDITION

System ▶︎ CurrentLayout ≠ “BattleLayout”

SUB-EVENT ACTION

Player ▶︎ Set visibility Visble

System ▶︎ Set group “Player States” Activated

System ▶︎ Set group “Player Interactions” Activated

And just to make sure your player doesn’t get stuck in its Talking state, add this final sub-event:

SUB-EVENT CONDITION

System ▶︎Is NOT BattleInProgress

Player ▶︎ State = “Talking”

SUB-EVENT ACTION

Player ▶︎ Set State to “Normal”

Now your player shouldn’t be visible when you enter the BattleLayout, but should still function as intended when not on that layout!

Nesting all of that under the one Start of Layout event you had in the initialisation group should look something like this:

Now we can add that challenge mechanic to the other NPCs!RAYCASTING & PLAYER DETECTION

As always, we’re going to start by adding this mechanic to the simplest object type – in this case, the static NPC. This NPC does not move unless interacted with by the player so there should be almost no competing mechanics when we add this new one.

INITIALISATION

Before we start that however, we need to add a few initialisation events. These will run on the start of the layout and set a few things up.

CONDITION

System ▶︎ On Start of Layout

ACTION

System ▶︎ Set CurrentLayout to LayoutName

SUB-EVENT ACTION

Player ▶︎ Set solid collision filter to “NPC” Exclusive

We can also add the event for our ‘Go Back’ button into this group as it doesn’t quite fit anywhere else:

CONDITION

Button ▶︎ On Clicked

ACTION

System ▶︎ Go to layout “GameLayout”

ADDING OBSTACLES TO LINE OF SIGHT

Next, we need to assign some obstacles to our ‘Custom’ line of sight system. This can actually be done in the overarching NPC group as we can use one of the families to apply this logic to all of the NPC types.

CONDITION

System ▶︎ On Start of Layout

ACTION

NPC_Sprite ▶︎ Line of Sight ▶︎ Add obstacle Player

NPC_Sprite ▶︎ Line of Sight ▶︎ Add obstacle Wall

NPC_Sprite ▶︎ Line of Sight ▶︎ Add obstacle NPC_Base

NEW STATES FOR THE NPC

In order to make this system work, the NPC needs a few more states adding to it – Wait, Challenge and StartFight. Some of these will be quite similar to existing states, but it’s easier to have these to allow us to better differentiate what the NPC is doing.

The Challenge state is essentially the same as the pre-existing Walk state:

CONDITION

System ▶︎ For each Static_NPC_Base

Static_NPC_Sprite ▶︎ State = “Challenge”

ACTION

Static_NPC_Sprite ▶︎ Set animation to “Walk” & Self.Direction (play from beginning)

Static_NPC_Base ▶︎ Set TileMovement Enabled

SUB-EVENT CONDITION

Static_NPC_Sprite ▶︎ Direction = “Down”

SUB-EVENT ACTION

Static_NPC_Base ▶︎ Simulate TileMovement pressing Down

As with previous states, add in the same sub-events for Up, Right and Left.

The two states StartFight and Wait are the same in all but name, a quality they also share with the existing Idle state:

CONDITION

System ▶︎ For each Static_NPC_Base

Static_NPC_Sprite ▶︎ State = “Wait”

ACTION

Static_NPC_Sprite ▶︎ Set animation to “Idle” (play from beginning)

Static_NPC_Sprite ▶︎ Set animation speed to 0

SUB-EVENT CONDITION

Static_NPC_Sprite ▶︎ Direction = “Down”

SUB-EVENT ACTION

Static_NPC_Sprite ▶︎ Set animation frame to 0

Again, add in the sub-events for the remaining directions, making sure that they match up with the correct frames. If you didn’t want to use these similar states in your project, you could probably achieve a similar outcome using instance variables.

One more thing to do, to make sure we don’t lose any functionality of our NPCs is to tweak the existing Talking mechanic – where the player interacts with an NPC to trigger an action (like a dialogue) but they don’t want to battle. The only thing you need to do here is adding an extra condition to the event block:

CONDITION

Static_NPC_Sprite ▶︎ Is NOT CanBattle

And this will then allow us to trigger the opacity functions that are acting as a dialogue placeholder. It also means that the ‘battleable’ NPCs can have something to say after you’ve fought them.

Now. Onto the ‘Hey you!’ part of this tutorial.

CASTING RAYS

As you’ve already seen in setting up their states, the NPCs are designed to have an instance variable containing their direction so they’d always record which way they were moving or facing. This variable is now going to be used again so that the NPC can cast a Line of Sight ray in the direction that they’re currently facing. We are also going to use the new CanBattle Boolean to define which NPCs will be using this logic.

CONDITION

Static_NPC_Sprite ▶︎ Is CanBattle

SUB-EVENT CONDITION

Static_NPC_Sprite ▶︎ Direction = “Right”

SUB-EVENT ACTION

Static_NPC_Base ▶︎ Cast ray from (Self.X+8, Self.Y+8) to (Self.X+88, Self.Y+8) (use collision cells)

You can then repeat this for the remaining three directions. Given that this project is laid out in a grid, with the origin in the top left corner (0,0) we have to add the 8 to the X and Y coordinates to have the NPC cast their ray from the centre of the object. In the second coordinate, (where we’re casting the ray to) adding 80 to get +88 is just an arbitrary number, equivalent to 5 squares on the grid. You can play around with this number to achieve the effect you want.

ISSUING THE CHALLENGE

In the initialisation event block, we added several objects to the Obstacles list for the Line of Sight behavior attached to our NPC Bases. This means that more than just the player object is capable of intersecting the ray cast by the NPC and registering as such. So, we need to add a way of defining what intersects the ray, and only triggering the mechanic if that object is the player. Step forward the RayHitID variable we added to the NPC_Base family. Using this variable in the following events, we can trigger the mechanic based on what intersected the NPC’s line of sight:

CONDITION

Static_NPC_Base ▶︎ Ray intersected

ACTION

Static_NPC_Base ▶︎ Set RayHitID to Static_NPC_Base.LineOfSight.HitUID

SUB-EVENT CONDITION

Static_NPC_Base ▶︎ RayHitID = Player.UID

System ▶︎ Trigger once

SUB-EVENT ACTION

Static_NPC_Sprite ▶︎ Set animation speed to 0

Static_NPC_Sprite ▶︎ Set InterruptedState to Self.State

Static_NPC_Sprite ▶︎ Set InterruptedDirection to Self.Direction

System ▶︎ Create object Exclamation on layer 0 at (Static_NPC_Sprite.X, Static_NPC_Sprite.Y-16)

Functions ▶︎ Call LOSWalk {NPCUid: Static_NPC_Sprite.UID)

Player ▶︎ Set State to “Talking”

This is all well and good, but currently calling LOSWalk won’t actually do anything, seeing as the function doesn’t exist. So, let’s build that and another function we’ll be using shortly. For LOSWalk:

ON FUNCTION LOSWALK – NUMBER PARAMETER NPCUID

CONDITION

NPC_Sprites ▶︎ Pick instance with UID NPCUid

ACTION

NPC_Sprites ▶︎ Set State to “Wait”

System ▶︎ Wait 1 seconds

Exclamation ▶︎ Destroy

System ▶︎ Set State to “Challenge”

And for our other function, NPCFight:

ON FUNCTION NPCFIGHT – NUMBER PARAMETER NPCUID

CONDITION

NPC_Sprites ▶︎ Pick instance with UID NPCUid

ACTION

System ▶︎ Wait 2 seconds

System ▶︎ Signal “EndInteraction”

System ▶︎ Go to layout “BattleLayout”

Right, now we have those in place, we can go back to the Challenge mechanic. The LOSWalk function eventually sets the NPC’s state to Challenge so we need to define what to do when that happens:

CONDITION

Static_NPC_Sprite ▶︎ State = “Challenge”

SUB-EVENT CONDITION

Static_NPC_Sprite ▶︎ Is overlapping Player at offset (-8,0)

OR

Static_NPC_Sprite ▶︎ Is overlapping Player at offset (8,0)

OR

Static_NPC_Sprite ▶︎ Is overlapping Player at offset (0,8)

OR

Static_NPC_Sprite ▶︎ Is overlapping Player at offset (0,-8)

SUB-EVENT ACTION

Static_NPC_Sprite ▶︎ Set State to “StartFight”

And finally, it’s time to trigger the layout change, which only happens when the NPC has the CanBattle Boolean set to true and is in the “StartFight” state:

CONDITION

Static_NPC_Sprite ▶︎ State = “StartFight”

Static_NPC_Sprite ▶︎ Is CanBattle

ACTION

Functions ▶︎ Call NPCFight (NPDUid: Static_NPC_Sprite.UID)

Static_NPC_Sprite ▶︎ Set CanBattle to False

And that’s that – the game should now switch to the second layout and you’ve already added the events which allow the player to return to the first layout. But, something’s not quite right still.

FIXING THE PLAYER

As it stands, the NPC can spot the player, and trigger the required action, you can still see and control the player on the Battle layout, which is not what we want! So, we need to do some tweaking to the player events.

The first thing to do is make the player invisible and uncontrollable in the BattleLayout – the last thing you need in the middle of a fight is a little person wandering about, getting in the way! This will use another On start of layout event:

CONDITION

System ▶︎ On start of layout

SUB-EVENT CONDITION

System ▶︎ CurrentLayout = “BattleLayout”

SUB-EVENT ACTION

Player ▶︎ Set visibility Invisble

System ▶︎ Set group “Player States” Deactivated

System ▶︎ Set group “Player Interactions” Deactivated

But when we come back from the BattleLayout, then the player needs to be seen and controllable once again:

SUB-EVENT CONDITION

System ▶︎ CurrentLayout ≠ “BattleLayout”

SUB-EVENT ACTION

Player ▶︎ Set visibility Visble

System ▶︎ Set group “Player States” Activated

System ▶︎ Set group “Player Interactions” Activated

And just to make sure your player doesn’t get stuck in its Talking state, add this final sub-event:

SUB-EVENT CONDITION

System ▶︎Is NOT BattleInProgress

Player ▶︎ State = “Talking”

SUB-EVENT ACTION

Player ▶︎ Set State to “Normal”

Now your player shouldn’t be visible when you enter the BattleLayout, but should still function as intended when not on that layout!

Nesting all of that under the one Start of Layout event you had in the initialisation group should look something like this:

Now we can add that challenge mechanic to the other NPCs!ADDING THE MECHANIC TO OTHER NPCS

It wouldn’t be much of a game if only the un-moving NPCs had this functionality attached to them, so let’s add it to the other variations!

ROTATING NPCS

When just dealing with the talking interaction, we didn’t need to really worry about the direction on the rotating NPCs. However, this challenge mechanic relies on the direction to cast the ray, so we need to make sure these NPCs are assigning themselves a direction. When the NPC is in any of the ‘rotating’ states (that’s Clockwise and AntiClockwise for our non-random and Rotating for our random NPCs) then we need to assign a direction based on the animation frame currently playing:

CONDITION

Rotate_NPC_Sprite ▶︎ State = “Clockwise”

SUB-EVENT CONDITION

Rotate_NPC_Sprite ▶︎ Animation frame = 0

SUB-EVENT ACTION

Rotate_NPC_Sprite ▶︎ Set Direction to “Down”

Repeat this sub-event for the remaining three directions, and then for the AntiClockwise and Rotating states. Remember that for the AntiClockwise state, your left-facing and right-facing animation frames will be reversed compared to Clockwise.

That’s the only extra functionality you need to add for the two rotating types of NPCs, now you can add the rest of the changes that you made to the Static NPC.

PATH-BASED NPCS

Adding this mechanic to the Set Path NPC is the same as you did for the Static NPC, just make sure you’re using InterruptedState and InterruptedDirection as opposed to InterruptedState2 etc.

For the Random Path NPC however, we’ll need to take into account the object’s Timers so that it doesn’t carry on moving when we’ve triggered the LOS actions. All this takes, however, is one extra action in the Ray intersected event block.

In the sub-event where we check if the NPC_Base RayHitID equals the Player’s UID, you simply need to add the following action to the top of the list of actions that trigger:

RandomPath_NPC_Sprite ▶︎ Stop Timer “RandomNPCMove”

This will stop the timer so that the NPC never picks a new state or direction and means that the state changes associated with the ‘battle’ mechanic can continue as they should. Then, as the NPC will have the StartFight state when the layout is started again following the BattleScene, they’ll have the state and direction they started with, which should trigger the timer to start again.

And that’s us done! Now you should have a functioning system where your NPCs can notice the player and trigger an action. There’s probably a lot of tidying that could be done with this project, but I’ve kept a lot of things separate to try and better illustrate what’s actually happening in the project.

Hope you found this useful!

  • 0 Comments

Want to leave a comment? Login or Register an account!