Boa maneira de tocar um som quando algo acontece? Como isso soa?

10

Então, eu estava pensando em quão monolíticas minhas aulas ficam a maior parte do tempo. Por exemplo, no método Characterda classe Jump, pode-se ter uma referência a um objeto de efeito sonoro e tocá-lo. Por si só, isso é bom, mas quando a física, a animação, a colisão etc. são levadas em consideração, o método Jump se torna imenso e a Characterclasse tem muitas dependências para muitas coisas diferentes. Ainda assim, isso pode ser bom. No entanto, e se não quisermos mais tocar um som quando o personagem pular? Agora, temos que encontrar essa linha específica de código na bagunça confusa do Jumpcódigo e comentar ou algo assim.

Então .. eu estava pensando ..

E se, em vez disso, houvesse algum tipo de AudioSystemclasse e tudo o que ele fizesse fosse se inscrever em eventos aleatórios nos quais ela se interessa por outras classes. Por exemplo, a Characterclasse pode ter um Jumpedevento (também estático, suponho) que é gerado dentro da Characterclasse no método Então, a Characterclasse não saberia nada sobre o pequeno efeito sonoro que é reproduzido quando o personagem pula. A AudioSystemseria apenas uma enorme classe que o programador poderia retirar-se para ligar efeitos sonoros com certos eventos que acontecem no jogo através do uso de eventos estáticos. Então, se ele ficou muito grande que poderia ser separados em subclasses, como EffectsAudioSystem, BackgroundAudioSystem, AmbientAudioSystem, et cetera.

Então, nas opções do jogo, pode-se ter uma caixa de seleção para ativar ou desativar esse tipo de som e tudo o que precisa ser feito é apenas desativar esse sistema com uma bandeira booleana simples e única. Essa idéia de sistemas também pode ser estendida a coisas como física, animações etc. até o ponto em que a maioria das respostas ao jogo resultantes das ações dos jogadores são conectadas por esses sistemas elaborados e dissociados.

Ok, então minha pergunta pode ser um pouco vaga, mas como isso soa? Eu realmente nunca ouvi falar de muito tipo de conversa sobre esse tipo de sistema. Isso está na minha cabeça agora, sem qualquer codificação feita até agora, talvez seja um daqueles acordos "bons em teoria, mas não na prática". Esse tipo de sistema funcionaria com um jogo maior ou acabaria quebrando e se tornando ainda mais uma bagunça de espaguete do que o sistema original?

Richard Williams
fonte
5
Soa como uma boa idéia :) (Em uma nota mais grave: o uso de mensagens para comunicação entre fracamente acoplados subsistemas / classes é geralmente uma boa idéia do projeto-wise)
bummzack
1
é assim que é feito, você deve colocar sua renderização em uma classe separada também, se ainda não tiver (como em, você não deve ter uma função draw () na classe Character).
Dreta

Respostas:

2

As mensagens são um inferno para depurar e manter. Parece bom em teoria, mas, uma vez colocado em prática, fica confuso com o envio de muitos dados duplicados. O efeito de salto-som precisará de muito mais dados no final, por exemplo, a posição, velocidade, material em que o personagem está, você escolhe, a lista será longa no final.

Portanto, você precisará coletar esses dados e enviá-los ao AudioManager por meio de um evento / mensagem muito específico com os dados nele copiados ou enviar uma referência ao caractere na mensagem, para que o AudioManager possa acessar os dados, ambos as maneiras acabam bagunçadas, e agora o gerenciador de áudio precisa escolher um som para o material underground, etc.

Portanto, no final, o evento específico (que é uma classe muito específica apenas para esta mensagem) reunirá essas classes muito profundamente novamente. Não ganhou muito e, no final, você terá uma grande lista bagunçada de eventos / classes muito específicos que servem apenas ao propósito de enviar dados, que já existem e podem estar desatualizados e sofrerão com todos os outros problemas de dados duplicados. .

Portanto, haverá uma enorme lista de classes desnecessárias a serem mantidas, que introduzem um profundo acoplamento entre o personagem e o AudioManager, mas agora ele está espalhado por todo o código-fonte. Não apenas nas classes Character e AudioManager.

Ainda é uma boa idéia desacoplar seu código, mas as Mensagens são realmente apenas uma outra maneira de acoplamentos profundos. Alguns códigos precisam ser acoplados, use a maneira mais direta de combiná-los, não exagere na engenharia.

Maik Semder
fonte
1
Como um sistema de mensagens bem projetado "é apenas outra maneira de acoplamentos profundos"? Enviar mensagens é o acoplamento mínimo absoluto sem que os objetos nunca se comuniquem. A maneira como você o projetou pode causar problemas, mas se o sistema captar apenas um som, local e tipo, ele resolverá todos os problemas dele e não apresentará nenhum dos que você sugere que possa ocorrer. O sistema de áudio não deve calcular qual som é necessário, deve abstrair todas as configurações e, de preferência, problemas de difusão. pt.wikipedia.org/wiki/Coupling_(computer_programming)
ClassicThunder
1
@ClassicThunder, o ponto é que, na prática, essa abordagem não escala muito bem. Funciona bem para aplicativos simples e contanto que você precise apenas de um PlaySoundEvent geral. Mas a pergunta é sobre o AudioManager ouvindo um evento OnJump () especializado, para que o personagem possa se livrar do trabalho de áudio. No entanto, esse não será o caso de um simples PlaySoundEvent, pois o personagem deve escolher o som e enviá-lo ao AudioManager, o que invalida o ponto original da introdução do OnJumpEvent para se livrar do trabalho de áudio.
Maik Semder
1
Ao usar o OnJumpEvent, no entanto, você pode optar por adicionar a referência ao caractere ao evento ou copiar todos os dados importantes do caractere para o evento. Claro que você está certo, o último não introduziria um acoplamento profundo, mas sofrerá com problemas de duplicação de dados e um novo objeto de passagem de dados que deve ser mantido, como em todos os outros novos eventos.
Maik Semder
1
-1 porque eu concordo que ter mensagens tão especializadas (OnJump) provavelmente é uma má ideia, mas é apenas uma longa 'Não, isso é uma má ideia' em vez de informações úteis para levar a pessoa a fazer um evento no PlaySound com o nome de um efeito sonoro e uma posição e / ou volume 3D em que ocorreu.
James
@ James, obrigado pela contribuição, não quis dizer usar um evento PlaySound, quis dizer que os eventos são uma boa abstração para um aplicativo de banco de dados da GUI, mas não é prático para um jogo complexo.
Maik Semder
2

Eu não acho que um sistema de transmissão de mensagens tenha acabado com a engenharia. De fato, pode tornar muito mais fácil fazer as coisas na fase de polimento. Você está fazendo isso certo!

O que você descreveu é exatamente o que reuni para o nosso jogo Global Game Jam no ano passado. Eu era responsável por criar e editar o SFX, e integrar a música que eu e outro compositor escrevemos no jogo de uma maneira que não era ruim.

O que é ótimo nessa abordagem do ponto de vista de áudio é que ela permite fazer muitas coisas mais interessantes com o seu som. Se você acha que um efeito sonoro em um jogo é apenas um arquivo de som, volume e movimento panorâmico, está fazendo errado.

Exemplo

Para o nosso jogo, você era um dinossauro pilotando uma nave espacial correndo em planetas para marcar pontos. Como estávamos trabalhando no Flash, não era necessária uma infraestrutura orientada a dados. O AudioManager era uma classe que consistia em vários métodos estáticos cujo único objetivo era controlar quais sons aconteciam em resposta a um evento do jogo.

Se eu escrevesse em C ++, levaria um pouco mais de tempo para abstrair todos os comportamentos possíveis que os sons poderiam ter. Os requisitos para uma mensagem notificando o sistema em que uma ação ocorreu não seriam muito complicados. Seria necessário apenas o tipo de mensagem, o objeto de origem ou o objeto afetado, o acesso a algum tipo de contexto do estado do jogo e não muito mais. O protocolo pode crescer conforme as necessidades do jogo. Naturalmente, se você fizer tudo isso na implementação em código (como nosso código GGJ de má qualidade), terá um problema de classe monolítica pior. Mas isso é facilmente mitigado ao criar um sistema orientado a dados.

De qualquer forma, veja como nosso sistema de áudio do jogo reagiu a várias mensagens:

  • O jogador colide com o planeta: Isso provocaria um som de explosão do planeta, bastante básico. imediatamente após consultar o contador de combinação em execução. Se fosse alto o suficiente, agendaria um efeito sonoro para ser tocado meio segundo depois do dinossauro fazer um rugido de vitória. Também em segundo plano, foi calculado um valor aleatório da população do planeta (algo como 600 a 3000 - não sei por que esse intervalo foi escolhido, era uma mecânica de jogo abandonada e ainda estava por aí para eu usar para tornar o áudio interessante), e então eu usei isso para dimensionar o volume do som distante dos gritos (cidadãos planetários encontrando um destino prematuro).

  • O jogador mantém a barra de espaço para aceleração: ao receber isso, um pequeno som de propulsor foi tocado, mas também simultaneamente um rugido baixo do motor em loop aumentou 1,5 segundos. O sistema de partículas também usou isso para disparar um emissor IIRC

  • O player solta a barra de espaço para desacelerar: Agora que o player soltou a barra de espaço, o sistema de áudio sabia que tinha que acelerar o ciclo do motor. Se eu tivesse mais tempo, teria gostado de colocar outro som sobre ele, que era uma espécie de som lamentável.

  • O jogador colide com a mina espacial do mal: as minas espaciais são ruins, então não apenas existe um som de impacto metálico combinado com uma explosão (que é apenas um som), mas também há um som de desânimo de dinossauro selecionado aleatoriamente. É mais provável que escolha mais sons de "choro" à medida que a saúde do jogador diminui.

Um jogo já divertido se torna uma delícia de jogar quando sua trilha sonora é ativa e dinâmica, mesmo com apenas alguns comportamentos simples, como eu descrevi acima. Sim, há alguma logística para resolver, garantindo que os dados corretos sejam transmitidos. Mas ei, BFD. Isso está longe de ser a coisa mais complicada que você precisa escrever no escopo maior do código do jogo.

De fato, FMOD e Wwise funcionam assim. Eles não têm um distribuidor de mensagens central, mas você efetivamente publica eventos em seus sistemas centrais e eles reagem tocando um efeito sonoro que foi pré-projetado por um implementador de áudio em uma ferramenta de autoria. Pense nisso como dar ao seu jogo um DJ ao vivo. Ele senta e assiste o que está acontecendo, e aciona clipes de som nos momentos certos para manter as coisas interessantes, misturando-as para que se encaixem bem no ambiente de áudio preexistente.

[EDIT] Além disso, vejo que você marcou este C #. É este XNA e, em caso afirmativo, você está usando o XACT? Se você estiver usando o XNA, deverá usar o XACT.

michael.bartnett
fonte
1
Isso pode funcionar para um projeto pequeno, pode até ser divertido. No entanto, em uma grande, você acaba com um grande número de classes de mensagens, que precisam ser mantidas, enquanto uma simples chamada de função teria o mesmo efeito. É por isso que o sistema de eventos não escala bem, fica difícil gerenciar quanto maior o projeto.
Maik Semder
1
Como trabalhamos com FMOD no meu estúdio, não há sistema de mensagem / evento, você não envia eventos para FMOD, você simplesmente chama uma função c ou um método c ++ para reproduzir algo. Eles apenas chamam seus sons de "eventos", o que não o torna um sistema de eventos, é apenas o termo que eles usam em vez de som.
Maik Semder
Não por que? Você apenas chama a função diretamente em vez de usar um evento para passar os parâmetros. Um evento é no final nada mais como uma chamada de função, apenas passando os parâmetros no objeto de evento, em vez de transmiti-los diretamente. A única diferença é o novo indireto introduzido pelo sistema de eventos, mas no final é uma chamada de função simples, apenas desnecessariamente complicada demais.
Maik Semder
@MaikSemder Como as chamadas de método não acabam na sua própria emaranhada teia de maldade? Além disso, tentei anotar essa distinção entre um sistema de eventos e os "eventos" usados ​​pelo Wwise e pelo FMOD. A ideia é que a lógica de áudio complexa não pertença às classes de objetos do jogo, e abstraindo a lógica do som, de modo que a interface seja semelhante ao despacho de um evento, torna mais fácil ter uma lógica de áudio rica. Eu realmente vejo pouca diferença funcional entre EventManager->dispatch("Sound:PlayerJump")e soundSystem->playFMODEvent("/MyGame/Player/Jump").
michael.bartnett
2
Pode haver o benefício de uma codificação mais fácil, mas não é de graça, vem com custos de desempenho, manutenção e depuração mais difícil. O que quero dizer é que o benefício não vale a pena para grandes projetos. Você está lidando com muito mais objetos do que sem eventos e precisa pagar o preço por isso. O único lugar que eu consideraria usar um sistema de mensagens é a comunicação entre segmentos para impedir o bloqueio entre os segmentos.
Maik Semder
0

Concordo com Maik Semder, que um sistema de transmissão de mensagens pode estar com excesso de engenharia (por enquanto, pelo menos).

Pelo que entendi, sua classe atualmente se parece com a "classe monolítica" de Bjorn, como pode ser visto em "Uma classe monolítica" aqui .

Sugiro que você leia esse artigo e, embora um sistema de componentes completos seja um exagero por enquanto, se você ler "Dividir o resto", isso deve lhe dar uma boa maneira de abstrair seus comportamentos e, eventualmente, talvez mudar para um mais complexo solução. Isso lhe dará uma boa base para começar de qualquer maneira.

deceleratedcaviar
fonte