Como atualizar as configurações de vídeo em uma tela Opções sem reiniciar?

9

Atualmente, estou criando um RPG 2D em C ++ 11 com Allegro 5 e boost.

Meu objetivo é atualizar de alguma forma as configurações do meu jogo quando uma opção é alterada no menu Opções. Não quero forçar o usuário a reiniciar meu jogo. Outros jogos não precisam ser reiniciados ao alterar a resolução ou passar da tela cheia para a janela, então meu jogo também não deveria. Por favor, veja uma visão simplificada do sistema abaixo.

Observe que não estou necessariamente querendo chamar diretamente meu objeto de jogo a partir do OptionsScreen. A linha pontilhada é meramente ilustrar o efeito que estou tentando alcançar; de alguma forma causar uma atualização do jogo quando uma opção é alterada em uma parte diferente do sistema.

Explicação detalhada

O ScreenManager contém uma lista de todos os GameScreenobjetos existentes no momento. Estas serão várias telas do jogo, incluindo pop-ups. Esse design adere mais ou menos à amostra Game State Management em C # / XNA .

O ScreenManagercontém uma referência ao meu Gameobjeto. O Gameobjeto inicializa e modifica as configurações do jogo. Se eu quiser alterar a resolução, ficar em tela cheia ou silenciar o volume, eu o faria na Gameaula.

No entanto, o OptionsScreen atualmente não pode acessar a classe Game. Veja o diagrama abaixo:

A GameScreen pode sinalizar três eventos, onFinished, onTransitionStarte onTransitionEnd. Não existe onOptionsChangedporque apenas uma tela faz isso. O ScreenManager não pode configurar a manipulação de eventos para isso porque lida com todas as telas como GameScreens.

Minha pergunta é: como posso alterar meu design para que uma alteração no OptionsMenu não exija reinicialização, mas que seja alterada imediatamente? Eu preferiria solicitar que meu Gameobjeto fosse atualizado assim que o botão Aplicar fosse clicado.

IAE
fonte
Essa pergunta (mesmo que relacionada ao jogo) parece perfeita para programmers.stackexchange.com porque é mais sobre o design geral de OO do que sobre o jogo real
bughi
Onde você conseguiu gráficos tão impressionantes?
Joltmode 13/05
@psycketom: o primeiro é do Visio 2013 e o segundo é gerado a partir do código pelo VS2012. Espero que ajude!
IAE

Respostas:

1

Pelo que vi, a abordagem mais fácil é ler um arquivo de opções na inicialização para determinar as configurações atuais de exibição; depois, quando a tela de opções for exibida, carregue todas as opções atuais de um arquivo.

Quando as alterações são finalizadas através de um botão applyou ok, elas são salvas novamente em um arquivo. Se alguma alteração afetar a exibição, notifique o usuário que o jogo deve ser reiniciado para que ele entre em vigor.

Quando o jogo é reiniciado, as (agora novas) configurações de exibição são lidas novamente a partir do arquivo.

--EDITAR--

E ... teria ajudado se eu notasse a última frase. Você não quer ter que reiniciar. Torna as coisas um pouco mais difíceis, dependendo da sua implementação e da sua Biblioteca de gráficos de back-end.

IIRC, Allegro possui uma chamada de função que permite alterar as configurações de exibição em tempo real. Ainda não estou de acordo com o Allegro 5, mas sei que você poderia fazê-lo em 4.

Casey
fonte
Eu não acho que o problema esteja relacionado ao Allegro e suas capacidades. "Minha pergunta é: como posso alterar meu design para que uma alteração no OptionsMenu não exija reinicialização, mas que seja alterada imediatamente?" A reinicialização ocorre porque o "OptionsScreen atualmente não pode acessar a classe Game" e precisa carregá-los do arquivo de configuração, se bem entendi.
ClassicThunder
@ Casey: Olá Casey! :) Sim, estou usando um arquivo de configuração para armazenar os dados de exibição relevantes, mas quero evitar a reinicialização. É um jogo pequeno e outros jogos podem alterar a resolução sem ter que reiniciar, então não vejo por que devo forçar o usuário a reiniciar com o meu. É uma questão de usabilidade. Embora eu pudesse simplesmente chamar as funções de allegro dispay, estou tentando ficar fora de controle e não misturar minhas chamadas gráficas apenas porque posso; Não considero esse bom design.
IAE
Eu destaquei o bit "sem reiniciar" para que outros não tropeçam na mesma última frase. Um fato tão importante não deveria ter chegado ao final, peço desculpas):
IAE
@SoulBeaver Quem disse que você deve fazê-lo para não reiniciar? É uma coisa viável exigir isso; na verdade, está se tornando mais comum hoje em dia. Alguns lançamentos recentes (XCOM: Enemy Unknown, Skyrim etc.) de jogos de grande orçamento exigem uma reinicialização. (o fato de estarem no Steam pode ser o culpado devido à sincronização de opções no servidor, mas não vamos lá ...)
Casey
11
@ Casey: Verdade, e para certas opções, posso ver por que isso é necessário, mas para coisas como tela cheia / com janelas ou resolução de tela? Eu nunca vi um jogo que exigisse uma reinicialização para essas configurações. Minha pergunta básica é: por que devo forçar meu usuário a reiniciar quando o jogo pode alterar as configurações automaticamente?
IAE
1

Isto é o que eu faço para o meu jogo. Eu tenho 2 funções separadas para inicializar coisas, 'init' e 'reset'. O Init é chamado apenas uma vez na inicialização e faz coisas que não dependem de nenhuma configuração, como carregar os principais ativos. Redefinir faz o layout da interface do usuário com base na resolução da tela, assim é chamado sempre que as configurações são alteradas.

init();
bool quit = false;
while( !quit )
{
    createWindow();
    reset();

    bool settings_changed = false;
    while( !quit && !settings_changed )
    {
        ... main loop
        // set quit=true if user clicks 'quit' in main menu
        // set settings_changed=true if user clicks 'apply' in options
    }

    destroyCurrentWindow();
}

Não conheço Allegro, mas minha resposta é bastante geral, por isso espero que ajude você ou qualquer outra pessoa com um problema semelhante.

DaleyPaley
fonte
Essa é uma solução interessante. Eu estava tentando ter um controle rígido sempre que uma redefinição é chamada, mas você faz isso independentemente de uma alteração. Definitivamente, é algo que eu poderia implementar, e vou investigar, obrigado!
IAE
1

Sem mexer com sua arquitetura atual, vejo duas maneiras. Primeiro, você pode armazenar um ponteiro para a Gameinstância no diretórioOptionsScreen classe. Segundo, você pode Gamebuscar as configurações atuais em um determinado intervalo, digamos a cada segundo.

Para realmente se adaptar às novas configurações, o Game classe precisa implementar algum tipo de função de redefinição que busca as configurações atuais e reinicializa com base nelas.

Para uma solução limpa, você precisa de um gerente global de algum tipo, portanto, é mais esforço implementá-lo. Por exemplo, um sistema de eventos ou sistema de mensagens. É muito útil permitir que as classes se comuniquem sem vínculos fortes, como agregação ou composição, etc.

Com um gerente de eventos global, ele OptionsScreenpoderia simplesmente disparar globalmente um evento de redesenho queGame se registrou para ouvir antes.

Geralmente, você pode implementar uma classe de gerente armazenando eventos e retornos de chamada ouvindo-os em um mapa de hash. Em seguida, você pode criar uma única instância desse gerenciador e passar ponteiros para seus componentes. Usar C ++ mais recente é bastante fácil, pois você pode usar std::unordered_mapcomo mapa de hash e std::functionarmazenar retornos de chamada. Existem diferentes abordagens que você pode usar como chave. Por exemplo, você pode tornar o gerenciador de eventos baseado em string, o que torna os componentes ainda mais independentes. Nesse caso, você usaria std::stringcomo chave no mapa de hash. Eu pessoalmente gosto disso e definitivamente não é um problema de desempenho, mas a maioria dos sistemas de eventos tradicionais trabalha com eventos como classes.

danijar
fonte
Olá danijar. Eu já estou usando o boost :: signs para configurar um esquema rudimentar de manipulação de eventos. A linha forte do GameScreen para o ScreenManager é realmente essa manipulação de eventos. Você tem algum recurso detalhando a arquitetura de um gerente de eventos global? Mais trabalho ou não, isso pode levar a uma implementação limpa.
IAE
Embora eu não tenha lido nenhuma pesquisa sobre isso, implementei um gerenciador de eventos global para o meu próprio mecanismo. Se você estiver interessado na implementação, enviarei um link para você. Atualizei minha resposta para cobrir mais detalhes.
Danijar
1

Bem, este é um caso específico do padrão Observer.

Existe uma solução que envolve retornos de chamada. Essa é a melhor maneira de fazer isso, se você quiser um acoplamento solto, e acho que também é a mais limpa. Isso não envolverá gerentes ou singletons globais.

Basicamente, você precisará ter algum tipo de SettingsStore. Lá você armazena as configurações. Quando você cria uma nova tela, eles precisam de um ponteiro para a loja. No caso doOptionsScreen ele modificará algumas das próprias configurações. No caso do GameScreen, apenas os lerá. Portanto, no seu jogo, você criaria apenas uma instância, que será transmitida por todas as telas que requerem uma.

Agora, essa SettingsStoreclasse terá uma lista de notifiables. São classes que implementam uma certa ISettingChangedinterface. A interface seria simples e contém o seguinte método:

void settingChanged(std::string setting);

Em sua tela, você implementará a lógica de cada configuração de sua preferência. Em seguida, adicione-se à loja para ser notificado: store->notifyOnChange(this);. Quando uma configuração é alterada, o retorno de chamada é chamado com o nome da configuração. O novo valor de configuração pode ser recuperado doSettingsStore .

Agora, isso pode ser aumentado ainda mais com as seguintes idéias:

  • Use uma classe que armazene as strings de configurações - pode ser até a SettingsStore (const strings) para impedir a cópia e colar as strings.
  • Ainda melhor, em vez de uma string, use uma enumeração para especificar em quais configurações você tem
  • Você pode até especificar os valores antigos e novos como argumentos no retorno de chamada, mas isso seria mais complicado, pois você pode ter valores de string ou int, ou outros enfeites. Você decide.
Timóteo
fonte
0

Leia suas configurações de um arquivo em variáveis. Faça com que seu Gerenciador de telas rastreie se a tela de onde veio é a tela Opções e, se for, recarregue suas configurações a partir das variáveis. Quando o usuário estiver saindo do jogo, escreva as configurações nas variáveis ​​novamente no arquivo.

131nário
fonte