sexta-feira, 25 de fevereiro de 2011

Regular Expressions Tester - Regex Tester

Saindo um pouco do tema jogos, resolvi fazer um programinha a noite, no tempo livre, para me ajudar a criar e testar Regular Expressions, ou Regex (Se não sabe o que é, esse site é uma maravilha)

Seu funcionamento é simples. Ele possui três campos, o primeiro você escreve sua regex, no segundo, algum texto para testá-la, e no último, apareçem os resultados.
Existe uma pequena mensagem de status, um botão para salvar os resultados (incluindo a regex claro) e ainda um seletor de linguagem (ele inicia no inglês, mas também tem em português).

Pra quem se interessar, o link para baixar o programa tá ai embaixo, é de graça e pode usar como você quiser, mas uma menção ou um link para o blog seriam bastante apreciados! ^^

DotNetRegexTester1.0.zip
63.16 KB


Todas as instruções de uso e outros detalhes estão inclusos no programa e no arquivo leia-me. (sim, tenha a certeza de ler)

Abraços e bom proveito! ^^

quinta-feira, 24 de fevereiro de 2011

Managers em Jogos

Acho que todo mundo que já programou algum jogo, por mais simples que seja, notou que ele pode ficar com o código bem confuso muito rápido. Aquele seu método Update principal fica com 200, 300, 600 linhas de código onde você processa entrada, atualiza física, mensagens, o jogados, inimigos, sons... Ai você percebe que tem que organizar a coisa ou vai enlouquecer, e perde mais tempo juntando pequenas coisinhas do que juntando teu jogo como um todo.

Ai surgem os Managers, ou gerenciadores: classes que são responsáveis por tudo o que envolva seu nome. Err... Nada melhor que um exemplo:

class InputManager
{
   void Update()
   {
      //Atualiza os estados de teclado e mouse.
   }

   bool IsKeyPressed(Key)
   {
      //Testa se uma determinada tecla foi pressionada.
   }

   event MouseClicked; //Dispara um evento sempre que o mouse for clicado.
}

O exemplo anterior trata de um gerenciador de entradas, basicamente mouse e teclado, ou controles, ou qualquer outra coisa, e expõe dois métodos e um evento. Isso é só um exemplo, um gerenciador de verdade exporá bem mais métodos e eventos que isso.
Note as vantagens desse tipo de abordagem:
1- Fácil expansão: Colocar um joystick ali no meio é tão fácil quanto adicionar um ou dois métodos bem simples.
2- Fácil manutenção: Se algo bugar, você sabe exatamente em qual gerenciador olhar, e até mesmo, em qual método o problema pode estar, muitas vezes, sem nem precisar utilizar o debugger.
3- Leitura tranquila: Seu update principal vai ficar enxuto, com várias chamadas para updates do gerenciador de entrada, de física, de som, de jogador, de inimigos, de tiros, etc. (Se todos os gerenciadores derivarem de uma classe base com um método virtual update fica ainda melhor.)

Mas como tudo na vida, esses gerenciadores devem ser usados com parcimônia e bom senso, ou eles vão entupir teu código e podem até piorar a situação geral. Imagine que você vai construir um jogo de tiro, poderá ficar tentado a pensar: "Cada tiro terá seu próprio gerenciador, assim só preciso chamar update neles sem muita preocupação." Errado!

Sua performance vai para o buraco se pensar dessa forma... Imagine 100 tiros voando numa guerra, cada um deles com seu próprio gerenciador, ocupando um monte de memória e fazendo o computador sofrer com um monte de chamados de métodos.

Nessa situação, assim como também os inimigos, sons e qualquer outra coisa que tenha o potencial de vir em quantidade, a melhor opção é criar um único gerenciador para cada categoria. Assim esse único gerenciador ficará responsável por atualizar posições de todos os seus "filhos", fazê-los pensar, gritar ou qualquer coisa que eles devam fazer.

No caso do tiro, sempre que você quiser que um tiro apareça na tela, ao invés de criá-lo você mesmo, peça ao seu gerenciador de tiro que adicione um naquela posição, naquela direção e deixe que ele cuide do resto. Ele só precisará avançar sua posição e verificar se ele bateu em alguma coisa para destruí-lo (e possivelmente disparar um evento para dizer pra quem interessar que o tiro foi destruído, onde e em que bateu e assim por diante.

Isso ainda pode diminuir o acoplamento de classes; o jogador que antes precisaria de apegar à entrada, armas, sons, tiros e física, agora só precisa da entrada e dos tiros, o restante só precisam se "engachar" aos eventos corretos para tomarem suas devidas ações.

Espero que tenham gostado, qualquer coisa comentem ai e podemos elaborar mais a ideia.

Abraços!

domingo, 13 de fevereiro de 2011

Finite State Machine - Como eu fiz

Programadores já sabem disso: Um computador executa uma instrução atrás da outra, e se não houver mais instruções, o programa é simplesmente encerrado. Mas para um jogo isso não é interessante, já que ele precisa rodar por quanto tempo o usuário quiser (assim como a maioria das outras aplicações, mas vamos focar nos jogos), então a solução é criar um loop que só sai quando o usuário mandar, algo como:

while ( true )
{
   Atualizar();
   if ( usuario.mandou.sair )
      break;
   Desenhar();
}

pronto, isso pode dar conta em um jogo muito simples, onde o usuário já entra no jogo, talvez passando por uma tela qe aceita qualquer entrada para ir para o jogo em si, mas quando a coisa começa a crescer, ou seja, você quer adicionar uma tela de opções, salvar e carregar jogos, pontuação persistente, telas de game over e game win e por ai vai, seu loop vai pareçer com isso:

while ( true )
{
   if ( telaDeOpcoes )
   {
      AtualizarOpcoes();
      DesenharOpcoes();
   }
   else if ( SalvarJogo )
   {
      SalvaJogoAtual();
   }
   else if ( GameOver )
   {
      MostraTelaGameOver();
   }
   else
   {
      Atualizar();
      if ( usuario.mandou.sair )
         break;
      Desenhar();
   }
}

Nossa, eu nem compliquei muito e já ficou complicado de manter esse código. Poderiamos usar inteiros, booleanos, enumerações e até classes mais simples parar representar os estados que ainda assim, seria complicado manter o código.

É ai que entra a Finite State Machine, ou Máquina de Estados Finitos. O que é isso?
É uma máquina com uma quantidade finita de estados! =D
Falando sério agora, vamos começar pela definição de estado. Imagine você, de mal humor e seu professor lhe apresenta uma prova surpresa cujo assunto você não sabe, sua reação: "QUE DROGA!". Agora se você estivesse de bom humor e soubesse do assunto: "Ainda bem que sei esse assunto...". Mal ou bom humor, raiva, tristeza, felicidade, etc são estados, são eles que vão determinar como agir em cada situação.

Voltando para os jogos, se o jogador, no meio da jogatina, clica na opção "Salvar Jogo", seu jogo deve mudar o estado atual de "jogando" para "salvar" e talvez apresentar um menu onde o usuário pode escolher em qual slot quer que seja salvo seu progresso. Uma Finite State Machine, ou FSM como será mencionada a partir de agora, gerencia essas mudanças de estado que seu jogo sofre, então o seu loop principal ficaria mais ou menos assim:

while ( true )
{
   FiniteStateMachinhe.Atualizar();
   Desenhar();
}

Muito mais simples, não é? Mas para onde foram todas aquelas linhas de código de antes? Elas foram para dentro dos seus respectivos estados. A FSM determina qual estado o jogo esta atualmente, chamando os métodos "atualizar" e "desenhar" do estado (ou de mais de um, dependedo da sua implemantação) e fazendo mudanças caso lhe seja solicitado! Isso que dizer que cada estado é independente do outro e pode pedir para ser retirado, colocar outro por cima dele ou mudar completamente de estado.

O jeito mais simples de fazer isso foi utilizando um stack, ou pilha, e nada melhor que um exemplo:

Eu começei a jogar XYT e apareçeu a introdução. Rapidamente eu aperto uma tecla para ir ao menu principal (mudança de estado). Noto que o som está muito alto e resolvo abaixar, clico em opções (colocação de estado), abaixo o volume e volto ao menu principal (retirada de estado) e só então entro no jogo (colocação de estado), mas depois de um tempo, perco completamente... Game over (mudança de estado) e logo após, retorno ao menu (mudança de estado).

Todas essas mudanças, colocações e retiradas são o motivo de usar uma stack, onde os pushes e pops fazem exatamente esse trabalho. Quando quero mudar, removo todos os estado e empurro o que eu quero mudar, como no caso da introdução para o menu, o usuário não vai voltar para a intordução. Empurro um estado quando eu sei que o usuário vai sair dele e voltar para o anterior, como no caso do menu de opções e finalmente retiro um estado quando ele não faz mais sentido, como quando o usuário perde (jogar sem vidas não faz sentido).

Cada estado terá seu próprio método Atualizar() assim como Desenhar(), se for o caso, e poderá solicitar ao FSM qualquer mudança que julgar necessária. Isso simplifica tremendamente o trabalho do programador, pois ele pode criar, remover ou modificar completamente estados sem muitas mudanças no código base.

Novamente não apresentarei código fonte ou tutoriais, mas se a demanda for suficiente (comentem!) então eu posso fazer!

Até a próxima, falarei dos Managers!

Abraços!

sexta-feira, 11 de fevereiro de 2011

Eu começei a programar jogos a alguns anos, mas nunca fiz nada que merecesse ser lançado, já que fazia mais para aprender técnicas e métodos em geral, mas agora eu resolvi fazer um jogo completo, simples, de nave, igual a muitos outros, mas completo de qualquer forma.

Enquanto fazia esse jogo, queria um método rápido para detectar colisões, foi ai que me deparei com o SAT, ou Separating Axis Algorithm. Eu não vou me ater a como ele funciona em detalhes (aqui tem uma explicação muito boa, em inglês), vou apenas falar por cima e mostrar como eu implementei esse algoritimo e estou bem satisfeito com os resultados (Talvez se houver pedidos suficientes, eu faça uma explicação completa e tutorial, inclusive incluíndo código fonte completo da coisa).

O SAT projeta os poligonos em determinados eixos normais aos lados de cada objeto e testa a distância entre eles determinando se eles se tocam ou não. Se em todos os eixos eles se tocaram, então é garantido que os objetos se tocam e é possível determinar o eixo e a quantidade de penetração. Mas tudo isso só funciona se os polígonos forem convexos! (Caso não sejam, divida-os em diversos convexos menores)
Esse algoritimo é do tipo "early-out", ou seja, ele só roda por completo se realmente existir uma colisão. Assim que detecta um espaço entre os objetos testados, ele já garante que não ocorre colisão. Mas, nem tudo é perfeito, e se você for ligado em geometria, deve ter percebido que esse algoritimo só funciona em polígonos regulares, inclusive círculos.

Bem, chega de lero-lero, vamos ao (pseudo)código, nesse caso, C# e XNA. \o/

O primeiro passo é criar uma classe que possa representar um polígono qualquer. Ou seja, uma posição (x, y), um ângulo de rotação, uma escala, um centro, e claro, os vértices:

class Polygon
{
    int x, y;
    int rotation;
    float scaleX;
    float scaleY;
    Matrix transform;
    List<Vector2> transformedVertices = new List<Vector2>();
    List<Vector2> rawVertices = new List<Vector2>();
    Vector2 center;
}

Todos os campos devem ser auto-explicativos, com exceção talvez das duas listas de vértices. 'transformedVertices' retorna a lista com vértices rotacionados e com escala apropriada, enquanto 'rawVertices' retorna os vértices brutos, sem transformações. O método get de 'transformedVertices' foi implantado dessa forma:

public List<Vector2> TransformedVertices
{
    get
    {
        transformedVertices.Clear();
        for ( int i = 0; i < rawVertices.Count; i++ )
        {
            Vector2 temp = Vector2.Transform(rawVertices[i], transform);
            temp.X += x;
            temp.Y += y;
            transformedVertices.Add(temp);
        }
        return transformedVertices;
    }
}

Primeiro descartamos todos os vértices (aqui poderia ter uma otimização, para não recriar a lista todas as vezes, mas deixarei isso como exercício) e então para cada vértice bruto, aplicamos a matriz de transformação, somamos com a posição do polígono e fazendo isso cada vértice, temos a lista de vértices transformados prontos para colisão. Para atualizar a matriz de transformação, criamos este métodos:

private void updateTransformation()
{
    transform = Matrix.Identity;
    transform = Matrix.CreateScale(scaleX, scaleY, 0) *
        Matrix.CreateRotationZ(rotation);
}

Simplesmente criamos uma escala e depois rotacionamos em torno do eixo Z. (Porque o eixo Z se estamos trabalhando em 2D? Quem será que responde? =D). Lembre-se que a escala 1 não altera o objeto, 0 o objeto não vai apareçer e acima de 1, bem, você já deve saber...

Tudo o que falta agora é a criação do polígono em si:

public static Polygon BasicPolygon(int sides = 3, int radius = 100, float creationAngle = 0f)
{
    if ( sides < 3 ) throw new ArgumentException("Polygon - Needs at least 3 sides");
    float sumX, sumY;
    sumX = sumY = 0f;
    Polygon poly = new Polygon();
    float rotangle = (float)MathHelper.TwoPi / sides;
    float angle;
    Vector2 pt;
    for ( int i = 0; i < sides; i++ )
    {
        angle = ( i * rotangle ) + ( ( (float)Math.PI - rotangle ) * 0.5f );
        pt = new Vector2();
        pt.X = (float)Math.Cos(angle) * radius;
        pt.Y = (float)Math.Sin(angle) * radius;
        pt = Vector2.Transform(pt, Matrix.CreateRotationZ(creationAngle));
        sumX += pt.X;
        sumY += pt.Y;
        poly.AddVertexAtEnd(pt);
    }
    poly.RadiansRotation = 0;
    poly.ScaleX = poly.ScaleY = 1;
    poly.center = new Vector2(sumX / sides, sumY / sides);
    return poly;
}

Tudo o que esse método faz é criar um polígino regular inscrito na circunferência de raio especificado, o centro do polígino também é calculado utilizando a média de todos os vértices. Estas coordenadas são locais, e não coordenadas de tela. Você pode criar outros métodos para retornar políginos não regulares ou para utilizar listas de pontos.

A última coisa a fazer antes de criar o SAT em si, é uma pequena classe para representar nossas informações de colisão:

public class CollisionInfo
{
    public float OverlapAmount;
    public Vector2 SmallestSeparationAxis;
    public Vector2 NormalAxis;

    public CollisionInfo()
    {
        OverlapAmount = 0;
        SmallestSeparationAxis = new Vector2();
        NormalAxis = new Vector2();
    }
}

Essa classe deve ser auto-explicativa, mas mesmo assim, vamos lá: OverlapAmount indica quanto nossos polígonos penetraram um no outro enquanto SmallestSeparationAxis é o eixo onde ocorreu a menor penetração entre os polígonos, ou seja, para remover os objetos da colisão, basta mover um deles desse modo:

novaPosicao = info.OverlapAmount * info.SmallestSeparationAxis;

Ou da forma que lhe for conveniente.

E agora sim, podemos criar nosso método SAT! \o/

private static CollisionInfo SATCheckPolygons(Polygon polygonA, Polygon polygonB)
{
    float minA, maxA;
    float minB, maxB;
    Vector2[] AxesForA = new Vector2[polygonA.Sides];
    Vector2[] AxesForB = new Vector2[polygonB.Sides];
    List<Vector2> verticesA;
    List<Vector2> verticesB;

    float overlap = float.MaxValue;

    CollisionInfo result = new CollisionInfo();
    result.IsShapeAOverlappedInB = true;
    result.IsShapeBOverlappedInA = true;

    verticesA = polygonA.TransformedVertices;
    verticesB = polygonB.TransformedVertices;

    //Pega todos os eixos dos dois polígonos.
    for ( int i = 0; i < AxesForA.Length; i++ )
    {
        Vector2 vertex1 = verticesA[i];
        Vector2 vertex2 = verticesA[i + 1 == polygonA.Sides ? 0 : i + 1];
        Vector2 edge = vertex1 - vertex2;
        AxesForA[i] = new Vector2(-edge.Y, edge.X);
        AxesForA[i].Normalize();
    }
    for ( int i = 0; i < AxesForB.Length; i++ )
    {
        Vector2 vertex1 = verticesB[i];
        Vector2 vertex2 = verticesB[i + 1 == polygonB.Sides ? 0 : i + 1];
        Vector2 edge = vertex1 - vertex2;
        AxesForB[i] = new Vector2(-edge.Y, edge.X);
        AxesForB[i].Normalize();
    }
    
    /Projeta os dois polígonos em cada eixo e checa o resultado.
    for ( int i = 0; i < AxesForA.Length; i++ )
    {
        projectPolygonToAxis(polygonA, AxesForA[i], out minA, out maxA);
        projectPolygonToAxis(polygonB, AxesForA[i], out minB, out maxB);

        if ( ( minA > maxB || minB > maxA ) ) return null; //Foi encontrado um "buraco"
        else // Formar vetor de retorno de colisão
        {
            float o = Math.Min(maxA, maxB) - Math.Max(minA, minB);
            if ( o < overlap )
            {
                overlap = o;
                result.OverlapAmount = o;
                result.SmallestSeparationAxis = AxesForA[i];
                //Projeta a distância de A para B no eixo, se estiver na esquerda, não fazer nada já que usamos normais esquerdas, caso contrário, nega o eixo.
                Vector2 v = polygonB.Center - polygonA.Center;
                if ( Vector2.Dot(v, AxesForA[i]) > 0 )
                    result.SmallestSeparationAxis *= -1;
            }
        }
    }
    for ( int i = 0; i < AxesForB.Length; i++ )
    {
        projectPolygonToAxis(polygonA, AxesForB[i], out minA, out maxA);
        projectPolygonToAxis(polygonB, AxesForB[i], out minB, out maxB);

        if ( ( minA > maxB || minB > maxA ) ) return null; //Foi encontrado um "buraco"
        else // Formar vetor de retorno de colisão
        {
            float o = Math.Min(maxA, maxB) - Math.Max(minA, minB);
            if ( o < overlap )
            {
                overlap = o;
                result.OverlapAmount = o;
                result.SmallestSeparationAxis = AxesForB[i];
                Vector2 v = polygonB.Center - polygonA.Center;
                if ( Vector2.Dot(v, AxesForB[i]) > 0 )
                    result.SmallestSeparationAxis *= -1;
            }
        }
    }
    // Se chegou aqui, podemos garantir que houve colisão
    return result;
}

Pronto! Basta jogar dois polígonos nesse método e ele vai retornar null caso não haja colisão ou um objeto CollisionInfo com as informações da colisão. Lembre-se que o SAT só suporta polígono convexos.

Depois farei um post semelhante detalhando como utilizar esse método com circunferências, tanto com outra cincunferência quanto com um polígono comum.

Abraços e espero que tenham gostado! Qualquer coisa grita ai em baixo! \o/