sexta-feira, 18 de maio de 2012

paSDLAudio e paSDLMusic

Olá leitor!

Após um certo tempo sem postar muitos avanços, venho hoje falar sobre como foi o desenvolvimento inicial da parte de som da engine. Primeiramente eu precisava reunir informações a respeito de como implementaria essa parte da engine, quais funções utilizaria, quais efeitos exploraria e qual seria o sistema de classes.

Após um estudo entre a SDL_Audio e a SDL_Mixer, preferi a Mixer, devido às suas funções mais aprimoradas. Assim, planejei dividir a engine de som em três partes:

Classe paSDLAudio: Responsável por inicializar a SDL_Mixer, bem como oferecer as constantes e ajustar parâmetros globais para as demais classes de som (ex: volume para Música e Efeitos).

Classe paSDLMusic: Responsável por gerar instâncias com métodos envolvendo a reprodução de músicas, bem como os parâmetros das músicas do jogo.

Classe paSDLSoundEffects: Responsável por gerar instâncias com métodos envolvendo a reprodução de efeitos, bem como do gerenciamento de seus canais e os parâmetros dos efeitos no jogo.


Neste post, irei abordar o desenvolvimento das duas primeiras classes, envolvendo o que deu certo e as centenas de coisas que deram errado (rsrsrsrsrsrs).

Iniciando o desenvolvimento da paSDLAudio, decidi por fazê-la oferecer uma preparação para o audio no jogo, bem como o suporte para as funcionalidades das outras classes de som. Por enquanto, essa classe possui somente a configuração da frequência do audio e da saída mono ou stereo.

O maior problema foi com a paSDLMusic. Primeiramente, planejei a classe para que essa contesse métodos para inicializar os atributos da música na classe, bem como seu conteúdo (do tipo Mix_Music). Os principais métodos da classe que desenvolvi ficaram totalmente funcionais. São eles:

1- reload: Recarregar a música, passando um novo endereço.

2- play: Tocar a música, passando o número de vezes que a mesma repetirá e o número de segundos do FadeIn. Os valores padrão são infinitos loops para o loop, e 0 para o tempo.

3- stop: Para a música. Recebe um tempo em segundos identificando o FadeOut. Por padrão é 0.

4- set/getVolumeMusics: Retorna e recebe o volume das músicas (alterando este parâmetro, toda os volumes das músicas são alterados).

5- isPlaying / isPaused / isStoped: Retorna o estado atual dos casos especificados.

6- pause: Pausa a música.

7- restart: Reinicia a música (sem fadeIn, mas reinicia o número de loops inseridos no primeiro play).


A maior dificuldade na criação dos métodos acima foi sincronizá-los de forma a oferecer uma boa estrutura anti-usuário. Por exemplo, o que aconteceria se eu apertasse pause e depois stop? Com o retorno padrão da SDL_Mixer, pause continuaria verdadeiro. Outro problema foi a ausência do estado stop, tendo eu que criá-lo baseando nos resultados das funções Mix_PlayingMusic() e Mix_PausedMusic().


Parece terminado, certo? Errado! Ainda falta estabelecer uma forma de comunicar o usuário quando a música termina, além de acrescentar a um contador o número total de músicas que ja foram criadas, para não exceder um certo limite (8 no caso).

Para tal, tive que utilizar a função Mix_HookMusicFinished, a qual recebe um ponteiro para uma função que será chamada quando a música finalizar.
A minha primeira tentativa de resolver o problema foi passar um ponteiro para um método, cuja sintaxe foi descrevida pelo autor que estava lendo o tutorial como "temida até pelos experts em programação avançada de ponteiros". Consegui programar o ponteiro de métodos, mas infelizmente a função só recebe realmente ponteiros de funções.

Meu segundo passo foi criar uma função do tipo friend, a qual recebesse uma referência a um objeto da classe para que eu pudesse modificar as variáveis privadas quando o fim da música fosse chamado. O grande problema é que a tal função também não aceita apontar para funções que recebem parâmetros.

Sem sucesso nas tentativas, comecei a tentar pelo caminho dos métodos e variáveis estáticas, já que estes podem ser acessados facilmente em qualquer lugar. Então, descobri como utilizar métodos estáticos, e também como fazer a misteriosa declaração de variáveis estáticas. Para declará-las com sucesso, foi necessário definí-las no escopo privado da classe e no topo do arquivo cpp.

Feitas todas as modificações necessárias dentro de todos os métodos da classe para substituir algumas variáveis e métodos para estáticos, tudo parecia correr bem, quando outro problema surge: Em caso de loop, a SDL_Mixer só chama a função quando todos os loops tiverem sido rodados, o que estragava completamente boa parte do que tinha feito.
A solução que encontrei foi gerenciar a reprodução uma a uma, mandando reproduzir uma vez a cada vez que a música acabasse, até um contador atingir o número total de loops desejados. O problema dessa abordagem foi que estabeleci praticamente a maior responsabilidade de gerenciamento para a função passada como parâmetro em Mix_HookMusicFinished. Isso se tornou um problema, pois estava perdendo o controle dos estados da música (pausada, parada, reproduzindo, finalizada).

Lá vou eu mais uma vez. Após resolver todos estes pepinos, surge outro problema: Acabei tendo a necessidade de definir o endereço das músicas como estático para que a função tivesse acesso. Isso feria completamente o sentido da classe, uma vez que cada objeto é uma música. Se a variável contendo as músicas for estática, então não existem objetos com músicas diferentes.
Tentei resolver esse problema aplicando um friend entre as classes paSDLAudio e paSDLMusic (hahaha, aprendi muitas coisas novas com essa maldita classe de som) para ter acesso a variáveis privadas de gerenciamento, e criei um vetor boleano que sinalizasse se a posição do vetor de músicas estava ocupada. Então, percorria-se todo o vetor em um for buscando posições false, e a primeira encontrada era atribuída como ID para aquela música.

Parece genial, mas é estúpido, pois para acessar a música certa eu também precisaria deixar o ID como estático rsrsrsrs... Nesse ponto, a brincadeira ja estava começando a perder a graça...

Ainda tentei, ao invés de usar a variável estático com o nome da música para reprodução, um ponteiro estático para a música atual, mas já estava tendo sérios problemas com os principios do KISS e YAGNI para fazer algo que simplesmente poderia ser resolvido facilmente pelo usuário...

Ou seja, se o usuário quer controlar quando a música termina, basta que ele mande a música ser reproduzida apenas uma vez usando play, e, quando isStoped() retornasse true, o usuário reiniciasse a reprodução usando restart(). É, as vezes uma funcionalidade a menos resolve todos os problemas...

Apesar dos problemas, consegui exercitar três conceitos que não utilizava (métodos e variáveis estáticas para classes, friend de funções e classes e ponteiros para funções e métodos). Também me rendeu uma boa refatoração, e centenas de testes da classe. No fim deu tudo certo!


Pretendo continuar implementando a parte de som, agora com foco nos SoundEffects.

Até lá!

segunda-feira, 14 de maio de 2012

Produções do fim de semana

Olá!

O fim de semana, a segunda e terça feira até agora não foram muito produtivos. Isso porque estive lidando com algumas coisas que eu não dominava muito bem. Foquei em aprimorar meus conhecimentos sobre a biblioteca SDL_Mixer, sistemas básicos de colisão, blend, e, na parte da documentação, diagramas de casos de uso, realizações de casos de uso, diagrama de atividades e diagrama de classes e objetos.

Os assuntos abordados que envolvem a documentação foram estudados a fim de auxiliar a Keli na revisão dos documentos e diagramas que já estão prontos.

Os assuntos referentes à colisão foram estudados seguindo o planejamento da busca pelas melhores maneiras de detecção de colisão no Jogo Ticket To Ride e no Gerador de Mapas. Estudei três técnicas:

- Colisão por Axiis Aligned Bouding Boxes.
- Colisão por Bouding Circle.
- Colisão de Bouding Circle com AABB.

Ainda, estudei a biblioteca SDL_Mixer, buscando separar as funcionalidades mais interessantes para a reprodução de sons para a engine. O desenvolvimento das classes de som já foi iniciado por mim hoje!

Pretendo até o final da quarta-feira estar com as classes de audio prontas e testadas, para então revisar o final da sprint da semana com a Keli e especificar as próximas tarefas.

sexta-feira, 11 de maio de 2012

Alterações na Documentação



Olá!!!

Neste post, informarei a situação da documentação e as alterações recentes.

Tivemos várias alterações na Estimativa de Desenvolvimento, tanto na parte de implementação, quanto na documentação em si. Levando em conta que minha responsabilidade é atualizar e balancear essas mudanças, sendo estas decididas em equipe, precisei modificar o Documento de Arquitetura e, consequentemente, as tarefas no Pivotal Tracker.

As principais modificações foram realizadas em:

* Requisitos Funcionais: Adição de itens no tópico Menu.

* Estimativas de Desenvolvimento:

  - Estória: Programação intermediária da engine: foi adicionada a tarefa "Programação da classe de tratamentos de erros da engine".

 - Estória: Elaboração inicial do jogo Ticket To Ride: a produção da arte foi melhor dividida em tarefas. Assim, nesta estória foram adicionadas as tarefas "Produção dos principais elementos da arte do jogo - Menu." e "Produção dos principais elementos da arte do jogo - Sprite das cartas de construção (trens coloridas)."

- Estória: Desenvolvimento intermediário do jogo Ticket To Ride: seguindo a divisão de aspectos de arte, adicionei as tarefas. "Produção dos elementos da arte – HUD Padrão", "Produção dos elementos da arte – HUD (Personagens)", "Produção dos elementos da arte – Tela(s) de Regras" e "Produção dos elementos da arte – Telas do Jogo".

  - Estória: Documentação: nesta estória havíamos definido certas tarefas sem ter o conhecimento do Documento de Arquitetura, pois este foi passado a nós após a realização da Estimativa de Desenvolvimento. Com o documento divulgado, resolvi substituir as tarefas anteriores pelos tópicos (vistos anteriormente no post Escopo geral da documentação) do referido documento.  

  - Estória: Programação avançada da engine: a pedido do Ráfagan, fiz a substituição da tarefa "Inserção de recursos OpenGL para produção de jogos 3D." pelas tarefas "Implementar cálculo de iluminação.", "Implementar efeitos de Blend,", "Implementar carregamento de modelos 3D." e "Implementar colisão 3D."

* Além de recalcular a soma das complexidades de cada estória alterada e fazer a substituição ou inserção das tarefas na ferramenta Pivotal Tracker, aproveitei para colocar etiquetas(labels) referentes ao nome de cada estória do Documento de Arquitetura:

- Estória: Programação básica da engine. > programação básica

- Estória: Programação intermediária da engine. > programação intermediária

- Estória: Programação de um Gerador gráfico de mapas T.T.R. com leitura de arquivos XML. > programação gerador gráfico.

- Estória: Elaboração inicial do jogo Ticket To Ride. > elaboração inicial.

- Estória: Desenvolvimento intermediário do jogo Ticket To Ride. > desenvolvimento intermediário.

- Estória: Fase final de produção do jogo Ticket To Ride. > fase final.

- Estória: Documentação. > documentação.

- Estória: Programação avançada da engine. > programação avançada.

- Estória: Teste do game. > testes.

- e + etiquetas de divisões: programação, arte, som e extras para facilitar a pesquisa por tipo de tarefa além da estória a qual já pertencem.

* Diagrama de Casos de Uso: ao realizar a tarefa de Realização de Casos de Uso seguindo este diagrama, senti a necessidade de fazer algumas alterações, como adicionar o caso de uso "Visualizar Regras" diretamente como opção do menu, e não apenas na animação programada para aparecer em determinado tempo após não ocorrer nenhum evento no menu; E um novo caso de uso chamado "Iniciar Partida" entre o sorteio da ordem dos jogadores e receber cartas para ficar mais claro o inicio da partida.

Por enquanto são estas, mas podem aparecer mais alterações, pois ainda estou fazendo a Realização dos Casos de Uso referentes a este diagrama.

Assim que terminar esta tarefa poderei comentar se houveram novas alterações.

Refatoração Concluída

Olá!

Das refatorações que faltavam, nenhuma falta mais!

paSDLWindow deu origem à nova classe paSDLEvent como comentado no post anterior, paOpenGL se tornou paOpenGLWindow, dando origem às classes paOpenGLDraw, sendo esta responsável pela configuração do desenho na tela (iluminação, blend, configuração de texturas,...); e paOpenGLTransform, responsável por realizar as transformações básicas e inversões de matrizes.

Também trabalhei para melhorar o sistema de proteção das classes,  declarando construtores como explicitos, colocando os construtores de cópia padrão no escopo private, e implementando outros quando necessário.

Criar uma nova classe para o gerenciamento do desenho em OpenGL ainda necessita de alguns ajustes, mas está quase lá.

Pretendo daqui pra frente focar na finalização da classe paOpenGLDraw, criar um exemplo para teste das novas alterações na engine que ainda não foram testadas, e dar algumas modificações no modo de carregamento de texturas e controle da câmera, além de modificar a classe paSDLEvent para comportar um modelo de eventos mais simples para o usuário.

Terminadas essas alterações, estarei focando no desenvolvimento de um modelo de colisão por seleção usando OpenGL, e se eu conseguir, o próximo passo será desenvolver o gerador de mapas!

Até lá!

Nova funcionalidade: paSDLEvent

Olá pessoal!

Hoje eu consegui uma proeza que ha tempos estava querendo fazer. A captura de eventos da engine!!!

Quando inciei a programação da mesma, havia planejado três tópicos inciais: Desenho na tela, GameLoop e captura de eventos.

Os dois primeiros eu consegui, porém o terceiro tópico era sempre adiado, por motivos de importância maior, como, por exemplo, me atualizar na matéria de OpenGL. Essas obrigrações acabaram me obrigando a criar uma estrutura simples de captura de eventos.

Mas isso terminou hoje. Com a refatoração da engine, a próxima parte a ser refatorada da paSDLWindow seria os métodos com tratamento de eventos. Nesse momento, a necessidade de criar uma classe para tratamento de eventos formalizada se tornou iminente.

No meu planejamento inicial, o objetivo era basicamente programar uma estrutura do tipo Polling, onde todas as teclas seriam testadas quanto ao seu estado: ativo, pressionado ou liberado, muito semelhante ao o que acontece na Chien2D. Essa abordagem permite que multiplos eventos do teclado (como o pressionar de várias teclas ao mesmo tempo) seja efetuado mais facilmente, abordagem esta que a operação manual de captura de eventos usada pela SDL não comporta.

Para quebrar a dificuldade dessa implementação, a dividi em três dificuldades:

1- Capturar um evento qualquer;
2- Capturar um evento das teclas desejadas;
3- Capturar um evento de uma tecla desejada levando como consideração as estruturas ativo, pressionado e liberado.

Na primeira tarefa, implementei uma estrutura básica de captura de eventos, recebendo os casos de SDL_KEYDOW, SDL_KEYUP, SDL_QUIT, SDL_MOUSEMOUTION, SDL_MOUSEBUTTONDOWN e SDL_MOUSEBUTTONUP.

Para a segunda tarefa, implementei uma estrutura de enumeração, contendo os nomes das constantes das teclas que a engine irá suportar. estas constantes são:


K_UP, K_DOWN, K_RIGHT, K_LEFT, K_ESC, K_F1, K_F2, K_F3, K_F4, 
K_A, K_B, K_C, K_D, K_E, K_F, K_G, K_H, K_I, K_J, K_K, K_L, K_M,
K_N, K_O, K_P, K_Q, K_R, K_S, K_T, K_U, K_V, K_W, K_X, K_Y, K_Z, 
K_0, K_1, K_2, K_3, K_4, K_5, K_6, K_7, K_8, K_9,
K_ENTER, K_SPACE, K_LALT, K_RALT, K_LCTRL, K_RCTRL, K_LSHIFT, K_RSHIFT, K_ENDBUTTON

M_LEFT, M_RIGHT, M_MIDDLE



Por fim, a captura levando como consideração as tags mencionadas. Para tal, precisei implementar structs que contivessem as três tags como variáveis boleanas, e atualizá-las a cada pressionar de teclas, sendo que cada tecla possui sua própria struct. no caso das teclas liberadas é fácil, mas como identificar se uma tecla está pressionada ou ativa?

A solução que implementei foi inicializar a cada gameloop os estados das teclas pressionado e ativo para falso, ou seja, para que o estado pressionado voltasse a ser verdadeiro, o usuário se obrigaria a apertar diversas vezes o evento da tecla para alterar seu valor boleano de pressionado. Já o estado ativo não era ressetado no inicio de cada GameLoop, mas apenas quando a tecla fosse liberada.

Quando implementei a classe no jogo, eu somente tive que mencionar a captura de polling da mesma na camada mais acima de abstração da engine (ou seja, em paGameLoop), e o usuário se preocuparia apenas em capturar o estado atual da tecla desejada por meio de um ponteiro.

Por hoje é só. Ainda pretendo implementar uma interface secundária de captura de eventos para os usuários que não forem utilizar a paGameLoop.

Até a próxima!

quinta-feira, 10 de maio de 2012

Demais alterações

Olá!


Terminada a detecção dos erros anteriores e a organização da classe paGameLoop, iniciei a refatoração da classe paSDL. Pra começar troquei seu nome para paSDLWindow, de forma que os métodos relacionados a tratamento de eventos e desenho e carregamenta de surfaces necessariamente devem ser removidos. Como a interface de tratamento de eventos ainda não está completa, decidi começar pelo desenho.

Esse processo deu a luz a uma nova classe: paSDLDraw. Ela é responsável por carregar e desenhar qualquer coisa que envolva a SDL. Os maiores trabalhos que tive com ela foram remodelar a classe paSDLSprite para que esta não mais acessasse a classe paSDLWindow para desenhar, e também na classe paOpenGL, a qual desenha as texturas usando o tratamento de inversão de píxels com a SDL.

Meus próximos objetivos agora são finalizar a refatoração da classe paSDLWindow, assim como as da paOpenGL; e depois iniciar a revisão do código referente à classe paCamera, que ainda não está completa!

Até a próxima!

Refatorações e Correções de problemas

Olá a todos!

Nessa quarta-feira (09/05) me dediquei inteiramente a buscar maneiras de melhorar o código da engine para poder seguir em frente. Os maiores problemas envolvidos a essa iniciativa envolviam o fato de que muitas implementações mais novas estavam pouco encapsuladas (por exemplo, a classe paGameLoop acabava deixando de fazer algumas funções mais complexas, deixando essa responsabilidade para o usuário).
Outros problemas referiam-se às classes paSDL e paOpenGL. O maior deles era que a classe openGL é filha de SDL. Pensando nas relações hierarquicas "É UM", essa assertiva se torna confusa.

Buscando resolver essas coisas, comecei inicialmente tentando modificar a classe paGameLoop para fazer algumas funções de ajustes da janela, como inicializá-la, desabilitar o modo de controle de GameLoop padrão, configurar o desenho, dentre outras. Ou seja, o usuário que não desejar implementar as configurações desejadas para a janela e para a engine manualmente, poderá fazer uso desses métodos de configuração.

Nessa altura do campeonato, alguns bugs e comportamentos estranhos estavam me incomodando (para uma maior visão sobre o problema abordado a seguir, leia o post anterior). O primeiro deles envolvia a lentidão da compilação após a inserção das novas configurações de projeto ja comentadas anteriormente. Em seguida vinham erros msiteriosos que apareciam nos includes e em algumas regiões do código, mas que, ao compilar, o executável era gerado com sucesso. O ultimo problema envolvia a necessidade de gerar um novo arquivo lib a cada modificação na engine, além de que muitas vezes parecia que os arquivos acessados pelos jogos não eram os mesmos que os arquivos da engine que estava modificando

Como havia feito muitas modificações no projeto, estava difícil saber de onde vinham todos esses erros. E depois de tentar resolvê-los de centenas de maneiras diferentes, acabei desistindo e voltando para a abordagem anterior de um projeto só com a engine e os jogos em conjunto. Enquanto programava no código desta maneira, descobri que o motivo da lentidão na compilação eram os propertie managers que não faziam parte do computador que eu estava usando. Agora sei que mesmo funcionando, não é a melhor escolha manter os propertie managers de ambos os computadores no projeto.

O outro caso dos erros misteriosos se resolveu quando eu passei a dizer o endereço exato dos arquivos do tipo header no include. Como dividi o projeto em uma pasta include e outra para a lib, mantendo os arquivos cpp junto ao arquivo de projeto do visual studio, os arquivos .h, mesmo estando juntos, eram sinalizados como arquivos não encontrados pelo visual studio. O intrigante é que a teoria de que estes arquivos estavam no local certo se fazia satisfeita quando a compilação rodava normalmente.

Por exemplo, o include abaixo no paSDLSprite.h era sublinhado pelo VS como um erro:

#include "paSDL.h"

Engraçado para arquivos localizados no mesmo lugar... Mas a solução para satisfazer as vontades do VS está em especificar o endereço do arquivo de acordo com o local onde se encontra o arquivo do projeto:

#include "include/paSDL.h"


Por enquanto é só. No próximo post especificarei as demais modificações que fiz na engine...