The following files have been attached to this tutorial:
This tutorial hasn't been translated.
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.
Welcome to this NPC tutorial – now complete and with the example project included!
The aim of this is to show you how to create a system to control multiple types of non-playable characters, which can all act independently of each other. The tutorial will be covering Static, Rotating, Set Path and Random Path styles of movement.
The tutorial itself will recreate how I built the example project, which you can then use for your own games! It might not work for everyone, but it's a good starting point.
This system uses states to dictate what each instance of an NPC should be doing – should it be walking, turning, idling etc. These states are then set using instance variables.
Briefly, I’ll also cover how to have a player character interact with that NPC. It will only have placeholder code in it, but that should be enough to get you started with your own interaction system!
So, let’s start by setting up the project (note I’m using graphics by Kenney, which are available from their website.)
You’ll need one event sheet and two layouts. The first layout is going to be our ‘game screen’ where we can see each type of NPC working, while the second will be an Object Bank. The game layout’s dimensions are 320x320, but it really doesn’t matter for the Object Bank – it just needs to be large enough to hold a handful of sprites.
The point of the object bank, in this case, is to give you a space in which to organise your NPC objects. In this example, you have two sprites for each NPC – a base and the animations (hereby referred to as Sprite).
The base will do all the moving, while the sprite will handle animations, instance variables etc.
In this layout, you’ll see that I have five sets of squares and characters (bases and sprites). These are all paired together using a container. This way, I only need to place the sprite on the game layout, but the base will then be created when the sprite is at the start of the layout. This is also why the sprite will hold all of the important, customizable instance variables – if they were attached to the base, then I couldn’t change them on individual instances because it doesn’t exist on the layout until the layout is created.
Now that we have our bases and sprites, we need to create a family for each. This will make it easier to apply a set of common features to both object types.
The family for the bases (NPC_Base) needs two family behaviors – tile movement and solid. Make sure that default controls are disabled on the tile movement behaviour too. This will allow our characters to move around but won’t allow them to pass through each other or the player. The family only needs one family instance variable: Pair_ID. We’ll be using this to make sure the base is always paired to its sprite.
The sprite family (NPC_Sprite) will have just one family behavior: Timer. We’ll be using timers in some of our movement controls. The family does require a fair few instance variables though:
In order to test some of the logic we’re creating in this system, we’ll need a ‘player character’. This will just be a square sprite with the Tile Movement and solid behaviors and the instance variables MovementDirection and State.
Before we delve into how to set up the NPCs, there is some functionality we’re going to need for the player. We need to define a couple of states for the player, make sure that the instance variable MovementDirection is set according to the TileMovement direction, and we need to set up the interaction system.
Our player will have two states in this example – Normal and Talking. When in the Normal state, TileMovement is enabled and the player can move about freely. In Talking, we disable TileMovement so that the player can’t abandon an NPC they’re interacting with.
Let’s start with the Normal State, where we’ll also set the player’s MovementDirection instance variable. Start with an event with the following:
Player ▶︎ Compare Instance Variable ▶︎ State = “Normal”
Player ▶︎ Set TileMovement Enabled
We don’t need to worry about setting events for making the player move as we can use the default controls in the TileMovement behavior. But we can use the behavior’s Is moving in direction condition to set the instance variable.
So, in a sub-event from the one you just created, put the following:
Player ▶︎ TileMovement ▶︎ Is moving Right
Player ▶︎ Set Movement Direction to “Right”
Create three more of these sub-events for the remaining three directions.
Our second state, Talking just requires one simple event:
Player ▶︎ State = “Talking”
Player ▶︎ Set TileMovement Disabled
Now we can use our states in the interaction system. (Make sure you’ve added the keyboard object!) If the player is overlapping an NPC and the Z key is pressed, we want to fire a set of placeholder events – as if the player has interacted with the NPC. So, we need the following:
Keyboard ▶︎ On Z pressed
Player ▶︎ State ≠ “Talking”
Player ▶︎ MovementDirection = “Right”
Player ▶︎ Is overlapping NPC_Sprites (family) at offset (8,0)
NPC_Sprites ▶︎ Set InterruptedDirection to Self.Direction
NPC_Sprites ▶︎ Set InterruptedState to Self.State
NPC_Sprites ▶︎ Set Direction to “Left”
NPC_Sprites ▶︎ Set State to “Talking”
Player ▶︎ Set State to “Talking”
This chunk means that if the player is on the left-hand side of an NPC and is facing it, and the Z key is pressed, the NPC should change direction to face the player, and enter the “Talking” state. Like before, create three more of these blocks for the remaining three directions. You should end up with this:
And that’s the interaction system finished – time for the first NPC!
It's a bad idea to use Wait, about 10% of the times for complex projects it bugs and doesn't work, especially in triggers. Using a global timer would be better, though a bit clunkier.
Nice! This will also help the user get familiar with functions and passing parameters through which is always daunting to think about when you haven't used them.
Tell me about it! I'd been putting off using functions for ages because I had no idea what to do with them!