Pergunta

Estou desenvolvendo um clone jogo de estratégia em tempo real na plataforma Java e tenho algumas questões conceituais sobre onde colocar e como gerenciar o estado do jogo. Os usos jogo Swing / Java2D como renderização. Na actual fase de desenvolvimento, nenhuma simulação e não AI está presente e somente o usuário é capaz de alterar o estado do jogo (por exemplo, construir / demolir um edifício linhas de produção, Add-Remove, montar frotas e equipamentos). Portanto, a manipulação estado do jogo pode ser realizado no segmento de eventos expedição sem qualquer consulta renderização. O estado do jogo também é usado para exibir várias informações agregadas para o usuário.

No entanto, como eu preciso introduzir simulação (por exemplo, a construção de progresso, mudanças da população, os movimentos da frota, fabricação de processo, etc.), mudando o estado do jogo em um temporizador e EDT irá certamente abrandar a prestação.

Vamos dizer que a operação de simulação / AI é realizada em cada 500ms e eu uso SwingWorker para o cálculo de cerca de 250ms de comprimento. Como posso garantir, que não há nenhuma condição de corrida sobre o estado do jogo lê entre a simulação ea possível interação do usuário?

Eu sei que o resultado da simulação (que é pequena quantidade de dados) podem ser eficientemente voltou para o EDT via a chamada SwingUtilities.invokeLater ().

O modelo de estado do jogo parece ser muito complexo para ser inviável para apenas usando classes de valor imutáveis ??em todos os lugares.

Existe uma abordagem relativamente correta para eliminar esta condição de corrida leitura? Talvez fazer uma clonagem estado jogo completo / parcial em cada escala de temporizador ou alterar o espaço de vida do estado do jogo de EDT em algum outro segmento?

Update: (a partir dos comentários que eu dei) O jogo opera com 13 jogadores controlados pela IA, 1 jogador humano e tem cerca de 10.000 objetos do jogo (planetas, edifícios, equipamentos, pesquisa, etc.). Um objeto de jogo, por exemplo, tem os seguintes atributos:

World (Planets, Players, Fleets, ...)
Planet (location, owner, population, type, 
    map, buildings, taxation, allocation, ...)
Building (location, enabled, energy, worker, health, ...)

Em um cenário, o usuário constrói um novo edifício para este planeta. Este é realizada em EDT como as necessidades mapa e edifícios de coleta de ser mudado. Paralelo a isso, uma simulação é executada em cada 500ms para calcular a alocação de energia para os edifícios em todos os planetas do jogo, o que necessita para percorrer a coleção edifícios para as estatísticas coleta. Se a alocação é calculado, ele é enviado para o EDT e campo de energia de cada edifício é atribuído.

interações jogador só humanos têm essa propriedade, porque os resultados da computação AI são aplicados às estruturas em EDT de qualquer maneira.

Em geral, 75% dos atributos de objeto são estáticos e utilizados apenas para renderização. O resto é mutável, quer através de interação com o usuário ou simulação decisão / AI. Ele também é assegurada, que nenhuma nova simulação / step AI é iniciado até que a anterior escreveu de volta todas as alterações.

Meus objetivos são:

  • Evite atrasar a interação do usuário, por exemplo, usuário coloca o edifício para o planeta e só depois 0.5s recebe o feedback visual
  • Evite bloquear a EDT com computação, espera de bloqueio, etc.
  • questões Evite simultaneidade com passagem de recolha e modificações, alterações de atributo

Opções:

  • Fine grained objeto bloqueio
  • coleções imutáveis ??
  • campos voláteis
  • Snapshot parcial

Todos estes têm vantagens, desvantagens e as causas para o modelo e o jogo.

Update 2: Eu estou falando sobre este jogo . Meu clone é aqui . As capturas de tela pode ajudar a imaginar as interações de renderização e modelo de dados.

Update 3:

Vou tentar dar um exemplo de código pequeno para esclarecer o meu problema como parece a partir dos comentários que é mal compreendido:

List<GameObject> largeListOfGameObjects = ...
List<Building> preFilteredListOfBuildings = ...
// In EDT
public void onAddBuildingClicked() {
    Building b = new Building(100 /* kW */);
    largeListOfGameObjects.add(b);
    preFilteredListOfBuildings.add(b);
}
// In EDT
public void paint(Graphics g) {
    int y = 0;
    for (Building b : preFilteredListOfBuildings) {
        g.drawString(Integer.toString(b.powerAssigned), 0, y);
        y += 20;
    }
}
// In EDT
public void assignPowerTo(Building b, int amount) {
    b.powerAssigned = amount;
}
// In simulation thread
public void distributePower() {
    int sum = 0;
    for (Building b : preFilteredListOfBuildings) {
        sum += b.powerRequired;
    }
    final int alloc = sum / (preFilteredListOfBuildings.size() + 1);
    for (final Building b : preFilteredListOfBuildings) {
        SwingUtilities.invokeLater(=> assignPowerTo(b, alloc));            
    }
}

Assim, a sobreposição é entre o onAddBuildingClicked () e distributePower (). Agora imagine o caso onde você tem 50 destes tipo de sobreposições entre as várias partes do modelo de jogo.

Foi útil?

Solução

Isso soa como ele poderia se beneficiar de uma abordagem cliente / servidor:

O jogador é um cliente - interatividade e renderização acontecer nesse fim. Assim, o jogador pressiona um botão, o pedido vai para o servidor. A resposta do servidor volta, e o estado do jogador é atualizado. Em qualquer ponto entre essas coisas que acontecem, a tela pode ser pintado-re, e reflete o estado do jogo como o cliente atualmente sabe-o.

A AI também é um cliente. - É o equivalente a um bot

A simulação é o servidor. Ela recebe atualizações de seus clientes em vários momentos e atualiza o estado do mundo, em seguida, envia essas atualizações para todos, conforme apropriado. Aqui é onde ela se liga com a sua situação: A simulação / AI exige um mundo estático, e muitas coisas estão acontecendo ao mesmo tempo. O servidor pode simplesmente fila solicitações de mudança e aplicá-los antes de enviar as atualizações de volta para o cliente (s). Assim, tanto quanto o servidor está em causa, o mundo do jogo não está realmente mudando em tempo real, está mudando sempre que o danado servidor bem decide que é.

Finalmente, no lado do cliente, você pode evitar o atraso entre pressionar o botão e ver o resultado, fazendo alguns cálculos aproximados rápidas e exibindo um resultado (por isso a necessidade imediata é atingida) e, em seguida, exibindo o resultado mais correto quando o servidor fica em torno de falar com você.

Note que isso não realmente tem que ser implementado em uma rede TCP / IP over-the-internet tipo de forma, assim que ele ajuda a pensar nisso nesses termos.

Como alternativa, você pode colocar a responsabilidade de manter a coerência de dados durante a simulação em um banco de dados, uma vez que já está construído com bloqueio e coerência em mente. Algo como sqlite poderia trabalhar como parte de uma solução sem rede.

Outras dicas

Não tenho certeza eu entendo totalmente o comportamento que você está procurando, mas parece que você precisa de algo como um estado do segmento mudança / fila para todas as mudanças de estado são manipulados por um único segmento.

Criar uma API, talvez como SwingUtilities.invokeLater () e / ou SwingUtilities.invokeAndWait () para a fila de mudança de estado para lidar com seus pedidos de mudança de estado.

Como é que isso se reflete no gui eu acho que depende do comportamento que você está procurando. ou seja, não pode retirar o dinheiro porque o estado atual é de R $ 0, ou pop volta para o usuário que a conta estava vazia quando o pedido de retirada foi processado. (Provavelmente não com essa terminologia ;-))

A abordagem mais fácil é fazer a simulação rápido o suficiente para executar no EDT. Prefere programas que trabalho!

Para o modelo de dois fios, o que eu sugiro é sincronizar o modelo de domínio com um modelo de renderização. O modelo tornar devem manter os dados sobre o que veio do modelo de domínio.

Para uma atualização: No bloqueio de segmento de simulação do modelo render. Atravessar a atualização modelo onde as coisas são diferentes do que se espera atualizar o modelo tornar render. Ao atravessar acabado, desbloquear o modelo render e marcar uma repintura. Note-se que nesta abordagem, você não precisa de um bazillion ouvintes.

O modelo renderizar pode ter diferentes profundidades. Em um extremo que poderia ser uma imagem e a operação de actualização é apenas para substituir uma única referência com o novo objeto de imagem (isso não vai pega, por exemplo, o redimensionamento ou outra interação superficial muito bem). Você pode não se preocupar verificar se um item tem mudança e apenas eveything atualização.

Se a alteração do estado do jogo é rápido (uma vez que você sabe o que mudá-lo para) você pode tratar o estado do jogo como outros modelos do balanço e única mudança ou visualizar o estado no EDT. Se mudar o estado do jogo não é rápido, então você pode mudança de estado de sincronização e fazê-lo no trabalhador swing / temporizador (mas não o EDT) ou você pode fazê-lo em segmento separado que você trata de forma semelhante ao EDT (em que ponto você olhar através de um BlockingQueue para lidar com solicitações de mudança). O último é mais útil se a interface do usuário nunca tem que recuperar as informações do estado do jogo mas em vez disso tem a prestação muda enviada via ouvintes ou observadores.

É possível atualizar de forma incremental o estado do jogo e ainda ter um modelo que é consistente? Por exemplo recalcular para um subconjunto de planeta / jogador / objetos de frota entre rende / atualizações do usuário.

Se assim for, você pode executar atualizações incrementais no EDT que apenas calcular uma pequena parte do estado antes de permitir que o EDT às entradas do usuário de processo e render.

Após cada atualização incremental na EDT você precisa lembrar o quanto dos restos modelo a ser atualizado e marcar uma nova SwingWorker no EDT para continuar este processamento após quaisquer entradas do usuário pendentes e prestação foi executada.

Isso deve permitir que você para evitar a cópia ou bloqueio do modelo de jogo, ainda mantendo as interações do usuário responsivo.

Eu acho que você não deve ter Mundo armazenar quaisquer alterações de dados ou fazer a qualquer objetos em si, ele só deve ser usado para manter uma referência a um objeto e quando que as necessidades de objeto para ser mudado, ter o jogador fazer a mudança a mudança -lo diretamente. Neste caso, a única coisa que você precisa fazer é sincronizar cada objeto no mundo do jogo para que quando um jogador está a fazer uma mudança, nenhum outro jogador pode fazê-lo. Aqui está um exemplo do que eu estou pensando:

Um jogador precisa de saber sobre um planeta, por isso pede Mundial para que o planeta (como é dependente de sua implementação). Mundial retorna uma referência para o objeto planeta Jogador A pedido. O jogador A decide fazer uma mudança, então ele faz isso. Vamos dizer que ele adiciona um edifício. O método para adicionar um edifício para o Planeta é sincronizado de modo que apenas um jogador pode fazê-lo de uma vez. O edifício vai manter o controle de seu próprio tempo de construção (se houver), de modo método de construção add do planeta seriam libertados quase imediatamente. Desta forma, vários jogadores podem pedir informações no mesmo planeta, ao mesmo tempo, sem afetar o outro e os jogadores podem adicionar edifícios quase simultaneamente sem muita aparência de lag. Se dois jogadores estão à procura de um lugar para colocar o prédio (se isso é parte do seu jogo), em seguida, verificar a adequação de um local será uma consulta não uma mudança.

Me desculpe se isso não resposta que você está pergunta, eu não tenho certeza se eu entendi corretamente.

Como sobre a implementação de uma arquitetura de tubos e filtros. Pipes conectar filtros juntos e pedidos de fila se o filtro não é rápido o suficiente. Processamento acontece filtros dentro. O primeiro filtro é o mecanismo de AI, enquanto o motor de renderização é implementado por um conjunto de filtros subsequentes.

Em cada escala de temporizador, o novo estado mundo dinâmico é calculado com base em todos os insumos (tempo é também uma entrada) e um Copiar inserido no primeiro tubo.

No caso mais simples o seu motor de renderização é implementado como um único filtro. Ele só tem os instantâneos de estado do tubo de entrada e processa-lo juntamente com o estado estático. Em um jogo ao vivo, o motor de renderização pode querer ignorar estados se houver mais de um no tubo, enquanto se você estiver fazendo uma referência ou produzir um vídeo que você vai querer tornar cada um.

Quanto mais filtros você pode decompor o seu motor de renderização para, melhor o paralelismo será. Talvez seja mesmo possível decompor o motor AI, por exemplo, você pode querer separar estado dinâmico em rápida evolução e estado de mudança lenta.

Esta arquitetura dá-lhe boa paralelismo sem um monte de sincronização.

Um problema com esta arquitetura é que a coleta de lixo está indo para executar freqüentemente congelando todos os tópicos de cada vez, possível matar qualquer vantagem adquirida com multi-threading.

Parece que você precisa de um PriorityQueue para colocar as atualizações para o modelo on, em que atualizações frmo o usuário têm prioridade sobre as actualizações a partir da simulação e outros insumos. O que eu ouço você dizer é que o usuário sempre precisa de feedback imediato sobre suas ações wheras as outras entradas (simulação, caso contrário) poderia ter trabalhadores que podem demorar mais do que um passo de simulação. Em seguida, sincronizar na PriorityQueue.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top