Ao carregar mapas enormes adicionais, o método de carregamento lança uma exceção de falta de memória onde uma nova instância do bloco de mapas é criada. Eu gostaria de ter o mapa inteiro processado pelo menos no aplicativo do servidor (e no cliente, se possível). Como devo resolver este problema?
UPD: a questão aqui é como fazer com que o jogo pare de funcionar quando ainda houver memória livre para uso. Quanto à divisão do mapa em partes, é uma boa abordagem, mas não o que eu quero no meu caso.
UPD2: No começo, designei uma textura para cada nova instância da classe de blocos e foi isso que consumiu muita memória (também carregando tempo). Agora, leva cerca de quatro vezes menos espaço. Obrigado a todos, agora eu posso rodar mapas enormes sem pensar em dividi-los em pedaços ainda.
UPD3: Depois de reescrever o código para ver se as matrizes de propriedades do bloco funcionam mais rapidamente ou consomem menos memória (do que as referidas propriedades em seus respectivos blocos como propriedades do objeto), descobri que não apenas levei muito tempo para tentar isso, não trouxe nenhuma melhoria de desempenho e tornou o jogo muito difícil de depurar.
fonte
Respostas:
Isso sugere fortemente que você não está representando seus blocos corretamente, pois isso significa que cada bloco tem aproximadamente 80 bytes de tamanho.
O que você precisa entender é que é preciso haver uma separação entre o conceito de jogo de um bloco e o bloco visual que o usuário vê. Esses dois conceitos não são a mesma coisa .
Veja Terraria, por exemplo. O menor mundo de Terraria ocupa 4200x1200 peças, ou seja, 5 milhões de peças. Agora, quanta memória é necessária para representar esse mundo?
Bem, cada ladrilho tem uma camada de primeiro plano, uma camada de fundo (as paredes do fundo), uma "camada de arame" para onde os fios vão e uma "camada de móveis" para onde os itens de móveis vão. Quanta memória cada bloco ocupa? Mais uma vez, estamos apenas falando conceitualmente, não visualmente.
Um bloco de primeiro plano pode ser facilmente armazenado em um curto não assinado. Não existem mais de 65536 tipos de blocos em primeiro plano, portanto, não faz sentido usar mais memória que isso. Os blocos de plano de fundo podem facilmente estar em um byte não assinado, pois existem menos de 256 tipos diferentes de blocos de plano de fundo. A camada de arame é puramente binária: ou um ladrilho tem um arame ou não. Então isso é um bit por bloco. E a camada de móveis poderia novamente ser um byte não assinado, dependendo de quantas peças de móveis diferentes fossem possíveis.
Tamanho total da memória por bloco: 2 bytes + 1 byte + 1 bit + 1 byte: 4 bytes + 1 bit. Portanto, o tamanho total de um pequeno mapa de Terraria é 20790000 bytes, ou ~ 20 MB. (nota: esses cálculos são baseados no Terraria 1.1. O jogo se expandiu muito desde então, mas mesmo o Terraria moderno pode caber dentro de 8 bytes por local de bloco, ou ~ 40MB. Ainda é bastante tolerável).
Você nunca deve ter essa representação armazenada como matrizes de classes C #. Eles devem ser matrizes de números inteiros ou algo semelhante. A estrutura do AC # também funcionaria.
Agora, quando chega a hora de desenhar parte de um mapa (observe a ênfase), o Terraria precisa converter esses blocos conceituais em blocos reais . Cada ladrilho precisa realmente escolher uma imagem de primeiro plano, imagem de fundo, uma imagem de mobília opcional e ter uma imagem de arame. É aqui que o XNA entra com suas várias folhas de sprite e tal.
O que você precisa fazer é converter a parte visível do seu mapa conceitual em blocos de folhas de sprite XNA reais. Você não deve tentar converter a coisa toda de uma só vez . Cada bloco que você armazena deve ser apenas um índice dizendo "Eu sou o tipo X do bloco", em que X é um número inteiro. Você usa esse índice inteiro para buscar qual sprite você usa para exibi-lo. E você usa as folhas de sprite do XNA para tornar isso mais rápido do que apenas desenhar quads individuais.
Agora, a região visível dos ladrilhos precisa ser dividida em vários pedaços, para que você não construa constantemente folhas de sprite sempre que a câmera se mover. Portanto, você pode ter pedaços de 64x64 do mundo como folhas de sprite. Quaisquer pedaços de 64x64 do mundo que são visíveis na posição atual da câmera do jogador são aqueles que você desenha. Quaisquer outros pedaços nem sequer têm folhas de sprite; se um pedaço cair da tela, você joga fora essa folha (nota: você realmente não o exclui; você o mantém por perto e o resecifica para um novo pedaço que pode se tornar visível mais tarde).
Seu servidor não precisa conhecer ou se preocupar com a representação visual de blocos. Tudo o que precisa é se preocupar com a representação conceitual. O usuário adiciona um bloco aqui, por isso altera esse índice.
fonte
Divida o terreno em regiões ou pedaços. Depois, carregue apenas os pedaços visíveis para os jogadores e descarregue os que não são. Você pode pensar nisso como uma correia transportadora, onde você está carregando pedaços em uma extremidade e descarregando-os na outra enquanto o jogador se move. Sempre ficando à frente do jogador.
Você também pode usar truques como instanciamento. Onde se todos os blocos de um tipo têm apenas uma instância na memória e são desenhados várias vezes em todos os locais onde são necessários.
Você pode encontrar mais idéias como esta aqui:
Como eles fizeram isso: milhões de peças em Terraria
'Zoneamento' de áreas em um grande mapa de peças, bem como masmorras
fonte
A resposta do Byte56 é boa, e comecei a escrever isso como um comentário, mas demorou muito e contém algumas dicas que podem ser mais úteis em uma resposta.
A abordagem é absolutamente tão boa para o servidor quanto para um cliente. Na verdade, é provavelmente mais apropriado, já que o servidor será solicitado a se preocupar com muito mais áreas do que o cliente. O servidor tem uma idéia diferente sobre o que precisa ser carregado do que um cliente (se preocupa com todas as regiões com as quais todos os clientes conectados se importam), mas é igualmente capaz de gerenciar um conjunto de regiões de trabalho.
Mesmo que você possa colocar um mapa tão gigantesco (e provavelmente não pode) na memória, não quer . Não há absolutamente nenhum ponto em ter dados carregados que você não precisa usar imediatamente, é ineficiente e lento. Mesmo se você pudesse ajustá-lo na memória, provavelmente não seria capaz de processar tudo isso em qualquer período de tempo sensato.
Suspeito que sua objeção a descarregar determinadas regiões seja porque sua atualização mundial está iterando todos os blocos e processando-os? Certamente isso é válido, mas não impede que apenas certas peças sejam carregadas. Em vez disso, quando uma região for carregada, aplique as atualizações perdidas, para atualizá-la. Isso também é muito mais eficiente em termos de processamento (concentrando um grande esforço em uma pequena área de memória). Se houver algum efeito de processamento que possa cruzar os limites da região (por exemplo, um fluxo de lava ou um fluxo de água que transitará para outra região), vincule essas duas regiões para que, quando uma for carregada, a outra também. Idealmente, essas situações seriam minimizadas; caso contrário, você voltará rapidamente ao caso 'todas as regiões devem ser carregadas o tempo todo'.
fonte
Uma maneira eficiente que usei em um jogo XNA foi:
Você também pode usar um único mapa grande dessa maneira, se não for tão grande e usar a lógica apresentada.
É claro que o tamanho das suas texturas deve ser equilibrado e a resolução paga um ótimo preço nisso. Tente trabalhar em uma resolução mais baixa e, se necessário, faça um pacote de alta resolução para carregar se uma configuração estiver definida como.
Você terá que repetir apenas as partes relevantes deste mapa a cada vez e renderizar apenas o necessário. Dessa forma, você melhorará muito o desempenho.
Um servidor pode processar um mapa enorme mais rapidamente porque não precisa renderizar (uma das operações mais lentas) o jogo; portanto, a lógica para isso pode ser o uso de pedaços maiores ou até mesmo o mapa inteiro, mas processa apenas a lógica em torno de jogadores, como em uma área de exibição de área 2 vezes maior que o jogador.
Um conselho aqui é que eu não sou especialista em desenvolvimento de jogos e meu jogo foi feito para o projeto final da minha graduação (que eu tive a melhor pontuação), então talvez eu não esteja tão certo em todos os pontos, mas eu tenho pesquisei na web e sites como http://www.gamasutra.com e o site de criadores de XNA creators.xna.com (no momento http://create.msdn.com ) para reunir conhecimentos e habilidades e funcionou bem para mim .
fonte
Ou
fonte
texture2d
? Cada bloco possui um único ou eles compartilham texturas? Além disso, onde você disse isso? Você certamente não colocou na sua pergunta, que é onde as informações devem estar. Explique melhor suas circunstâncias específicas, por favor.Em resposta à sua atualização, você não pode alocar matrizes
Int32.MaxValue
em tamanho maior. Você tem que dividi-lo em pedaços. Você pode até encapsulá-lo em uma classe de invólucro que expõe uma fachada semelhante a uma matriz:fonte