How to handle a player leaving in a multiplayer game

Started by
6 comments, last by hplus0603 6 years, 3 months ago

Hello,

This is a general programming architecture question.

In a multiplayer game, what is your approach to handle if one of the players has left?

The problem:

* Let's say there is one Player object that represents this player who has left the game.
* Now every game component that referenced this Player object has to set its references to NULL, so the Player object can be either garbage collected.
* These game components may also have to reset their internal state.
 

Possible approaches:
1. Subscribe all such game components that deal with Player objects class to a "Player Left" event which is broadcasted by the framework.
2. Do not store references to Player objects in any classes, but instead store only a Player ID number. Then the Player object can be received from the framework by the ID, or the framework shall return NULL if the player has already left the game.
3. Any other approach?

So what do you think about 1-3 points?

Advertisement

How do you handle if a grenade explodes? Everyone who has a reference to that grenade need to remove that reference. It's the same problem.

Either option works, and they each have benefits and draw-backs. If you don't do a lot of object/object interaction directly, the ID may be easier. If you do a lot of objects-talking-to-objects, then a cached pointer is likely better.

 

enum Bool { True, False, FileNotFound };

What language are you using?

In C++ 11 an option might be to use weak_ptr? I don't know if this differs significantly in performance from rolling your own with an ID.

in C# a WeakReference can be used which is similar.

Edit: I haven't really used either and would probably default to using an ID. But I'm facing similar considerations soon so I'll be tagging along for the pros and cons (I'm using C# myself).

 

 

Developer journal: Multiplayer RPG dev diary

Thank you both of you.

I don't have much experience with C++ so let's leave the weak_ptr to the experts.

I'm programming in Java. I didn't know Java has WeakReference too.
With C#/Java WeakReference, you don't know when the garbage collector will actually collect the object, it may be done only later. In consequence you would also have to check a player.isConnected() flag before working with the object. This would also bring a kind of non-determinism to your code, which is bad (same principle as Effective Java Item 7: Avoid Finalizers).

I think what I'm going to implement in Java is something like this:


interface PlayerEnteredListener
  entered(player)

interface PlayerLeftListener
  left(player)

class PlayerManager
  entered(Player)
  left(Player)
  addListener(PlayerEnteredListener)
  addListener(PlayerLeftListener)
  removeListener(PlayerEnteredListener)
  removeListener(PlayerLeftListener)
  //other methods:
  List<Player> getAllPlayers()
  Player getPlayer(int ID)

This should be simple and easy to debug.

This would also bring a kind of non-determinism to your code, which is bad

The core problem is that "players disconnecting" is non-deterministic. You don't have control over that.

What you can do in your code is have a clear interface to the non-deterministic parts of the program (player connection/disconnection and player commands) and funnel between non-deterministic and deterministic domains (code modules) in your program at pre-determined times (typically, once every main "tick" or simulation step.)

In many ways "player X disconnected" is no different from "player X walked forward" and is another event that your system needs to react to and do the right thing for. Typically, your network connection handling code will forward events from the connection, which includes things like "player gave a command," and thus also "player disconnected," and the rest of the code simply reacts to those commands. Your world, in turn, typically will emit events such as "bomb B exploded at position C" or "player Q picked up health pack H" or whatever. This will also include "player X disconnected" so that other players and entities who need to know this, find out about it.

Yes, this means that you have to sequence the shutdown of players, because other players will be referencing the original player entity after the network has disconnected but before everything is properly torn down. Typically, you solve this by having "network connection" be different from "player entity" in your object model. And, typically, you actually end up with an even finer granularity because different objects need different lifetimes. For example, "player controller objects" are often as thing, as are "player world effects" (spells, etc) Each of these objects have a carefully managed, well defined lifetime, and the role of the game engine is to make sure that objects live during their lifetime, emit events to those who need to see those events, and then reap the objects when they are supposed to go away.

enum Bool { True, False, FileNotFound };

I don't have an opinion on how to implement this in a specific language, but as far as the approach goes, I really like storing ID's to objects instead of using references. One reason I took this approach with my multiplayer game is it translates seamlessly to client-server communications. If a player clicks on another player to select them, I store the player ID in the client UI layer as the 'targetted player' and query the server for the player data. I take the same approach in all the server code, for example if a player has an effect applied by another player, then again I store the ID of that player rather than a reference.

The only downside to this approach is of course the lookup cost when you do need to get the reference to do some work. At the moment I'm just searching arrays for the player so it's O(n), which of course if fine when you don't have many players, but eventually a hashtable will be better. But that'll be one of those "problems that mean you're successful" :)

Good luck!

ChasmLords is a multiplayer roguelike with faction based PvP and dungeon crawling. Join the beta at Chasmlords.com!

storing ID's to objects instead of using references

And what do you do when the object that the ID references, no longer exists?

Storing an ID may prevent the dereference of a stale pointer, but it doesn't prevent a bug, and in many ways, the crash from a bad pointer is better than the silent failure of a stale ID, because finding the former so you can fix it, is easier!

 

enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement