Projetando um mecanismo flexível baseado em blocos

8

Estou tentando criar um mecanismo de jogo flexível baseado em blocos para criar todos os tipos de jogos de quebra-cabeça não em tempo real, assim como Bejeweled, Civilization, Sokoban e assim por diante.

A primeira abordagem que tive foi ter uma matriz 2D de objetos Tile e, em seguida, ter classes herdadas do Tile que representavam os objetos do jogo. Infelizmente, dessa maneira, não pude empilhar mais elementos de jogo no mesmo bloco sem ter uma matriz 3D.

Então fiz algo diferente: eu ainda tinha a matriz 2D de objetos Tile, mas cada objeto Tile continha uma lista na qual eu colocava e entidades diferentes. Isso funcionou bem até 20 minutos atrás, quando percebi que era muito caro fazer muitas coisas, veja este exemplo:

Eu tenho uma entidade de parede. Toda atualização que eu tenho que verificar os 8 azulejos adjacentes, depois todas as entidades da lista de azulejos, verificar se alguma dessas entidades é uma parede e finalmente desenhar o sprite correto. (Isso é feito para desenhar paredes próximas umas das outras sem problemas)

A única solução que vejo agora é ter uma matriz 3D, com muitas camadas, que pode se adequar a qualquer situação. Mas, dessa forma, não posso empilhar duas entidades que compartilham a mesma camada no mesmo bloco. Sempre que eu quero fazer isso, tenho que criar uma nova camada.

Existe uma solução melhor? O que você faria?

Vee
fonte
Como o uso de uma matriz 3D solucionaria seu problema com a situação de verificação na parede. Ainda não seria o mesmo?
Michael Coleman
Eu saberia que as paredes apenas permanecem na camada número 1. Então, eu posso fazer: Tiles [Wall.X - 1, Wall.Y, 1] é Wall?
Vee
Do que você não pode simplesmente verificar o primeiro elemento da lista? Não vejo como uma lista levanta um problema.
Michael Coleman
A parede pode estar em qualquer lugar da lista com a segunda abordagem. Preciso percorrer todas as Entidades e verificar se é uma parede.
Vee
4
Você realmente teve um problema de desempenho ou está apenas preocupado com isso? Você definiu o perfil?
munificent

Respostas:

2

Você realmente viu esse problema ou apenas pensou nisso? Porque não vejo como a iteração na lista de objetos tem algum efeito perceptível no desempenho. Você precisaria ter centenas de objetos por bloco para que isso importasse. A maioria dos jogos que eu consigo pensar tem 10, no máximo.

Se você realmente tiver um problema, a melhor ideia é armazenar dados em cache, não alterar a representação. No seu exemplo, a configuração da parede provavelmente não muda todos os quadros. Apenas armazene em cache o tipo correto de sprite, não o recalcule constantemente.

Se você quer fazer algum jogo maluco que tem muitos objetos e eles mudam o tempo todo, você pode criar diferentes camadas com diferentes semânticas. Por exemplo, a camada de parede contém apenas paredes, não mais que 1 por ladrilho e, por exemplo, a camada de decoração contém listas de objetos que nunca mudam.

Por fim, se você deseja ter uma arquitetura ultra-flexível que suporte idealmente todos os jogos possíveis - azar, não existe.

deixa pra lá
fonte
2

Duas sugestões Primeiro, você deve resolver o sprite com o qual cada bloco será desenhado durante o carregamento do seu mapa / nível do bloco. Deve ser fácil atravessar a matriz 2D em que seu nível está carregado e decidir que uma parede certiana precisa ter a forma de L e a outra precisa ter a | forma.

Segundo, eu armazenaria dados estáticos relacionados a um bloco em um local diferente dos dados ativos. Portanto, um objeto de bloco pode ter uma lista de objetos que entram e saem do bloco, mas não há necessidade de percorrê-lo para ver se, por exemplo, queremos apenas saber se o bloco é passável ou não.

Esse código psudo é mais ou menos como eu me aproximei de um sistema baseado em bloco.

Tile {
  Texture tex;
  //...other static data...
  List currentObjects;
}

Tile t = Tiles[x][y];

Isso pressupõe que há peças estáticas no seu jogo, mas isso é uma suposição muito boa em muitos jogos baseados em peças onde você está lidando com paredes e similares.

Nick Van Brunt
fonte
1

Você disse:

Eu tinha que ter uma matriz 2D de objetos Tile e, em seguida, ter classes herdadas do Tile que representavam os objetos do jogo. Infelizmente, dessa maneira, não pude empilhar mais elementos de jogo no mesmo bloco sem ter uma matriz 3D.

Então fiz algo diferente: eu ainda tinha a matriz 2D de objetos Tile, mas cada objeto Tile continha uma lista na qual eu colocava e entidades diferentes. Isso funcionou bem até 20 minutos atrás, quando percebi que era muito caro fazer muitas coisas

Essa é exatamente a situação que você deseja resolver usando o padrão MVC (model view controller) para separar seu modelo da sua visualização. O MVC cria esse problema em duas partes:

  • Como modelar uma pilha de peças
  • Como exibir uma pilha de peças

Em vez de ter uma matriz 2D de blocos ou uma matriz 2D de uma lista de blocos, seu modelo armazenaria uma matriz 2D de objetos de jogo. Então sua matriz 2D de classes de blocos examinará os objetos do jogo correspondentes e decidirá como renderizá-los.

Em vez de uma instância de Tile única renderizar um único objeto de jogo, uma instância de Tile única renderiza algo que se parece com uma pilha de objetos. Isso seria mais eficiente e permitiria uma separação clara da lógica do código principal e da aparência.

Assim como paredes. A lógica é muito trivial, mas a renderização é mais complexa.

ashes999
fonte
3
Não vejo como o MVC (que não é comum em jogos da minha experiência) ajudaria aqui. Na verdade, isso pioraria: você acabaria com alguma redundância entre o modelo e a exibição que deve ser mantida em sincronia.
munificent
11
Sim, quando você tiver um problema, use <buzzword>. Agora você tem dois problemas.
precisa
11
np, esvaziei mais detalhes.
precisa saber é o seguinte
OK, eu retirei meu voto negativo. Ainda assim, não acho que o MVC seja útil aqui. Na maioria das vezes, o objeto difere pela lógica, não (apenas) pela representação gráfica.
Nevermind
Não posso agradar a todos, eu acho. Para mim, o MVC saltou imediatamente como parte da solução para parte do problema. Mas concordo que não estou fornecendo uma solução completa para todos os problemas - isso exigiria um ensaio!
precisa saber é o seguinte
0

Eu concordo com @Omnion. Use a abordagem de lista, mas mantenha-a classificada se o desempenho for um problema. Ou seja, use uma abordagem híbrida. Use a lista como sua terceira dimensão, mas identifique apenas os 5 primeiros itens como um tipo específico, e qualquer coisa a seguir é um tipo de tinta ou um tipo que não exija verificação várias vezes por quadro.

Nate
fonte
0

Você nunca "deve" criar classes separadas para coisas como peças. Você deve criar uma estrutura a partir de bytes, shorts e ints que se refiram ao tipo de bloco e objetos nele. Os ints de desempenho são os melhores para acompanhar ou até em sistemas de 64 bits. Mas se você quiser salvar seu mapa, deve usar bytes ou curtos sempre que puder, especialmente para mapas grandes.

Nos meus jogos, tenho uma classe de mapa que possui apenas campos como: byte tileType; 256 tipos diferentes de terreno são suficientes para este jogo. Leva 1 byte. ushort tileObject; 65.536 objetos diferentes são suficientes. Leva 2 bytes etc.

Se eu pegar o exemplo acima, cada bloco ocupa apenas 3 bytes de memória. Portanto, um 10000x10000 teria 300 MB de memória e não deveria ser um problema. Sempre que precisar procurar algo, você determina onde no mapa deseja procurar e o que está procurando e itera as matrizes de acordo.

Se você tem coisas dinâmicas em todo o mapa, pergunte a si mesmo se o jogador realmente percebe essas coisas muito longe da janela de exibição.

Madmenyo
fonte