Como criar um GameManager?

Para criar esse GameManager vamos considerar um jogo de turnos. (Exemplo: Pokemon, Yu-Gi-Oh, Final Fantasy, etc.)

De cara já vamos criar uma classe e chama-la de GameManager. E logo a baixo criaremos um enumeration chamado GameState.

public class GameManager : MonoBehaviour {

}

public enum GameState {
    PlayerTurn = 1,
    EnemyTurn = 2,
    Decide = 3,
    Victory  = 4,
    Lose = 5,
}

O enum GameState vai nos garantir uma leitura mais fácil do nosso código. Além disso, criamos o enum fora da classe porque dessa forma podemos acessa-lo globalmente sem precisar do GameManager.

Para saber qual o GameState atual do seu jogo precisaremos de uma variável que salve esse valor e que possa ser acessada de qualquer lugar. Além disso, criaremos também um singleton para facilitar o acesso de outros arquivos ao GameManager.

public class GameManager : MonoBehaviour {
    public static GameManager Instance { get; private set; }
    
    private static GameState _currentState;
    [field: SerializeField]
    public static GameState CurrentState {
        get {
            return _currentState;
        }
    }
    
    private void Awake() {
        Instance = this;
    }
}

public enum GameState {
    PlayerTurn = 1,
    EnemyTurn = 2,
    Decide = 3,
    Victory  = 4,
    Lose = 5,
}

Após isso, precisamos criar um método para lidar com as trocas e todas as possibilidades de GameState, então criaremos o método ChangeState()

public class GameManager : MonoBehaviour {
    public static GameManager Instance { get; private set; }
    
    private static GameState _currentState;
    [field: SerializeField]
    public static GameState CurrentState {
        get {
            return _currentState;
        }
    }
    
    private void Awake() {
        Instance = this;
    }
    
    public void ChangeState(GameState newState) {
        _currentState = newState;
        
        switch (_currentState) {
            case GameState.PlayerTurn:
                break;
            case GameState.EnemyTurn:
                break;
            case GameState.Victory:
                break;
            case GameState.Lose:
                break;
            default:
                break;
        }
    }
}

public enum GameState {
    PlayerTurn = 1,
    EnemyTurn = 2,
    Decide = 3,
    Victory  = 4,
    Lose = 5,
}

Agora que temos já uma boa parte do código feita, precisamos criar os métodos para cada State, então faremos assim.

public class GameManager : MonoBehaviour {
    public static GameManager Instance { get; private set; }
    
    private static GameState _currentState;
    [field: SerializeField]
    public static GameState CurrentState {
        get {
            return _currentState;
        }
    }
    
    private void Awake() {
        Instance = this;
    }
    
    public void ChangeState(GameState newState) {
        _currentState = newState;
        
        switch (_currentState) {
            case GameState.PlayerTurn:
                HandlePlayerTurn();
                break;
            case GameState.EnemyTurn:
                HandleEnemyTurn();
                break;
            case GameState.Decide:
                HandleDecide();
                break;
            case GameState.Victory:
                HandleVictory();
                break;
            case GameState.Lose:
                HandleLose();
                break;
            default:
                Debug.Log("Não faz nada ou joga um erro!");
                break;
        }
    }
    
    private voic HandleLose() {
        Debug.Log("Mostra uma UI de DERROTA");
    }
    
    private voic HandleVictory() {
        Debug.Log("Mostra uma UI de VITÓRIA");
    }
    
    private voic HandleDecide() {
        Debug.Log("Irá decidir o que acontece depois do turno");
        Debug.Log("Caso o jogador esteja morto, vai mudar o estado para GameState.Lose");
        Debug.Log("Caso o inimigo esteja morto, vai mudar o estado para GameState.Victory");
        Debug.Log("Caso nenhum dos dois estejam mortos o jogo segue para o próximo turno");
    }
    
    private voic HandleEnemyTurn() {
        Debug.Log("Impede que o jogador jogue nesse turno!");
        Debug.Log("Deixa o inimigo jogar nesse turno!");
    }
    
    private voic HandlePlayerTurn() {
        Debug.Log("Impede que o inimigo jogue nesse turno!");
        Debug.Log("Deixa o jogador jogar nesse turno!");
    }
}

public enum GameState {
    PlayerTurn = 1,
    EnemyTurn = 2,
    Decide = 3,
    Victory  = 4,
    Lose = 5,
}

E com isso temos um GameManager funcional. Claro que será necessário alguns ajustes a depender do seu jogo, mas temos uma boa base para avançar e continuar o desenvolvimento do projeto.

Um exemplo de uso seria a seguinte possibilidade, imagine que você tem uma UI que mostra as possíveis ações do jogador, e uma dessas ações é encerrar o turno. No onClick desse botão você colocaria o seguinte método.

public void FinishTurn() {
    // Estamos mudando o estado para Decide
    // Por que esse estado é responsável por saber se o inimigo morreu
    // Ou se está vivo, para decidir se o jogo acabou ou se vai para
    // O próximo turno
    GameManager.Instance.ChangeState(GameState.Decide);
}

Nesse post passamos por alguns detalhes que não me aprofundei muito, porque pretendo fazer outras publicações sobre cada tema, como por exemplo, singletons, enumerations, propriedade auto-implementada, uma ideia de padrões para nomes de variáveis, ideias de como organizar o inspector entre outras coisas.

Quero aproveitar esse espaço para pedir a sua ajuda, esse é a minha primeira publicação aqui, e estou aceitando críticas construtívas, então se tiver algo que você reparou que possa me ajudar a melhorar, se tiver algo a acrescentar ou algo a corrigir, por favor me avise, ficarei muito feliz e agradecido com o feedback.

Bom, é isso, espero que tenha tido alguma utilidade para você e caso tenha dificuldade em algo ficarei feliz em ajudar no que eu puder.

:}

Créditos

Me inspirei bastante nesse vídeo do Tarodev.