Para cada jogo que eu fiz, acabei colocando todos os meus objetos de jogo (balas, carros, jogadores) em uma única lista de arrays, pela qual percorro para desenhar e atualizar. O código de atualização para cada entidade é armazenado em sua classe.
Fiquei me perguntando, este é o caminho certo para fazer as coisas, ou é um caminho mais rápido e eficiente?
software-engineering
Derek
fonte
fonte
Respostas:
Não existe um caminho certo. O que você está descrevendo funciona e é presumivelmente rápido o suficiente para que isso não importe, pois você parece estar se perguntando porque está preocupado com o design e não porque isso causou problemas de desempenho. Por isso, é um caminho certo, com certeza.
Você certamente poderia fazer isso de maneira diferente, mantendo, por exemplo, uma lista homogênea para cada tipo de objeto ou assegurando que tudo em sua lista heterogênea seja agrupado, de modo a melhorar a coerência do código que está no cache ou permitir atualizações paralelas. É difícil dizer se você verá alguma melhoria significativa no desempenho fazendo isso, sem saber o escopo e a escala dos seus jogos.
Você também pode levar em consideração as responsabilidades de suas entidades - parece que as entidades se atualizam e se processam no momento - para seguir melhor o Princípio de responsabilidade única. Isso pode aumentar a capacidade de manutenção e a flexibilidade de suas interfaces individuais, dissociando-as umas das outras. Mas, novamente, é difícil dizer se é um benefício do trabalho extra que implicaria saber o que sei dos seus jogos (que basicamente não é nada).
Eu recomendaria não se estressar muito com isso e lembre-se de que essa pode ser uma área potencial de melhoria se você notar problemas de desempenho ou de manutenção.
fonte
Como outros já disseram, se é rápido o suficiente, então é rápido o suficiente. Se você está se perguntando se existe uma maneira melhor, a pergunta a ser feita é:
Eu me vejo repetindo todos os objetos, mas operando apenas em um subconjunto deles?
Se o fizer, é uma indicação de que você pode querer ter várias listas contendo apenas referências relevantes. Por exemplo, se você estiver percorrendo todos os objetos do jogo para renderizá-los, mas apenas um subconjunto de objetos precisar ser renderizado, pode valer a pena manter uma lista Renderizável separada apenas para os objetos que precisam ser renderizados.
Isso reduziria o número de objetos pelos quais você percorre e ignora as verificações desnecessárias porque você já sabe que todos os objetos na lista devem ser processados.
Você teria que criar um perfil para ver se está obtendo muito ganho de desempenho.
fonte
Depende quase inteiramente de suas necessidades específicas de jogo . Pode funcionar perfeitamente para um jogo simples, mas falha em um sistema mais complexo. Se estiver funcionando para o seu jogo, não se preocupe ou tente projetar demais até que seja necessário.
Quanto ao motivo pelo qual a abordagem simples pode falhar em algumas situações, é difícil resumir isso em um único post, pois as possibilidades são infinitas e dependem inteiramente dos próprios jogos. Outras respostas mencionaram, por exemplo, que você pode querer agrupar objetos que compartilham responsabilidades em uma lista separada.
Essa é uma das mudanças mais comuns que você pode estar fazendo, dependendo do design do seu jogo. Aproveitarei a oportunidade para descrever alguns outros exemplos (mais complexos), mas lembre-se de que ainda existem muitas outras razões e soluções possíveis.
Para iniciantes, vou apontar que atualizar os objetos do jogo e renderizá-los pode ter requisitos diferentes em alguns jogos e, portanto, precisam ser manipulados separadamente. Alguns possíveis problemas com a atualização e renderização de objetos de jogo:
Atualizando objetos de jogo
Aqui está um trecho da Game Engine Architecture que eu recomendaria ler:
Em outras palavras, em alguns jogos é possível que os objetos dependam um do outro e exijam uma ordem específica de atualização. Nesse cenário, pode ser necessário criar uma estrutura mais complexa do que uma lista para armazenar objetos e suas interdependências.
Renderização de objetos de jogo
Em alguns jogos, cenas e objetos criam uma hierarquia de nós pais-filhos e devem ser renderizados na ordem correta e com transformações em relação aos pais. Nesses casos, você pode precisar de uma estrutura mais complexa, como um gráfico de cena (uma estrutura em árvore), em vez de uma única lista:
Outros motivos podem ser, por exemplo, o uso de uma estrutura de dados de particionamento espacial para organizar seus objetos de uma maneira que melhore a eficiência da execução do descarte de exibição de exibição.
fonte
A resposta é "sim, está tudo bem". Quando trabalhei em grandes simulações de ferro em que o desempenho não era negociável, fiquei surpreso ao ver tudo em uma matriz global grande e gorda (BIG e FAT). Mais tarde, trabalhei em um sistema OO e pude comparar os dois. Embora tenha sido super feia, a versão "uma matriz para governar todos eles" no C simples simples de thread único compilado em depuração foi várias vezes mais rápida que o sistema OO multithread "bonito" compilado -O2 em C ++. Eu acho que um pouco disso tinha a ver com a excelente localidade do cache (no espaço e no tempo) na versão big-fat-array.
Se você quer desempenho, uma grande matriz C global será o limite superior (por experiência).
fonte
Basicamente, o que você quer fazer é criar listas conforme necessário. Se você redesenhar objetos do terreno de uma só vez, uma lista deles poderá ser útil. Mas, para que isso valha a pena, você precisaria de dezenas de milhares deles e de 10.000 coisas que não são terreno. Caso contrário, ler atentamente sua lista de tudo e usar um "se" para retirar objetos do terreno é rápido o suficiente.
Eu sempre precisei de uma lista principal, independentemente de quantas outras listas eu já tenha. Geralmente, faço de um tipo de mapa, tabela com chave ou dicionário, para que eu possa acessar itens individuais aleatoriamente. Mas uma lista simples é muito mais simples; se você não acessar objetos do jogo aleatoriamente, não precisará do mapa. E a pesquisa seqüencial ocasional por alguns milhares de entradas para encontrar a correta não levará muito tempo. Então, eu diria que, o que quer que você faça, planeje manter a lista principal. Considere usar um mapa se ele ficar grande. (Embora se você pode usar índices em vez de chaves, pode ficar com matrizes e ter algo muito melhor que um mapa. Eu sempre acabava com grandes lacunas entre os números de índice, então tive que desistir.)
Uma palavra sobre matrizes: elas são incrivelmente rápidas. Mas, quando eles capturam cerca de 100.000 elementos, começam a dar encaixes ao Garbage Collectors. Se você pode alocar o espaço uma vez e não tocá-lo depois, tudo bem. Mas se você está continuamente expandindo a matriz, está continuamente alocando e liberando grandes blocos de memória e está apto a travar o jogo por segundos por vez e até falhar com um erro de memória.
Tão rápido e mais eficiente? Certo. Um mapa para acesso aleatório. Para listas realmente grandes, uma Lista vinculada vencerá uma matriz se e quando você não precisar de acesso aleatório. Vários mapas / listas para organizar os objetos de várias maneiras, conforme necessário. Você pode multiencadear a atualização.
Mas é tudo muito trabalho. Essa matriz única é rápida e simples. Você pode adicionar outras listas e outras coisas somente quando necessário, e provavelmente não precisará delas. Esteja ciente do que pode dar errado se o seu jogo ficar grande. Você pode querer ter uma versão de teste com 10 vezes mais objetos do que na versão real para ter uma idéia de quando você está enfrentando problemas. (Faça isso apenas se o seu jogo estiver ficando grande.) (E não tente o multi-threading sem pensar muito. Todo o resto é fácil de começar e você pode fazer o quanto quiser e recue a qualquer momento. Com o multi-threading, você pagará um preço alto para entrar, as taxas de permanência são altas e é difícil voltar.)
Em outras palavras, continue fazendo o que está fazendo. Seu maior limite é provavelmente o seu próprio tempo, e você está fazendo o melhor uso possível.
fonte
Eu usei um método um pouco semelhante para jogos simples. Armazenar tudo em uma matriz é muito útil quando as seguintes condições forem verdadeiras:
De qualquer forma, implementei essa solução como uma matriz de tamanho fixo que contém uma
gameObject_ptr
estrutura. Essa estrutura continha um ponteiro para o próprio objeto do jogo e um valor booleano representando se o objeto estava ou não "vivo".O objetivo do booleano é acelerar as operações de criação e exclusão. Em vez de destruir os objetos do jogo quando eles são removidos da simulação, eu simplesmente configuraria o sinalizador "vivo" para
false
.Ao executar cálculos em objetos, o código percorrerá a matriz, executando cálculos em objetos marcados como "vivos" e somente objetos marcados como "vivos" serão renderizados.
Outra otimização simples é organizar a matriz por tipo de objeto. Todos os marcadores, por exemplo, poderiam viver nas últimas n células da matriz. Em seguida, armazenando um int com o valor do índice ou um ponteiro para o elemento da matriz, tipos específicos podem ser referenciados a um custo reduzido.
Não é necessário dizer que esta solução não é boa para jogos com grandes quantidades de dados, pois a matriz será inaceitavelmente grande. Também não é adequado para jogos com restrições de memória que proíbem que objetos não utilizados ocupem memória. A conclusão é que, se você tiver um limite superior conhecido do número de objetos do jogo que terá em um determinado momento, não há nada de errado em armazená-los em uma matriz estática.
Este é o meu primeiro post! Espero que responda sua pergunta!
fonte
É aceitável, embora incomum. Termos como "mais rápido" e "mais eficiente" são relativos; normalmente você organizaria os objetos do jogo em categorias separadas (uma lista de marcadores, uma lista de inimigos etc.) para tornar a programação mais organizada (e, portanto, mais eficiente), mas não é como se o computador passasse por todos os objetos mais rapidamente .
fonte
Você diz matriz e objetos únicos.
Então (sem o seu código para analisar), acho que todos estão relacionados ao 'uberGameObject'.
Então, talvez dois problemas.
1) Você pode ter um objeto "deus" - faz toneladas de coisas, não realmente segmentadas pelo que faz ou é.
2) Você percorre esta lista e transmite para digitar? ou desembalar cada. Isso tem uma grande penalidade de desempenho.
considere dividir tipos diferentes (marcadores, bandidos etc.) em suas próprias listas fortemente tipadas.
fonte
update()
).Eu posso propor um compromisso entre lista única geral e listas separadas para cada tipo de objeto.
Mantenha a referência a um objeto em várias listas. Essas listas são definidas pelos comportamentos base. Por exemplo,
MarineSoldier
objecto será referenciado emPhysicalObjList
,GraphicalObjList
,UserControlObjList
,HumanObjList
. Então, quando você processa a física, iteraPhysicalObjList
, quando processa os danos causados pelo veneno -HumanObjList
se o Soldier morre - remova-o,HumanObjList
mas mantenha-oPhysicalObjList
. Para que esse mecanismo funcione, você precisa organizar a inserção / remoção automática de objetos quando ele é criado / excluído.Vantagens da abordagem:
AllObjectsList
com conversão na atualização)MegaAirHumanUnderwaterObject
seriam inseridos nas listas correspondentes em vez de criar uma nova lista para seu tipo)PS: Quanto à atualização automática, você encontrará problemas quando vários objetos estiverem interagindo e cada um deles depende de outros. Para resolver isso, precisamos afetar o objeto externamente, não dentro da auto-atualização.
fonte