Design de mapa inteiro vs. design de matriz de peças


10

Estou trabalhando em um RPG 2D, que apresentará os mapas habituais de masmorras / cidades (pré-gerados).

Estou usando peças, que depois combinarei para fazer os mapas. Meu plano original era montar os blocos usando o Photoshop, ou algum outro programa gráfico, para ter uma imagem maior que eu pudesse usar como mapa.

No entanto, eu li em vários lugares pessoas falando sobre como eles usaram matrizes para construir seu mapa no mecanismo (para que você forneça uma matriz de x blocos ao seu mecanismo e os monte como um mapa). Eu posso entender como isso é feito, mas parece muito mais complicado de implementar, e não vejo vantagens óbvias.

Qual é o método mais comum e quais são as vantagens / desvantagens de cada um?


1
O principal problema é a memória. Uma textura que preenche uma tela 1080p uma vez tem cerca de 60mb. Portanto, uma imagem usa um int por pixel, um bloco usa um int por (pixel / (tilesize * tilesize)). Portanto, se os blocos forem 32,32, você poderá representar um mapa 1024 vezes maior usando blocos versus um pixel. Também vezes textura trocando vai fazer mal empurrando essa quantidade de memória em torno etc ...
ClassicThunder

Respostas:


18

Primeiro, deixe-me dizer que os RPGs 2D estão próximos e queridos pelo meu coração, e trabalhar com os antigos motores DX7 VB6 MORPG (não ria, faz 8 anos, agora :-)) foi o que me interessou pelo desenvolvimento de jogos . Mais recentemente, comecei a converter um jogo em que trabalhei em um desses mecanismos para usar o XNA.

Dito isso, minha recomendação é que você use uma estrutura baseada em blocos com camadas para o seu mapa. Com qualquer API gráfica usada, você terá um limite no tamanho das texturas que pode carregar. Sem mencionar os limites de memória de textura da placa de vídeo. Portanto, considerando isso, se você deseja maximizar o tamanho de seus mapas, além de minimizar a quantidade e o tamanho de texturas carregadas na memória, mas também diminuir o tamanho de seus ativos no disco rígido do usuário E os tempos de carregamento, definitivamente vai querer ir com azulejos.

Quanto à implementação, eu entrei em detalhes sobre como eu lidei com isso em algumas perguntas aqui no GameDev.SE e no meu blog (ambos os links abaixo), e não é exatamente isso que você está perguntando, então eu vou vá para o básico aqui. Também anotarei os recursos dos ladrilhos que os tornam benéficos ao carregar várias imagens pré-renderizadas grandes. Se algo não estiver claro, me avise.

  1. A primeira coisa que você precisa fazer é criar uma planilha. Esta é apenas uma imagem grande que contém todos os seus blocos alinhados em uma grade. Esse (e talvez um extra, dependendo do número de blocos) será a única coisa que você precisará carregar. Apenas 1 imagem! Você pode carregar 1 por mapa ou um com cada peça do jogo; qualquer organização que funcione para você.
  2. Em seguida, você precisa entender como pode pegar essa "planilha" e converter cada bloco em um número. Isso é bem direto com algumas contas simples. Observe que a divisão aqui é uma divisão inteira , portanto, as casas decimais são descartadas (ou arredondadas, se você preferir). Como converter células em coordenadas e vice-versa.
  3. OK, agora que você dividiu a planilha em uma série de células (números), você pode pegar esses números e conectá-los ao contêiner que desejar. Por uma questão de simplicidade, você pode simplesmente usar uma matriz 2D.

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. Em seguida, você deseja desenhá-los. Uma das maneiras pelas quais você pode tornar isso MUITO mais eficiente (dependendo do tamanho do mapa) é calcular apenas as células que a câmera está visualizando no momento e passar por elas. Você pode fazer isso buscando as coordenadas da matriz do bloco de mapa dos cantos superior esquerdo ( tl) e inferior direito ( br) da câmera . Em seguida, faça um loop de tl.X to br.Xe, em um loop aninhado, de tl.Y to br.Ypara desenhá-los. Exemplo de código abaixo:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
  5. Jackpot! Esse é o básico do mecanismo de blocos. Você pode ver que é fácil ter até um mapa de 1000 x 1000 com pouco custo adicional. Além disso, se você tiver menos de 255 blocos, poderá usar uma matriz de bytes cortando a memória em 3 bytes por célula. Se um byte for muito pequeno, um ushort provavelmente será suficiente para suas necessidades.

Nota: Eu deixei de fora o conceito de coordenadas mundiais (que é a posição da sua câmera), pois acho que está fora do escopo desta resposta. Você pode ler sobre isso aqui no GameDev.SE.

Meus recursos de mecanismo de bloco
Observação: todos esses são direcionados para o XNA, mas praticamente se aplica a qualquer coisa - você só precisa alterar as chamadas de empate.

  • Minha resposta a esta pergunta descreve como eu manejo as células do mapa e as camadas no meu jogo. (Veja o terceiro link.)
  • Minha resposta a esta pergunta explica como eu armazeno os dados em um formato binário.
  • Este é o primeiro (bem, tecnicamente o segundo, mas o primeiro "técnico") post sobre o desenvolvimento do jogo no qual eu estava trabalhando. A série inteira contém informações sobre coisas como pixel shaders, iluminação, comportamento de blocos, movimento e todas essas coisas divertidas. Na verdade, atualizei a postagem para incluir o conteúdo da minha resposta no primeiro link que publiquei acima; portanto, você pode lê-la (talvez eu tenha adicionado coisas). Se você tiver alguma dúvida, deixe um comentário aqui ou aqui e ficarei feliz em ajudar.

Outros recursos do mecanismo de bloco

  • O tutorial do mecanismo de blocos deste site me deu a base que usei para criar meus mapas.
  • Ainda não assisti esses tutoriais em vídeo porque ainda não tive tempo, mas provavelmente são úteis. :) Eles podem estar desatualizados, se você estiver usando XNA.
  • Este site tem mais alguns tutoriais que (eu acho) são baseados nos vídeos acima. Pode valer a pena conferir.

Waow, que é duas vezes mais informações do que eu pedi, e praticamente responde a todas as perguntas que eu não pedi, ótimo, obrigado :)
Cristol.GdM

@Mikalichov - Ainda bem que pude ajudar! :)
Richard Marskell - Drackir 29/03

Normalmente, com uma matriz 2D, você deseja usar a primeira dimensão como seu Y e a próxima como seu X. Na memória, uma matriz será seqüencialmente 0,0-i e 1,0-i. Se você aninhar loops com a primeira galinha do X, na verdade está pulando para frente e para trás na memória, em vez de seguir um caminho seqüencial de leitura. Sempre para (y) e depois para (x).
Brett W

@BrettW - Um ponto válido. Obrigado. Isso me fez pensar em como as matrizes 2D são armazenadas em .Net (ordem maior da linha. Aprendi algo novo hoje! :-)). No entanto, depois de atualizar meu código, percebi que é exatamente o mesmo que você está descrevendo, apenas com as variáveis ​​alteradas. A diferença está em que ordem as peças são desenhadas. Meu código de exemplo atual desenha de cima para baixo, da esquerda para a direita, enquanto o que você descreve desenha da esquerda para a direita, de cima para baixo. Então, por uma questão de simplicidade, decidi voltar ao original. :-)
Richard Marskell - Drackir 29/03

6

Os sistemas baseados em ladrilhos e um modelo estático / sistema de textura podem ser usados ​​para representar um mundo e cada um tem diferentes pontos fortes. Se um é melhor que o outro, tudo se resume a como você usa as peças e o que funciona melhor para suas habilidades e necessidades.

Dito isto, ambos os sistemas podem ser usados ​​separadamente ou também juntos.

Um sistema padrão baseado em blocos possui os seguintes pontos fortes:

  • Matriz 2D simples de blocos
  • Cada peça possui um único material
    • Esse material pode ser uma textura única, múltipla ou mesclada dos ladrilhos circundantes
    • Pode reutilizar texturas para todos os blocos desse tipo, reduzindo a criação e o retrabalho de ativos
  • Cada bloco pode ser aceitável ou não (detecção de colisão)
  • Fácil de renderizar e identificar partes do mapa
    • A posição dos caracteres e a posição do mapa são coordenadas absolutas
    • Simples de percorrer blocos relevantes para a região da tela

A desvantagem dos blocos é que você precisa criar um sistema para construir esses dados baseados em blocos. Você pode criar uma imagem que use cada pixel como um bloco, criando assim sua matriz 2D a partir de uma textura. Você também pode criar um formato proprietário. Usando bitflags, você pode armazenar um grande número de dados por bloco em um espaço bastante pequeno.

A principal razão pela qual a maioria das pessoas faz blocos é porque isso lhes permite criar pequenos ativos e reutilizá-los. Isso permite que você crie uma imagem muito maior com pedaços menores. Reduz o retrabalho porque você não está mudando o mapa do mundo inteiro para fazer uma pequena alteração. Por exemplo, se você quiser mudar a sombra de toda a grama. Em uma imagem grande, você teria que repintar toda a grama. Em um sistema de ladrilhos, você apenas atualiza os ladrilhos de grama.

No geral, você provavelmente fará muito menos retrabalho com um sistema baseado em blocos do que um grande mapa gráfico. Você pode acabar usando um sistema baseado em blocos para colisão, mesmo que não o use no seu mapa de chão. Só porque você usa um bloco internamente não significa que você não pode usar modelos para representar os objetos do ambiente que podem usar mais de um bloco para seu espaço.

Você precisaria fornecer mais detalhes sobre seu ambiente / mecanismo / idioma para fornecer exemplos de código.


Obrigado! Estou trabalhando com C # e pretendo uma versão semelhante (mas menos talentosa) do Final Fantasy 6 (ou basicamente a maioria dos Square SNES RPG), portanto, ladrilhos e estruturas (ou seja, casas). Minha maior preocupação é que não vejo como construir a matriz 2D sem passar horas verificando "há um canto de grama lá, três horizontais retas, depois um canto de casa, então ...", com muitos erros possíveis ao longo do maneira.
Cristol.GdM

Parece que Richard o cobriu em termos de um código específico. Pessoalmente, eu usaria uma enumeração de tipos de til e de bitflags para identificar o valor do bloco. Isso permite armazenar mais de 32 valores em um número inteiro médio. Você também poderá armazenar vários sinalizadores por bloco. Então você pode dizer Grass = true, Wall = true, Collision = true, etc. sem variáveis ​​separadas. Em seguida, basta atribuir "temas", que determinam a planilha para os gráficos dessa região do mapa em particular.
Brett W

3

Desenhando o mapa: os mosaicos são fáceis no mecanismo, pois o mapa pode ser representado como uma enorme variedade de índices de mosaicos. O código do Draw é apenas um loop aninhado:

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

Para mapas grandes, uma imagem de bitmap enorme para o mapa inteiro pode ocupar muito espaço na memória e pode ser maior do que o que uma placa gráfica suporta para um único tamanho de imagem. Mas você não terá problemas com blocos porque aloca memória gráfica apenas uma vez para cada bloco (ou um total, de maneira ideal, se estiverem todos em uma folha)

Também é fácil reutilizar as informações do bloco, por exemplo, colisões. por exemplo, para obter o bloco de terreno no canto superior esquerdo do sprite do personagem:

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

Suponha que você precise verificar colisões com pedras / árvores, etc. Você pode colocar o ladrilho na posição de (posição do personagem + tamanho do sprite do personagem + direção atual) e verificar se está marcado como passável ou não.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.