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!