Armazenando objetos de jogo em vários contêineres

7

À luz do DRY , parece desejável armazenar uma coleção de objetos de jogos relacionados em apenas um contêiner. No entanto, pode ser necessário subconjuntos desses objetos em vários contextos. Pode ser sensato armazenar esses subconjuntos específicos em recipientes específicos e mais adequados. Isso aumenta o esforço para rastrear objetos entre contêineres, por exemplo, quando objetos são removidos do mundo do jogo.

Quais são as possibilidades de simplificar esse design e quais são os trade-offs típicos?

Ilustrar:

Em um jogo de RPG para vários jogadores, o servidor pode conter uma coleção de personagens do jogo em um mapa adequado para pesquisa por ID.

world
    map<id, Character> allCharacters

Um personagem também pode residir em um determinado nível de jogo. Para identificar todos os personagens presentes em um nível, pode parecer adequado introduzir um contêiner para cada nível que contenha os personagens atualmente presentes nele. Dessa forma, você pode executar uma lógica comum para todos os caracteres nesse nível.

world
    map<id, Character> allCharacters

    [levels]
        level1
           vector<Character> charactersOnPlayfield
        level2
           vector<Character> charactersOnPlayfield
        ...

Indo ainda mais longe, quando um personagem interage com as mensagens do mundo só deve ser roteado para caracteres dentro do alcance. Esse gerenciamento de interesses pode ser alcançado dividindo-se cada nível em uma grade de células, cada uma das quais armazenando os caracteres atualmente em pé nela.

world
    map<id, Character> allCharacters

    [levels]
        level1
           vector<Character> charactersOnPlayfield
           [cells]
               cell1
                   vector<Character> charactersOnCell
               cell2
                   vector<Character> charactersOnCell
               ...
        level2
           vector<Character> charactersOnPlayfield
           [cells]
               cell1
                   vector<Character> charactersOnCell
               cell2
                   vector<Character> charactersOnCell
               ...
        ...

Os objetos Personagem em diferentes níveis de abstração introduzem a necessidade de pensar cuidadosamente sobre a propriedade do objeto e o tempo de vida.

Observe que os objetos Character armazenados nos contêineres seriam naturalmente referências, não cópias. Além disso, estou assumindo que não há coleta de lixo.

jmp97
fonte

Respostas:

6

Eu aconselho a não ter várias listas como essa e, em vez disso, use algum tipo de biblioteca de sinal / slot (que basicamente é o padrão do observador) para facilitar o padrão "chamar uma função em todos os objetos nesta área / zona".

Os objetos podem gerenciar com facilidade o sinal que conectam a si mesmos, ou seja, toda vez que se movem, desconectam-se do sinal que representa seu slot antigo e se conectam ao novo, se você for tão refinado.

Tetrad
fonte
Isso ou criando coleções observáveis ​​/ filtradas somente leitura que baseiam seu conteúdo em outra coleção. Como alternativa, você pode criar coleções particionadas que podem ser visualizadas em termos das coleções filho (uma representando cada 'partição') ou de toda a coleção (agregada). Geralmente eu uso uma dessas abordagens, a menos que haja algum motivo convincente para um objeto ter associação discreta em coleções separadas (geralmente, não existe). Em qualquer um dos casos, você precisa considerar o acesso simultâneo e provavelmente desejará um recurso de captura instantânea / versão.
quer
3

Todo caractere deve, de uma forma ou de outra, conhecer todos os contêineres em que está contido. Isso pode ser explícito (uma lista real dos contêineres que o referenciam), computável (as coordenadas de caracteres são armazenadas, a partir das quais os vetores de células reais podem ser derivados ) ou não ditas ("os caracteres também podem ser armazenados em qualquer uma dessas seis listas e não vou me incomodar em acompanhar quais"). Ou qualquer combinação dos três. O importante é que é fácil e eficiente encontrar todos os locais que olham para um personagem.

Também deve haver um armazenamento canônico que armazene todos os personagens do jogo, como o mapa allCharacters lá em cima.

Um personagem está vivo se e somente se estiver contido em todos os caracteres. Se você deseja removê-lo de allCharacters, remova-o de todos os contêineres que também o contêm (que, conforme o primeiro parágrafo, devem ser razoavelmente eficientes) e está pronto.

Isso pressupõe que você não tem coleta de lixo - se o fizer, poderá deixar o GC cuidar disso. Se você tiver vazamentos, faça um contêiner de referência fraca contendo todas as entidades e use-o para analisar quais entidades estão vazando.

ZorbaTHut
fonte
Obrigado, isso é útil. Estou assumindo que não há coleta de lixo, acrescentou isso à minha pergunta.
Jsp97 #
Eu nunca usei uma plataforma na qual eu possa confiar no GC para a lógica de jogo. A maioria das plataformas possui um atraso - de comprimento variável, para GCs geracionais - que pode levar dezenas de quadros, antes de realmente coletar alguma coisa. Algumas linguagens como o Python garantem a finalização de referências não circulares imediatamente, mas é muito raro não tê-las nos jogos, especialmente no momento da morte (A e B atirando um no outro; um morre).
@ Joe, eu realmente não estou falando sobre usá-lo para a lógica de jogo, mas você pode usá-lo facilmente para as partes difíceis da coleção de cruft. Com o hipotético jmp97, você pode ter um sinalizador "o jogador ainda está vivo" e, em seguida, adicionar uma condição simples ao sistema "transmissão de interação" para garantir que o jogador esteja vivo antes de enviá-lo. (Ou insira isso na passagem da estrutura de dados.) Nesse ponto, sua lógica de jogo é centralizada nesse sinalizador simples e você também pode contar com o GC limpando a memória posteriormente.
precisa saber é o seguinte
2

Ao responder minha pergunta com a experiência que tive até agora, basicamente vejo estas opções:

Extraia sua coleção mais específica de objetos da coleção mais geral sempre que precisar

  • pro: repositório central de objetos
  • con: sobrecarga computacional

Armazene sua coleção mais específica em contêineres especializados, conforme fornecido no exemplo desta pergunta

  • pro: pesquisa rápida / dados pré-classificados
  • con: rastreamento de objetos mais envolvido

Use cache para determinadas perspectivas / subconjuntos dos objetos do jogo

  • pro: meio termo entre 1 e 2
  • con: precisa criar uma estratégia de cache que possa complicar as coisas

Os fatores para a escolha de uma dessas opções incluem

  • com que frequência a coleção específica de objetos é necessária
  • qual é o custo para rastrear os objetos nos vários contêineres (pode ser relevante apenas no caso de remoção de objetos)

O padrão observador pode ajudar a propagar a necessidade de alterar os outros subconjuntos de objetos.

jmp97
fonte