Terminei o que considero a fase de prototipação com algumas ferramentas antes de começar a escrever de forma séria um jogo. Escrevi código de qualquer maneira, sem me preocupar se estava elegante, reutilizável, modular, etc. O que queria fazer era experimentar algumas APIs e ver se elas me permitiam fazer as coisas do jeito que eu queria.
Estas APIs são: OpenGL, Assimp, Bullet e OpenAL. Com estas 4 APIs é possível criar jogos e gráficos em tempo real de alta complexidade, com características similares aos jogos mais modernos da atualidade, porém o meu intuito não é criar nada grande, afinal sou um programador apenas, porém com estas APIs posso me concentrar no que mais me interessa, que é escrever o código de um jogo, sem ter que me dedicar a escrever uma biblioteca de física, de detecção de colisões, de áudio 3D, etc.
Mesmo com todas estas ferramentas a disposição sei que a tarefa de desenvolver um jogo de corrida é bastante complexa, porém adotando estas ferramentas poderei me concentrar mais no que me interessa, que é desenvolver a física dos carros e aprender principalmente a API OpenGL. Com elas fiz o seguinte exemplo:
Utilizei a biblioteca Assimp para carregar o modelo dos dois cubos que foram criados com o Blender e exportados para o formato Collada. Através da Bullet adicionei as informações a respeito dos corpos rígidos e obtive as informações das colisões para reproduzir os sons através da OpenAL. OpenGL foi utilizada para gerar a visualização baseada nos dados de física disponíveis e renderizar informações visuais a respeito da geometria utilizada pela biblioteca de física (uma informação muito útil disponibilizada pela biblioteca Bullet).
Algumas pessoas ao lerem esse post podem se perguntar porque eu não estou utilizando uma engine para escrever meu jogo. A resposta é bem simples, quero aprender OpenGL e também quero aprender a integrar estas ferramentas. Depois de desenvolver este simples exemplo já tenho uma noção muito melhor de como as engines são programadas e entendo muito melhor como utilizá-las. Como quero trabalhar profissionalmente um dia com desenvolvimento de jogos, prefiro botar a mão na massa, eu aprendo muito melhor assim!
Não quero desenvolver um super jogo. Estou pensando simples, pois sei que não tenho recursos para desenvolver um jogo top de linhal, mas sei que tenho todas as condições para desenvolver um jogo divertido como o F1GP de 1993: http://www.simracingworld.com/games/2/ e este é justamente meu objetivo. Fazer um jogo de F1 nos mesmos moldes, porém com a ajuda de todas estas APIs que já comentei.
Não é uma tarefa incrivelmente difícil, mas também não é uma tarefa fácil, sendo assim me preocupo como irei desenvolver. Li já faz algum tempo o livro Design Pattens: Elements of Reusable Object-Oriented Software, porém sempre estou folheando para me inspirar em padrões que possam resolver problemas que estou tendo. Li alguns livros específicos de jogos, como Core Techniques and Algorithms in Game Programming (CTAGP), Game Architecture and Design (GAD) e Beginning OpenGL Game Programming (BOGP). Cada um desses livros tem coisas boas e ruins. O GAD possui bastante material orientado a equipes de desenvolvimento, porém também possui algumas ótimas figuras a respeito da arquitetura de um jogo moderno, coma a figura seguinte que mostra os módulos e a interação entre eles em jogo moderno:
Este livro também contém algumas técnicas úteis para pensar a intereção entre os diversos elementos presentes no jogo. O livro CTAGP é bastante abrangente e por isso não trata muito a fundo os assuntos abordados, servindo apenas de coceira mental para buscar mais informação. O BOGP é um ótimo livro, bastante simples e prático, contém muita informação interessante para quem quer usar OpenGL em jogos.
Acabei de comprar o livro Large-Scale C++ Software Design, que apesar de já ser antigo é um dos poucos livros que trata da organização física dos arquivos de um projeto. Apesar de não querer fazer nada grandioso, tenho certeza que meu jogo não será igual um trabalho de faculdade, será bem maior e mais complexo, considero que será um projeto de médio-porte. Desta forma estou ansioso pela chegada deste livro. Apenas depois de lê-lo vou começar a produziar o código "tracer bullets", como descrito no livro The Pragmatic Programmer, que será um código para me guiar onde pretendo chegar, nada rígido, porém será o esqueleto que trará organização e flexibilidade para poder melhorar o código ao adquirir novos conhecimentos.
Neste meio tempo quero ler alguns livros sobre design de jogos que têm assuntos que me interesseram, sendo eles: 3D Game Engine Architecture, 3D Game Engine Design, 3D Game Engine Programming e 3D Game Programming All In One. Como dá pra perceber vou acabar escrevendo uma engine. Não é meu objetivo, porém grande parte dos seus módules serão "façades" e ou "fábricas" que utilizarão outras APIs, como a Assimp e a Bullet. Quero me concentar mesmo em como desenvolver a física do carro.
Se você gostou deste post, talvez ache interessante este também: http://cerdiogenes.blogspot.com/2010/11/comparacao-entre-metodos-para-evolucao.html. É um artigo que escrevi como trabalho final da minha pós-graduação e aborda o treinamento de carros de corrida utilizando aprendizado de máquina e inteligência computacional.
Se você quiser baixar o código que desenvolvi clique aqui! Para compilá-lo basta digitar "make" e depois "./sample cubo_simples.dae" para executar. Você vai precisar das bibliotecas de desenvolvimento da sua distribuição e das bibliotecas bullet, freealut, openal, vorbis e vorbisfile. Se não esqueci de nada é isso!
sexta-feira, 7 de janeiro de 2011
quarta-feira, 10 de novembro de 2010
Assimp, Blender, DevIL = Objeto renderizado com mapeamento UV usando OpenGL
Continuo buscando meios para fazer meu jogo. Já decidi algumas coisas, porém colocar todas as coisas juntas, escrever o código e fazer executar é a coisa mais difícil, ainda mais quando se está aprendendo todas as tecnologias necessárias para isso, no entanto é recompensador cada progresso feito.
Eu decidi que o Blender será meu editor, tendo em vista duas alternativas: escrever meu próprio editor ou utilizar um já disponível. Hoje em dia a quantidade de editores 3D é enorme, sendo que se eu fosse escrever um, até que este chegasse em um nível de maturidade aceitável levaria muito tempo, sendo assim optei por utilizar o Blender, que possui uma comunidade enorme a sua volta e é extensível através de plugins, permitindo customizá-los de forma a obter algumas das facilidade que é possível quando se usa um editor próprio, tal como verificar determinadas propriedades de uma cena criada.
O Blender oferece o suporte para realizar o unwrapping (abrir a malha) de um objeto, criando um mapa de textura UV. Nesta página você pode encontrar mais informações a respeito do que estou dizendo: http://www.blender.org/development/release-logs/blender-234/uv-unwrapping/. Este mapa pode ser exportado e editado em seu aplicativo de edição de imagens preferido.
No meu aprendizado eu fiz algo bem simples, abri a malha de um cubo e com o Gimp pintei cada face de uma cor. Após preparar esta textura voltei ao Blender e a apliquei ao cubo. Pronto, este era o objeto que eu queria renderizar em minha aplicação OpenGL, sendo assim exportei-a para o formato Collada 1.4 através do Blender.
Para carregá-la em minha aplicação utilizei a biblioteca Assimp. Apesar de muitos dizerem que escrever código para carregar um formato específico é bastante simples não queria me ater a esta tarefa, pois o principal motivo para fazê-lo é desempenho e eu por enquanto quero experimentar com as peças que podem me auxiliar e o mais rápido possível :).
Além da Assimp utilizei a bilbioteca DevIL que permite importar diversos formatos de arquivo para memória e serem utilizados como textura em uma aplicação OpenGL. Além disso a Assimp também importa todos os pontos do mapeamento UV, sendo possível dizer em quais locais determinados pontos da textura devem ser aplicados na malha do cubo.
Para fazer tudo isso rodar, ainda era necessário escrever o código para carregar o objeto e renderizá-lo na tela. Para facilitar a vida a biblioteca Assimp traz um código de exemplo: http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/samples/SimpleOpenGL/, porém este não utiliza texturas. Um outro desenvolvedor criou um novo exemplo com suporte a texturas: http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/samples/SimpleTexturedOpenGL/, porém apenas com suporte para compilar no Windows usando o Visual Studio, que eu não pretendo utilizar :). Eu fiz uma mistura dos dois alterando o primeiro exemplo e compilei no GNU/Linux, porém não vou publicar o código que escrevi, pois é algo simples e tenho certeza que você consegue fazer também :).
O resultado final? Olha aí que beleza!!!
Um passo de nada para a humanidade dos programadores de jogos, mas um grande passo para mim :P. Agora prentendo colocar dois cubos na minha tela, cada um girando para lados opostos, sendo que quero criar os dois cubos na mesma cena do Blender e tratá-los separadamente na aplicação (ou como um único objeto). Acredito que esta é uma forma interessante de representar objetos que durante a execução da aplicação possam ser quebrados.
Após isto quero adicionar suporte a princípios físico através da utilização da biblioteca Bullet. Após isto o céu é o infinito!!! Estarei bem armado para escrever meu jogo de Fórmula 1.
Eu decidi que o Blender será meu editor, tendo em vista duas alternativas: escrever meu próprio editor ou utilizar um já disponível. Hoje em dia a quantidade de editores 3D é enorme, sendo que se eu fosse escrever um, até que este chegasse em um nível de maturidade aceitável levaria muito tempo, sendo assim optei por utilizar o Blender, que possui uma comunidade enorme a sua volta e é extensível através de plugins, permitindo customizá-los de forma a obter algumas das facilidade que é possível quando se usa um editor próprio, tal como verificar determinadas propriedades de uma cena criada.
O Blender oferece o suporte para realizar o unwrapping (abrir a malha) de um objeto, criando um mapa de textura UV. Nesta página você pode encontrar mais informações a respeito do que estou dizendo: http://www.blender.org/development/release-logs/blender-234/uv-unwrapping/. Este mapa pode ser exportado e editado em seu aplicativo de edição de imagens preferido.
No meu aprendizado eu fiz algo bem simples, abri a malha de um cubo e com o Gimp pintei cada face de uma cor. Após preparar esta textura voltei ao Blender e a apliquei ao cubo. Pronto, este era o objeto que eu queria renderizar em minha aplicação OpenGL, sendo assim exportei-a para o formato Collada 1.4 através do Blender.
Para carregá-la em minha aplicação utilizei a biblioteca Assimp. Apesar de muitos dizerem que escrever código para carregar um formato específico é bastante simples não queria me ater a esta tarefa, pois o principal motivo para fazê-lo é desempenho e eu por enquanto quero experimentar com as peças que podem me auxiliar e o mais rápido possível :).
Além da Assimp utilizei a bilbioteca DevIL que permite importar diversos formatos de arquivo para memória e serem utilizados como textura em uma aplicação OpenGL. Além disso a Assimp também importa todos os pontos do mapeamento UV, sendo possível dizer em quais locais determinados pontos da textura devem ser aplicados na malha do cubo.
Para fazer tudo isso rodar, ainda era necessário escrever o código para carregar o objeto e renderizá-lo na tela. Para facilitar a vida a biblioteca Assimp traz um código de exemplo: http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/samples/SimpleOpenGL/, porém este não utiliza texturas. Um outro desenvolvedor criou um novo exemplo com suporte a texturas: http://assimp.svn.sourceforge.net/viewvc/assimp/trunk/samples/SimpleTexturedOpenGL/, porém apenas com suporte para compilar no Windows usando o Visual Studio, que eu não pretendo utilizar :). Eu fiz uma mistura dos dois alterando o primeiro exemplo e compilei no GNU/Linux, porém não vou publicar o código que escrevi, pois é algo simples e tenho certeza que você consegue fazer também :).
O resultado final? Olha aí que beleza!!!
Um passo de nada para a humanidade dos programadores de jogos, mas um grande passo para mim :P. Agora prentendo colocar dois cubos na minha tela, cada um girando para lados opostos, sendo que quero criar os dois cubos na mesma cena do Blender e tratá-los separadamente na aplicação (ou como um único objeto). Acredito que esta é uma forma interessante de representar objetos que durante a execução da aplicação possam ser quebrados.Após isto quero adicionar suporte a princípios físico através da utilização da biblioteca Bullet. Após isto o céu é o infinito!!! Estarei bem armado para escrever meu jogo de Fórmula 1.
sábado, 6 de novembro de 2010
Comparação Entre Métodos para Evolução de Controles de Carros de Corrida em Jogos Computacionais
Acabei meu artigo sobre treinamento de carros de corrida para jogos computacionais, onde compara diversas técnicas diferentes para a evolução dos controladores. Desenvolvi este trabalho como requisito para formação do curso de pós-graduação que estou cursando.
Neste trabalho utilizei quatro métodos, NEAT, GA, lógica fuzzy e aprendizado por reforço para treinar um carro de corrida no simulador TORCS.
Os resultados ficaram aquém das minhas expectativas, mas aprendi bastante com ele. Foi uma viagem interessante e se não cheguei no destino que queria, pelo menos agora tenho um entedimento melhor do mapa que pode me levar até lá.
Publiquei o artigo e o código fonte no meu site: https://sites.google.com/site/cerdiogenes/ia
Neste trabalho utilizei quatro métodos, NEAT, GA, lógica fuzzy e aprendizado por reforço para treinar um carro de corrida no simulador TORCS.
Os resultados ficaram aquém das minhas expectativas, mas aprendi bastante com ele. Foi uma viagem interessante e se não cheguei no destino que queria, pelo menos agora tenho um entedimento melhor do mapa que pode me levar até lá.
Publiquei o artigo e o código fonte no meu site: https://sites.google.com/site/cerdiogenes/ia
segunda-feira, 16 de agosto de 2010
Eclipse não detecta que JBoss inicializou
No Eclipse, ao clicar duas vezes no servidor de aplicação para visualizar suas configurações é possível alterar diversas configurações, porém estas configurações não são as configurações que o JBoss irá utilizar, elas indicam quais as configurações do servidor de aplicação.
No Eclipse, mudei a porta do JBoss para 8088 e ao inicializar o servidor o Eclipse não detectava que ele já havia inicializado e apontavo o erro "Server JBoss v4.2 at localhost was unable to start within 50 seconds. If the server requires more time, try increasing the timeout in the server editor."
Para alterar a porta do JBoss é necessário modificar o arquivo %JBOSS_HOME%/server/default/deploy/jboss-web.deployer/server.xml na linha
O Eclipse bem que podia ser mais esperto e dizer que esta configuração não representa a porta na qual o JBoss vai levantar, mas isto é algo que deve ser solicitado aos desenvolvedores do Eclipse.
No Eclipse, mudei a porta do JBoss para 8088 e ao inicializar o servidor o Eclipse não detectava que ele já havia inicializado e apontavo o erro "Server JBoss v4.2 at localhost was unable to start within 50 seconds. If the server requires more time, try increasing the timeout in the server editor."
Para alterar a porta do JBoss é necessário modificar o arquivo %JBOSS_HOME%/server/default/deploy/jboss-web.deployer/server.xml na linha
O Eclipse bem que podia ser mais esperto e dizer que esta configuração não representa a porta na qual o JBoss vai levantar, mas isto é algo que deve ser solicitado aos desenvolvedores do Eclipse.
segunda-feira, 26 de julho de 2010
Upload de arquivos no JSF2 no escopo de conversação
Há algum tempo vinha procurando uma solução para fazer upload de arquivo no JSF2 no escopo de conversação.
Existem vários sites que mostram como fazer upload de arquivo no JSF{1,2}, porém nenhum deles leva em consideração o escopo de conversação: http://forums.sun.com/thread.jspa?threadID=5399669, http://ironicprogrammer.blogspot.com/2010/03/file-upload-in-jsf2.html, http://dominikdorn.com/?p=169, http://balusc.blogspot.com/2009/12/uploading-files-with-jsf-20-and-servlet.html
Além destes sites também vi uma solução no livro Core Java Server Faces 3ª edição, no entanto nesta solução o mesmo problema existia. Ao submeter um arquivo, enquanto no escopo de conversação, o escopo era simplesmente destruído.
Entrei em contato com os diversos autores para verificar se eles me davam uma luz, pois ainda sou bastante cru para mexer com filtros em JSF. Algum tempo depois recebi a resposta do autor do Core Java Server Faces, o Cay Horstmann, que me sugeriu olhar como o Seam resolvia o problema, pois esta foi a tecnologia pioneira do escopo de conversação.
Ele me deu esse conselho no dia 30 de Junho, mas apenas hoje consegui finalizar a implementação.
Primeiro olhei o código do RichFaces, pois ele que contém o componente de file upload e então percebi que ele não implementava o filtro, que é o componente que analisa a requisição e processa os cabeçalhos para extrair o arquivo e os outros elementos da requisição. Descobri que quem fazia isso era o próprio Seam.
Coloquei no liquidificador a implementação do Seam e do Cay Horstmann e o sabor ficou muito bom, um componente de upload de arquivos simples e que funciona no escopo de conversação. Pefeito!
Porém não me peça para explicar porque o filtro do Seam funciona para salvar a conversação, ainda não consegui entender, mas se você souber, ficaria muito satisfeito em ouvir.
O projeto do NetBeans que criei pode ser baixado através do seguinte link: http://sites.google.com/site/cerdiogenes/java/jsf2/fileupload.zip?attredirects=0
Nele não existe nenhum tipo de validação, mas pretendo começar a trabalhar nisso em breve, pois vou precisar integrá-lo em um sistema e os requisitos se tornam mais específicos. Que venham os problemas então! E que sejam fáceis de resolver :D
Existem vários sites que mostram como fazer upload de arquivo no JSF{1,2}, porém nenhum deles leva em consideração o escopo de conversação: http://forums.sun.com/thread.jspa?threadID=5399669, http://ironicprogrammer.blogspot.com/2010/03/file-upload-in-jsf2.html, http://dominikdorn.com/?p=169, http://balusc.blogspot.com/2009/12/uploading-files-with-jsf-20-and-servlet.html
Além destes sites também vi uma solução no livro Core Java Server Faces 3ª edição, no entanto nesta solução o mesmo problema existia. Ao submeter um arquivo, enquanto no escopo de conversação, o escopo era simplesmente destruído.
Entrei em contato com os diversos autores para verificar se eles me davam uma luz, pois ainda sou bastante cru para mexer com filtros em JSF. Algum tempo depois recebi a resposta do autor do Core Java Server Faces, o Cay Horstmann, que me sugeriu olhar como o Seam resolvia o problema, pois esta foi a tecnologia pioneira do escopo de conversação.
Ele me deu esse conselho no dia 30 de Junho, mas apenas hoje consegui finalizar a implementação.
Primeiro olhei o código do RichFaces, pois ele que contém o componente de file upload e então percebi que ele não implementava o filtro, que é o componente que analisa a requisição e processa os cabeçalhos para extrair o arquivo e os outros elementos da requisição. Descobri que quem fazia isso era o próprio Seam.
Coloquei no liquidificador a implementação do Seam e do Cay Horstmann e o sabor ficou muito bom, um componente de upload de arquivos simples e que funciona no escopo de conversação. Pefeito!
Porém não me peça para explicar porque o filtro do Seam funciona para salvar a conversação, ainda não consegui entender, mas se você souber, ficaria muito satisfeito em ouvir.
O projeto do NetBeans que criei pode ser baixado através do seguinte link: http://sites.google.com/site/cerdiogenes/java/jsf2/fileupload.zip?attredirects=0
Nele não existe nenhum tipo de validação, mas pretendo começar a trabalhar nisso em breve, pois vou precisar integrá-lo em um sistema e os requisitos se tornam mais específicos. Que venham os problemas então! E que sejam fáceis de resolver :D
sexta-feira, 2 de julho de 2010
Enviar comandos de teclado a uma aplicação através do terminal
Estou tentando treinar um controlador para aprender a dirigir em uma pista através de aprendizado não supervisionado. Estou rodando meu experimento no simulador TORCS, o que envolve reiniciar a corrida muitas vezes. O TORCS tem um bug que depois de re-iniciada a corrida centenas de vezes acaba gerando um segmentation fault. Ou seja, é um bug bastante chato de ser reproduzido e consequentemente bastante difícil de depurar e corrigir.
Para contornar este problema e ganhar tempo de simulação resolvi procurar uma solução para verificar se o TORCS estava rodando, em caso negativo (aconteceu o segfault) eu o reiniciaria, porém teria que mandar 4 enters para iniciar a simulação.
Após uma googleada encontrei a solução rapidamente. Utilizando o comando xsendkeycode é possível enviar sinais de teclados para a aplicação que está com foco. Com o comando ps e grep consegui verificar se o programa estava rodando.
No link http://www.anyexample.com/linux_bsd/bash/check_if_program_is_running_with_bash_shell_script.xml encontrei o script que verifica se um programa está rodando e nos links http://linux.die.net/man/8/xsendkeycode, http://www.linuxforums.org/forum/linux-programming-scripting/136576-simulating-pressed-keys-shell-script-3.html achei as peças para enviar os comandos enter.
No final criei o seguinte script:
E chamei-o com a seguinte linha para ficar em um loop infinito:
while [ 1 ]; do ./run_game.sh; done
Para contornar este problema e ganhar tempo de simulação resolvi procurar uma solução para verificar se o TORCS estava rodando, em caso negativo (aconteceu o segfault) eu o reiniciaria, porém teria que mandar 4 enters para iniciar a simulação.
Após uma googleada encontrei a solução rapidamente. Utilizando o comando xsendkeycode é possível enviar sinais de teclados para a aplicação que está com foco. Com o comando ps e grep consegui verificar se o programa estava rodando.
No link http://www.anyexample.com/linux_bsd/bash/check_if_program_is_running_with_bash_shell_script.xml encontrei o script que verifica se um programa está rodando e nos links http://linux.die.net/man/8/xsendkeycode, http://www.linuxforums.org/forum/linux-programming-scripting/136576-simulating-pressed-keys-shell-script-3.html achei as peças para enviar os comandos enter.
No final criei o seguinte script:
#!/bin/bash
GAME='torcs'
OUT=$(ps ax | grep -v grep | grep $GAME)
if [ "x$OUT" != "x" ]
then
sleep 30
else
$GAME & sleep 2; xsendkeycode 36 1; xsendkeycode 36 0; xsendkeycode 36 1; xsendkeycode 36 0; xsendkeycode 36 1; xsendkeycode 36 0; xsendkeycode 36 1; xsendkeycode 36 0;
fi
E chamei-o com a seguinte linha para ficar em um loop infinito:
while [ 1 ]; do ./run_game.sh; done
segunda-feira, 31 de maio de 2010
Configurar Repositório Externo no Subversion
Quando estamos desenvolvendo alguns projetos, as vezes precisamos buscar código-fonte de repositórios externos. Isso é interessante até mesmo no desenvolvimento in house, pois as vezes utilizamos bibliotecas em diferentes projetos. Desta forma conseguimos manter uma base de software para diferentes aplicativos e atualizá-las de forma fácil.
Para configurar os repositórios externos utiliza-se os comandos:
O comando propset server para setar uma propriedade e sua syntaxe é:
Após está mudança, basta fazer um commit e um update das alterações para buscar o repositório externo. Caso você queira criar mais de um repositório externo adicione todas as referência externas em um arquivo da seguinte forma:
Isso é realmente muito fácil, mas a documentação do SVN deixa bastante a desejar em relação a um recurso tão útil.
Para configurar os repositórios externos utiliza-se os comandos:
svn propgetO comando propget serve para você solicitar uma propriedade configurada em um diretório. Um exemplo de sua utilização é:
svn propset
svn propget svn:externals .O ponto do final se refere ao diretório corrente, caso queira pegar a propriedade de outro diretório basta passar um caminho no lugar do ponto.
O comando propset server para setar uma propriedade e sua syntaxe é:
svn propset svn:externals 'dir_local http://endereco.svn.remoto/nome/do/diretorio' .Repare o ponto no final. Este comando diz que no diretório corrente (o ponto) será criado um diretório com o nome dir_local e seu conteúdo será buscado do repositório http://endereco.svn.remoto/nome/do/diretorio.
Após está mudança, basta fazer um commit e um update das alterações para buscar o repositório externo. Caso você queira criar mais de um repositório externo adicione todas as referência externas em um arquivo da seguinte forma:
dir_local1 http://endereco.svn.remoto/nome/do/diretorio1E ao utilizar o comando svn propset faça o seguinte:
dir_local2 http://endereco.svn.remoto/nome/do/diretorio2
dir_local3 http://endereco.svn.remoto/nome/do/diretorio3
svn propset svn:externals -F nome_do_arquivo .Não esqueça depois de setar a propriedade de realizar um commit.
Isso é realmente muito fácil, mas a documentação do SVN deixa bastante a desejar em relação a um recurso tão útil.
Assinar:
Postagens (Atom)
