Ways of working with NPCs in a multiplayer game (p.1)

The topic of managing NPCs in games is an interesting and less discussed one. In this article we’re going to cover some concepts and strategies to work with NPCs in our multiplayer game, focusing on different modalities of interaction with real players and the game environment.

NOTE: as a prerequisite for this article we assume you have at least a basic understanding of how Extension development works in SFS2X. If you need a refresher please check this article.

» The basics

Let’s start with the basics of creating one or more NPCs in an SFS2X extension, which is very easy:

User myNpc = getApi().createNPC("npc", getParentZone(), false);

Essentially we just need to provide a unique username and a reference to the current Zone. The last parameter is called forceLogin and is set to true if you want to kick out another User that exists with the same username.

In return this api call gives us a regular User object that represents the new (virtual) player and that behaves exactly like any other user in the system. We can join it in a Room, set some User Variables or send chat messages to other people on its behalf.

Like any other client the User object also has a Session object linked to it that represents the connection. In this case however the Session’s type is set to VOID which indicates a connection-less session (i.e. no socket is really used).

You can verify this by running this code and checking out the output:

System.out.println("Session type: " + myNpc.getSession().getType());

whereas a real player will have a Session of type DEFAULT (for TCP), WEBSOCKET or BLUEBOX depending on the type of connection in use.

» Controlling the NPCs

There are essentially three modalities that we normally employ to control non player characters in a game:

  1. Direct player/NPC interaction: e.g. a player clicks or sends a message to a specific NPC in the game
  2. NPC Triggers: a player enters the “range” or Area of Interest  (AOI) of a certain NPC which in turn activates a reaction (NPC starts shooting or chasing or a dialogue is initiated etc…)
  3. Independent NPC activity: the NPC is running its own AI logic, unrelated to other player’s actions

Depending on our game complexity and requirements we may to employ one, two or all three modalities. Let’s now discuss each one a little more in detail and see how they should work.

1) Direct player/NPC interaction

NPC1-zelda

This approach is very simple to implement. A common use case is a player clicking directly on a character and activating a dialogue or similar action. Since the request is initiated by the player we can implement this by sending an Extension call with the ID of the NPC (user id) and a code for the action to be performed, for example “dialogue”.

This in turn is handled on the server side where a dialogue tree is available and the conversation begins. Easy peasy.

In terms of NPC state it is convenient to attach it directly to the NPC’s User object so that it’s accessible at any time. In other words we can create any class that models our non player character’s state and then store it in the User’s object as shown in the following pseudo-code:

public class NpcState
{
	// declare fields

	// some methods
	public int getCoins() { ... }

	public void setCoins(int amount) { ... }

	public List getInventory() { ... }

	public boolean isActive() { ... }
}

// attach state to User object
npcUser.setProperty("state", new NpcState());

// ...
// ...
// later the object can be accessed and modified

NpcState state = (NpcState) npcUser.getProperty("state");
state.setCoins(100);

2) NPC Triggers

NPC2-bastion

Triggers will activate NPCs based on different parameters, a typical example is proximity. For instance enemies will detect the player and start shooting or chasing him once he’s close enough.

To work with triggers we can create a basic event system that allows the Extension to send internal notifications when a certain NPC detects a player in its range. This can be implemented in a myriad of ways depending on the nature of the game and its rules, but essentially it boils down to a simple detection loop.

Here’s a pseudo-code example for a 2D game:

void onPlayerMove(User user)
{
	float x = user.getVariable("x");
	float y = user.getVariable("y");

	for (User npc : npcList)
	{
		if (npc.detectPresence(x, y))
		{
			dispatchEvent(new DetectionEvent(npc, user));
		}
	}
}

The onPlayerMove(…) method is triggered every time a player changes his position: we loop through all NPCs and check if the player falls within the “detection area” of the NPC. If it does we can dispatch a new event and trigger the specific logic for this occurrence (e.g. the NPC starts chasing the player).

This approach is particularly effective used in conjunction with MMORooms and the relative MMO Api available in SmartFoxServer 2X. The MMORoom can handle thousands of players by partitioning the virtual 2D/3D space in smaller sectors and updating players that fall within each other’s AOI.

This way the loop through an NPC list becomes far more optimized as we don’t need to verify every NPC in the Room but only those that are in the immediate surroundings of the player.

3) Independent NPC activity

NPC3-icewinddale

Sometimes NPCs stand still waiting for something to happen, maybe looping through simple “idle” animations but there are also many use cases where we need them to move around or be active in the game world.

This independent activity is not triggered by direct player action or events and needs to be carried on while the NPC is unengaged. We may have a shop keeper tending to his emporium, a farmer doing work in his land, or some vehicle driving around in the traffic. Regardless of the specific task, this activity runs independently of player’s actions as a server side simulation.

The simulation can be set in motion via a Scheduler’s task running at specific intervals. Inside the task we can go analyze the list of active NPCs, determine those that need an update and finally call the relative class or method that handles the AI.

In pseudo-code it would look like this:

class NPCUpdateTask implements Runnable
{
	public void run()
	{
		for (User npc : npcList)
		{
			if (npc.isActive())
			{
				if (npc.getType() == NPCType.CITIZEN)
					updateCitizen(npc);

				else (if npc.getType() == NPCType.GUARD)
					updateGuard(npc);

				else if (npc.getType() == NPCType.PET)
					updatePet(npc);
			}
		}
	}
}

// Extension's init method
@Override
public void init()
{
	getApi().getSystemScheduler().scheduleAtFixedRate(new NPCUpdateTask(), 0, 500, TimeUnit.MILLISECONDS)
}

Since NPCs are represented by User objects we can use standard User Variables and get all players updated immediately. Additionally, when using MMORooms, all these updates will be distributed to those players that fall in range with the NPC visibility, which is very helpful when there’re hundreds of NPC in the game.

» Managing the NPCs

Before we wrap up this overview there’s one final consideration to make about the management of NPCs. We have mentioned non player characters integrate seamlessly in SFS2X because they are represented with same (User) class employed for players.

While this is useful, it can make it difficult to access a list of available NPCs without searching them in global user list, available at Zone level. To avoid these potentially long and sub-optimal searches we recommend keeping a local List (or Map) of the NPCs created.

This way we have immediate access to what we need and we don’t need to delve through the massive global user list.

» Conclusion

We have taken a bird’s eye look at the basic strategies and modes of interaction with NPCs, but the story isn’t over yet. In the next article we will approach this topic from a different angle and explore another solution to implement interactive NPCs.

Stay tuned for part two.

Until then if you have questions you can join the conversation over at our forums.