How to organize classes more than one GameMode/GameState.

Looking for some advice how to organize communication between Game Modes, Game States, Characters etc.
I’m facing a problem, when I have one a bit complex PlayerCharacter class where part of the functionality is directly related with specific game mode.
I’m aware that’s not the perfect solution and that’s why I would like to get some hints how to organize it better. Maybe, some of you have an experinence in managing more than one GameMode/GameState with the same character. I mean about using the same character class for let’s say GameModeTeamDeadMatch and GameModeSearchAndDestroy.
I have some ideas, but i’m not sure which one I shoud go for:

  1. Delagates for character related events.
    Each specific character event should be handled on delagates. E.g when the player is dead, Character class broadcasts on a “Dead” delagate.
    Each GameMode should implement character dead event by itself, e.g GameModeTeamDeadMatch has it’s own implementation for OnPlayerDead() which is binded to player character “Dead” delagate and GameMode respawns a player after some short delay. In case GameModeSearchAndDestroy will be respawned at the beggining of the next round.

  2. Game mode/Game state intefaces interface
    From character perspective, each communication with GameMode or GameState should be done through common interface.
    Each specific GameMode/GameState implements the Interface by it self to provide a custom functionality.

  3. More OOP architecture

    Specific character class for each game mode. The first would be some basic character with common functionality for all modes(movement, shooting etc.).
    Child classed created for specific modes, e.g PlayerCharacterDeadMatch which works only with GameModeTeamDeadMatch, and other PlayerCharacterSearchAndDestroy which is used only for GameModeSearchAndDestroy. Child character classes adds some specific per mode functionality to the character, e.g PlayerCharacterSearchAndDestroy can equip/plant/defuse bomb.

Thanks in advance to any advice.

You got most of it correct tbh except for the Character inheritance stuff.
You should aim for Characters to be all-purpose and not specific to a particular Game Mode. That way, you can easily switch Game Modes without having to make a new Character.

If you absolutely require per-Game Mode capabilities that you don’t think should always be allowed on the player, then it may be better to let the Game Mode add a Component to the Character that provides the player with those additional capabilities. In this case, composition always beats inheritance.

Game Mode: defines the game rule set and processes those rules.

Game State: stores the current game-wide state (shouldn’t really do any logic. It’s mainly for replication purposes).

Player State: stores player-specific state (shouldn’t really do any logic. It’s mainly for replication purposes).

Player Character: the living state and capabilities of the player. (By living state, I mean this state should be considered transient and will be wiped out upon death).

Player Controller: handles input and stores sensitive information between client and server. Is also used to allow the client to directly communicate to the server via RPCs (since it’s the only thing out-of-the-box that the client actually owns).

Game Instance: kinda like engine-game state. It’s mostly useless but there’s some use cases. Usually used for managing local players, but no one really cares about split screen anymore.

The utilisation of these objects only really matters for arena-style multiplayer games, like the ones you mentioned. For single player games or those that play much more differently, these objects are mostly irrelevant and if anything, will just get in your way.

I don’t think there’s a right way to do this, but there are a few options.

Delegates would work if the game mode is spawning the characters. They are harder to debug though.

I think an easier way would be to cast the game mode within your character class, see what kind of game mode it is, and then act on that. In fact, you could even fetch this information when you spawn the character and cache it.

You could also have a base class for the game mode and create common functions to be used in both of your game modes that derive from that base class. Then it would automatically figure out what function to call when called from the character.