Treinamento em programação para jogos 2016-2 [concluído]

Começou uma nova turna de treinamento de programação. Por enquanto, os participantes são:

  • Pedro
  • Leonardo
  • César
  • João Pedro

Assim como da última vez, seguiremos os tutoriais em

1 Curtida

Se eu tiver alguma dúvida, pergunto aqui mesmo ou devo criar um tópico? Em qual categoria?

Pode mandar aqui nessa thread ou criar um tópico na categoria “Ajuda”, se for algo mais complexo.

Beleza então.
Começa a acontecer uma coisa um pouco bizarra depois que o joguinho do tutorial abre, tenho quase certeza que tem a ver com o fato de eu ter colocado o comando “love.audio.newSource” dentro da função moveObject:

Acho que se eu colocá-lo para fora da função vai resolver o problema, mas não tive tempo de testar.

Outra coisa que aconteceu foi um erro do tipo “attempt to index a nil value” sendo que não mudei absolutamente nada na lista de objetos, e inclusive o mesmo for funcionou antes.

Esse funciona:

Esse não:

O primeiro problema é o que você falou mesmo. A função love.audio.newSource aloca na memória uma sequência de som. Se você faz isso na função moveObject, que é chamada para todo objeto todo ciclo do jogo, dá para imaginar quantas vezes você está criando na memória o mesmo objeto de som. A solução é criar esse objeto apenas uma vez e guardar em alguma variável para dar play().

Já o segundo problema é porque o trecho

if d > 32 then
  object.col = false
  objects[i].col = false
end

está fora do for, logo a variávei i não existe mais. Em Lua, tudo que “não existe” vale nil.

Ajuda a evitar esse tipo de confusão se a identação do código seguir as estruturas de controle (for, if, etc.) bem à risca. Por exemplo, a sua segunda versão ficaria:

local function handleCollisions(objects, object)
  for i=1,MAX_OBJECTS do
    local d = distance(object, objects[i])
    if d < 32 and not object.col and not objects[i].col then
      -- *snip*
    end
    object.col = true
    objects[i].col = true
  end
  if d > 32 then
    object.col = false
    objects[i].col = false -- 'i' não existe aqui!
  end
end

eu aqui digitando a resposta, mas vc foi mais rapido.

o erro foi de sintaxe mesmo né

digo, os end

Acho que não chegou a ter erro de sintaxe. Parece que falta um end, mas é justamente porque a identação não está convencional.

Obrigado! Agora funcionou.

Apareceu outro problema quando tirei o love.audio.newSource de dentro do moveObject: o som só toca novamente quando o anterior termina.

Você está criando o audio Source de que tipo? “stream” ou “static”?
Use static para efeitos sonoros e stream para trilha.

Se nao for isso, o problema é onde você está chamando a funçao love.audio.play()

Tente também dar stop() antes de dar play():

local source = love.audio.newSource("geddan.mp3")

function love.update (dt)
  -- stuff here
  source:stop()
  source:play()
  -- stuff here
end

Se um som já está tocando, dar play() nele não faz ele recomeçar do zero. Precisa fazer stop() para isso.

Testei com o os dois e não houve diferença, também coloquei “love.audio.play()” fora de qualquer função e aconteceu a mesma coisa.

Desse jeito o som para abruptamente e recomeça do zero, mas acontece que várias bolinhas batem na parede ao mesmo tempo. Não há uma maneira de um som tocar simultaneamente ao outro, como acontecia quando o “love.audio.newSource” estava dentro do “moveObject”?

A ideia era isso acontecer mesmo. Permitir que haja várias “cópias” simultâneas do som e ao mesmo tempo controlar o uso de memória é uma funcionalidade mais avançada mesmo, mas que dá tranquilamente para fazer.

No mundo de computação musical, isso se chama polifonia (em contraponto com sons monofônicos). A ideia é ter um número fixo (determinado empiricamente ou de acordo com as limitações da plataforma) de cópias de um som, e cada vez que um novo play() é requisitado, o comando deve ir para a próxima cópia disponível. Se chegar ao ponto em que nenhuma cópia está disponível você pode aplicar diversas políticas de acordo com suas necessidades. Você pode:

  1. Reusar a cópia ativa mais antiga (a que faz mais tempo que recebeu o comando play())
  2. Reusar a cópia que estiver mais baixa em volume (teoricamente ela já não “faz mais diferença”)
  3. Impedir a nova cópia de ser tocada.

Dentre outras opções. Fica a seu critério. Quando uma cópia é reusada, a ideia é fazer o stop() seguido de play(), forçando ela a recomeçar mesmo. Note que isso não é um migué. Dependendo da quantidade de sons sendo tocados, um som a mais ou a menos não passa de ruído, então ter um limite “artificial” que “joga sons fora” vai na prática levar a uma experiência muito melhor para o jogador.

Se quiser detalhes mais a fundo de como implementar isso de “maneira séria”, recomendo ler este capítulo do livro Game Programming Patterns (o exemplo que ele usa não é o mesmo, mas a técnica se aplica do mesmo jeito).

1 Curtida

Hum… É que no meu caso, quando o som reinicia parece que eu puxei meu fone de ouvido do computador por um instante, não é uma transição suave. Acho que a segunda opção vai ser a mais ideal pra mim. Vou aproveitar e dar uma lida nesse capítulo.

Uma experiência que tive com sons repetidos foi num jogo de nave. Cada vez que um tiro acertava algo, tocava um som super curto. Estava tudo ok, até um ponto que quando tinha uns 30 tiros por segundo, a performance caia absurdos. Ao aplicar a estrategia (3) com base em “se um som tocou faz x milisegundos”, o problema de performance foi embora sem nenhuma diferença perseptivel.

1 Curtida

A love nao tem nada tipo spritebatch para sons? Aí vc nao aloca a mesma memoria grande da estrutura de som varias vezes

Edit: bem, acho que o spritebatch faz que nao se mande a mesma estrutura de imagem varias vezes pra placa de video. Acho que pra som isso nao faz sentido né, até pq nunca faz sentido tocar o mesmo som mais que trinta vezes anyway

E salvar varios audio Source iguais nao deve ser tao ruim se for durante o load

É, a LÖVE não tem nada equivalente a um spritebatch para sons, embora seja possível sim fazer algo nesse sentido usando OpenAL diretamente.

Exato.

Eu ia reutilizar a cópia mais baixa em volume, mas o único comando que eu encontrei para obter o volume retorna o master volume, e não o de uma única source. Decidi então criar uma lista de sources para irem tocando em sequências, e funciona bem, a única desvantagem é que o número de sources tem que ser bem maior que o números de objetos senão elas são “consumidas” muito rapidamente (devido às múltiplas colisões ocorrendo simultaneamente):

E uma última coisa que eu gostaria de saber é sobre um glitch bizarro que acontece às vezes quando as bolinhas colidem com a parede. De certa forma é desprezível, mas após a adição de efeitos sonoros é bem mais percetível: as bolinhas colidem com a parede inúmeras vezes em um curto espaço de tempo ao invés de serem refletidas normalmente, geralmente quando o ângulo que sua trajetória faz com a parede é pequeno, como se fossem aquelas bolinhas que não quicam quando se deixa cair no chão.

Como disse o kazuo, nao é necessario inumeros sons. Limita-los para um valor entre 5 e 15 ja é mais do qe o suficiente para a percepcao auditiva do jogador.

Quanto a colisao acontecer varias vezes, é porque antes da bolinha sair de contato com a parede a logica do jogo é atualizada algumas vezes, o que engatilha o som tocar varias vezes, porque a bolinha ainda está “colidindo”.

Esse tipo de coisa acontece até em jogos grandes, e é normal. Talvez você queira criar um vetor de repulsao e somá-lo na velocidade da bolinha, em vez de apenas refletir sua direcao. E talvez associar o som a um par de corpos colidiveis, e tocá-lo apenas uma vez antes de eles nao mais estarem colidindo.

Além do que o @orenjiakira disse, e dependendo de como você implementou sua colisão, você também pode verificar se uma bola vai colidir ao invés de se ela já está colidindo. Assim você as rebate antes de elas de fato colidirem, e não tem como elas “entrarem” na parede e ficar rebatendo lá várias vezes.