A entidade deve lidar com seu próprio movimento?

Dec 06 2020

No momento, estou construindo um jogo simples baseado em turnos com uma Sala que consiste em uma matriz 2D de objetos Tile. Esses podem ser WallTile ou FloorTile. Os objetos de bloco têm um vizinho norte, leste, sul e oeste. Os FloorTiles têm uma pilha de entidades para que eu possa desenhar facilmente a entidade superior.

Agora estou me perguntando, meu Jogador é uma Entidade, quando o jogador se move, o jogador deve lidar com essa lógica, ou o Jogo que contém o meu Jogador?

No primeiro caso, seria algo como:

class Player : Entity {
    public Tile CurrentLocation

    public bool CanMoveTo(Direction) {
        // Let's say that direction is East
        return CurrentLocation.EastNeighbour == FloorTile
    }

    // This method does the actual move
    public void MoveTo(Direction)
}

No segundo caso, seria assim:

class Game {
    public Player Player;
    public Room Room;

    public void Update() {
        // Lets say we get direction from some input from somewhere
        if Room.TileAt(player.Y, player.X) is FloorTile then MoveThere()
    }
}

Não tenho certeza de qual é a melhor das duas opções. Inicialmente estou pensando na primeira solução. Qualquer ajuda ou dicas serão apreciadas.

Respostas

4 Philipp Dec 06 2020 at 11:26

Não existem maneiras certas ou erradas de fazer as coisas. Apenas maneiras que funcionam ou não funcionam para você.

Existem várias filosofias concorrentes na arquitetura de jogo convencional.

R: Arquitetura orientada a objetos

Cada entidade deve ser representada por um objeto. Esses objetos devem conter toda sua própria lógica, encapsular seu estado interno e se comunicar com outros objetos por meio de interfaces bem definidas. O motor principal do jogo apenas chama os métodos "Atualizar" e "Renderizar" de todas as entidades em um loop e então permite que as entidades façam o resto.

Quando você tem objetos que compartilham alguma funcionalidade, normalmente faria isso por meio de herança - extraindo a funcionalidade comum em uma classe base comum. Por exemplo, você pode querer ter uma classe Jogador e uma classe Inimigo que herdam de uma classe Combatente que contém todo o código de combate e herda de Entidade que contém o código básico para movimento. E Inimigo provavelmente seria uma classe abstrata que contém apenas a lógica comum a todos os inimigos, com os diferentes tipos de inimigos e seus comportamentos únicos implementados em classes herdadas de Inimigo.

Basicamente, a maneira que você deve fazer OOP pelo livro.

Esse estilo é frequentemente visto em desenvolvedores que vieram do desenvolvimento de aplicativos, porque é o estilo predominante lá. E o que funciona para aplicativos não é necessariamente completamente errado para jogos.

B: Arquitetura Entidade-Componente

Este estilo prefere composição em vez de herança . Uma entidade não é um objeto, é uma coleção de objetos com cada objeto implementando um recurso diferente dessa entidade. Portanto, você não tem uma classe "Player" implementando todas as coisas que um jogador pode fazer. Você tem uma Entidade genérica com um componente "Sprite", "Mover", "Shooter", "Hitpoints" e "PlayerInput".

A vantagem dessa abordagem é que os componentes são muito fáceis de misturar e combinar para criar novos tipos de entidades de jogo. Isso é muito útil para iterações rápidas no design do seu jogo. É a abordagem favorita de alguns motores de jogos populares. Como o Unity, por exemplo.

C: Arquitetura Entidade-Componente-Sistema

Uma entidade deve ser uma coleção de componentes exatamente como na arquitetura "Entidade-Componente". Mas, nessa arquitetura, esses componentes devem ser apenas portadores de dados burros. Normalmente na forma de estruturas com apenas variáveis ​​públicas e nenhum método. Toda a lógica deve estar em sistemas. Cada sistema é responsável por gerenciar um tipo de componente ou um conjunto de componentes. Por exemplo, você tem um MovementSystem que atualiza o componente Posição de cada entidade com um componente Movimento.

Mas isso é apenas uma simplificação exagerada. Se quiser saber mais, consulte a pergunta "O que é ECS puro?" . Você também pode consultar outras questões em nossa tag entidade-componente-sistema .

A abordagem ECS está muito na moda. O principal argumento para isso é que permite algumas otimizações de desempenho muito legais (particularmente quando se trata de localidade da memória e multithreading) sem sacrificar a modularidade e a velocidade de iteração da abordagem Entidade-Componente. Mas a desvantagem é que uma arquitetura ECS sólida e flexível requer uma grande quantidade de código de arquitetura que é executado "nos bastidores" e que pode ser difícil de implementar para iniciantes.

Qual você deve usar?

Isso é algo que você precisa descobrir sozinho. Mas seja qual for a abordagem que você escolher para o seu jogo, seria aconselhável mantê-la. Evite misturar e combinar padrões de arquitetura em seu jogo quando possível.

Turing Dec 09 2020 at 19:08

Basicamente, depende da quantidade de dados que passam pela rede do jogo. Qualquer multijogador massivo precisa manter o controle de quem está onde fazendo o que sempre (caso contrário, haverá trapaça massiva). No entanto, receber e enviar pacotes do servidor para todos os clientes (o que é conhecido como servidor autoritário) raramente é prático para qualquer quantidade de usuários significativos. Portanto, a filosofia parece ser quanta lógica você pode fazer com que o jogador controle sem afetar os outros jogadores. Em outras palavras, o que afeta você e somente você (exceto para saques) pode ser tratado pelo cliente. Considerando que tudo o que afeta vários jogadores deve ser tratado pelo servidor. Ou, se não for tratada, pelo menos autorizada antes que qualquer efeito ocorra.