Arquivos

Posts Etiquetados ‘Colisão’

[XNA] TileMap

Antes de começarmos a programar tilemaps vamos ver algumas definições.

Tiles:

Tiles são pequenos pedaços de imagens que servem para criar uma nova imagem composta conhecida como layer, utilizadas para criar o cenário de um jogo. Essa técnica é chamada de TileMaps, no qual, através de uma imagem contendo diversos pedacinhos de imagens (tiles), se pode criar um cenário para um jogo. Exemplos clássicos do uso de TileMaps, são em jogos de RPG (Role Playing Game). Os jogos de RPG geralmente possuem cenários extensos o qual se fosse feito com uma única imagem ocuparia muito espaço de memória e naquela época em que surgiram, as memórias eram bem limitadas. Então a técnica de dividir o cenário em tiles e recriá-lo em tempo de processamento fez essa técnica ser muito utilizada até hoje em dia.

Como esta técnica funciona?

A primeira etapa é definir os tiles, ou seja, seu tamanho e suas imagens dentro dele.

A segunda parte é montar o Tilemap (também conhecido como bricks), ou seja, definir a estrutura que vai descrever aquele cenário do nosso jogo.

E por fim em tempo de execução nosso algoritmo deve substituir os números da matriz de tilemap pelas posições correspondentes do nosso tile. Esse cenário é conhecido como layer.

Projeto:

Então para fixarmos a parte teórica vamos implementar um mapa básico para um jogo do Pacman, inicie um novo projeto e coloque o nome de Pacman.

Criado o projeto vamos então criar uma classe com o nome Tile.cs que servirá para definirmos as propriedades dos tiles do jogo. Veja como vai ficar a classe.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
namespace TileMap
{
    /// <summary>
    /// Controla o estado entre os tiles para ver se existe ou não colisão
    /// </summary>
    public enum TileCollision
    {
        /// <summary>
        /// Define que o tile á passavel
        /// </summary>
        Passable,
        /// <summary>
        /// Define que o tile é um sólido impassavel, ou seja,
        /// existe colisão caso tentem passar por este tile.
        /// </summary>
        Impassable
    };

    /// <summary>
    /// Definição da classe tile
    /// </summary>
    public class Tile
    {
        public Texture2D texture;
        public TileCollision collision;
        public readonly Vector2 size;
        /// <summary>
        /// Construtor de um novo tile
        /// </summary>
        public Tile(Texture2D texture, TileCollision collision)
        {
            this.texture = texture;
            this.collision = collision;
            this.size = new Vector2(this.texture.Width, this.texture.Height);
        }
    }
}

Essa classe define a imagem do tile, seu tamanho que é conforme o tamanho da imagem e se existe ou não colisão.

Agora clique com o botão direito e faça download das imagens abaixo.

Tiles:

block00.png

block01.png

block02.png

block03.png

Sprite:

ghost.png

Logo após fazer o download das imagens as carregue em seu projeto conforme o Solution Explorer abaixo.


Agora na nossa classe Game1.cs vamos carregar nossas imagens, mas para isso vamos criar uma lista da nossa classe Tile e um Texture2D para carregar a imagem do fantasmas (personagem do nosso jogo). Adicione então as seguintes variáveis:

// Texture a ser utilizada para o personagem do tutorial
Texture2D character;
// Lista de tiles para montar o cenário
List<Tile> tiles;

E então no método LoadContent vamos carregar as imagens.

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    // Criando a lista de tiles
    tiles = new List<Tile>();
    // Adicionando os tiles a lista
    tiles.Add(new Tile(Content.Load<Texture2D>(@"Tiles\block00"), TileCollision.Passable));
    tiles.Add(new Tile(Content.Load<Texture2D>(@"Tiles\block01"), TileCollision.Passable));
    tiles.Add(new Tile(Content.Load<Texture2D>(@"Tiles\block02"), TileCollision.Passable));
    tiles.Add(new Tile(Content.Load<Texture2D>(@"Tiles\block03"), TileCollision.Impassable));
    // Carrega a textura do personagem
    character = Content.Load<Texture2D>(@"Sprites\ghost");
}

Com nossas imagens carregadas precisamos agora criar a estrutura do nosso mapa. E nosso mapa vai ser da seguinte forma:

Propriedades do mapa:

  • Matriz de números inteiros de 28 x 25
  • Valor0 para block00
  • Valor1 para block01
  • Valor2 para block02
  • Valor3 para block03 e somente este bloco é impassível.

Abaixo então de onde colocamos nossas variáveis declare nosso mapa conforme o código abaixo.

/// <summary>

/// Este é o brick ou seja nossa estrutura do mapa para nosso jogo
/// Os números serão substituidos pelos tiles adcionados a nossa lista
/// </summary>
int[,] map = {
{3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3},
{3,2,2,2,2,2,2,2,2,2,2,2,2,3,3,2,2,2,2,2,2,2,2,2,2,2,2,3},
{3,1,3,3,3,3,3,2,3,3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,3,3,1,3},
{3,2,3,3,3,3,3,2,3,3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,3,3,2,3},
{3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3},
{3,2,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,2,3},
{3,2,2,2,2,2,2,2,3,2,2,2,2,3,3,2,2,2,2,3,2,2,2,2,2,2,2,3},
{3,3,3,3,3,3,3,2,3,3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,3,3,3,3},
{0,0,0,0,0,0,3,2,3,2,2,2,2,2,2,2,2,2,2,3,2,3,0,0,0,0,0,0},
{0,3,3,3,3,3,3,2,3,2,3,3,3,0,0,3,3,3,2,3,2,3,3,3,3,3,3,0},
{0,0,0,0,0,0,0,2,2,2,3,0,0,0,0,0,0,3,2,2,2,0,0,0,0,0,0,0},
{0,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,0},
{0,0,0,0,0,0,3,2,3,2,2,2,2,2,2,2,2,2,2,3,2,3,0,0,0,0,0,0},
{3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3},
{3,2,2,2,2,2,2,2,2,2,2,2,2,3,3,2,2,2,2,2,2,2,2,2,2,2,2,3},
{3,2,3,3,3,3,3,2,3,3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,3,3,2,3},
{3,2,3,3,3,3,3,2,3,3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,3,3,2,3},
{3,2,2,2,2,2,3,2,2,2,2,2,2,0,0,2,2,2,2,2,2,3,2,2,2,2,2,3},
{3,3,3,3,3,2,3,2,3,2,3,3,3,3,3,3,3,3,2,3,2,3,2,3,3,3,3,3},
{3,3,3,3,3,2,3,2,3,2,3,3,3,3,3,3,3,3,2,3,2,3,2,3,3,3,3,3},
{3,2,2,2,2,2,2,2,3,2,2,2,2,3,3,2,2,2,2,3,2,2,2,2,2,2,2,3},
{3,2,3,3,3,3,3,2,3,3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,3,3,2,3},
{3,1,3,3,3,3,3,2,3,3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,3,3,1,3},
{3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3},
{3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3},
};

NOTA: O mapa não precisa ser inicializado desta maneira, você pode carregar sua matriz através de um arquivo.

Antes de desenharmos nosso mapa e personagem vamos definir uma váriavel de posição, velocidade e direção para nosso personagem. Veja abaixo como fica a declaração das variáveis

// Posição do personagem

Vector2 position = new Vector2(324.0f, 408.0f);
// Velocidade do personagem
float speed = 2.0f;
// Direção de movimento do personagem
Vector2 direction = new Vector2(0.0f, 0.0f);

Com todas essas variáveis declaradas, agora podemos desenhar para ver o resultado e depois trabalhar na movimentação e colisão do personagem. Veja o método de desenho como fica.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.Black);
    spriteBatch.Begin();
    // Desenhando o mapa do jogo
    for (int i = 0; i < map.GetLength(0); i++)
    {
        for (int j = 0; j < map.GetLength(1); j++)
        {
             spriteBatch.Draw(tiles[map[i, j]].texture,
                new Vector2(j * tiles[map[i, j]].size.X, i * tiles[map[i, j]].size.X),
                Color.White);
        }
    }
    // Desenhando o personagem
    spriteBatch.Draw(character, position, Color.White);
    spriteBatch.End();
    base.Draw(gameTime);
}

Se você executar o programa agora poderá ver o mapa e o nosso personagem desenhado na tela, mas sem movimento. Para criarmos seu movimento precisamos fazer como já fizemos em diversos tutoriais.

protected override void Update(GameTime gameTime)
{
    // Recebe o estado do teclado
    KeyboardState keyboardState = Keyboard.GetState();
    // Verifica se o jogador pressionou seta para cima
    if (keyboardState.IsKeyDown(Keys.Up))
    {
        this.direction.Y = -1;
    }
    // Verifica se o jogador pressionou seta para baixo
    if (keyboardState.IsKeyDown(Keys.Down))
    {
        this.direction.Y = 1;
    }
    // Verifica se o jogador pressionou seta para direita
    if (keyboardState.IsKeyDown(Keys.Right))
    {
        this.direction.X = 1;
    }
    // Verifica se o jogador pressionou seta para esqueda
    if (keyboardState.IsKeyDown(Keys.Left))
    {
        this.direction.X = -1;
    }
    // Atualiza a posição do personagem
    this.position += this.direction * this.speed;
    base.Update(gameTime);
}

Legal, se executar o nosso joguinho agora verá poderá movimentar o fantasma, porém ele esta atravessando a parede e andando em diagonal, ou seja, precisamos corrigir isso. Para corrigirmos isso vamos ter que verificar se existe colisão do personagem com o tile que ele esta no momento. Então vamos criar um método para verificar isso. Abaixo do método de desenho, digite o seguinte código:

/// <summary>
/// Método que detecta colisão
/// </summary>
/// <param>posição y do tile</param>
/// <param>posição x do tile</param>
/// <returns></returns>
public bool CheckTileCollision(int x, int y)
{
    if (tiles[map[y, x]].collision != TileCollision.Impassable)
    {
        return false;
    }
    return true;
}

Agora temos um método que a partir de um ponto X e Y de tile ele verifica se o tile não é passável. Vamos então utilizar esse método após tentarmos movimentar nosso personagem no método Update(). Digite o seguinte código antes de atualizarmos a posição do personagem:

protected override void Update(GameTime gameTime)
{
    .
    .
    .
    .
    // Define a nova posição do personagem
    Vector2 newPosition = this.position;
    newPosition.X += this.direction.X * speed;
    newPosition.Y += this.direction.Y * speed;
    // Define os tiles vizinhos do personagem conforme a nova posição
    int left = (int)(newPosition.X / 24);
    int top = (int)(newPosition.Y / 24);
    int right = (int)((newPosition.X + (character.Width - 1)) / 24);
    int bottom = (int)((newPosition.Y + (character.Height - 1)) / 24);
    // Verifica colisão do personagem com todos os tiles vizinhos definidos ao deslocar-se
    if (this.direction.Y == -1) // Se o personagem esta indo para Cima
    {
        if (CheckTileCollision(left, top) || CheckTileCollision(right, top))
        {
            this.direction.Y = 0;
        }
    }
    else if (this.direction.Y == 1) // Se o personagem esta indo para baixo
    {
        if (CheckTileCollision(left, bottom) || CheckTileCollision(right, bottom))
        {
            this.direction.Y = 0;
        }
    }
    if (this.direction.X == 1) // Se o personagem esta indo para direita
    {
        if (CheckTileCollision(right, top) || CheckTileCollision(right, bottom))
        {
            this.direction.X = 0;
        }
    }
    else if (this.direction.X == -1) // Se o personagem esta indo para esquerda
    {
        if (CheckTileCollision(left, top) || CheckTileCollision(left, bottom))
        {
            this.direction.X = 0;
        }
    }
    // Verifica se o personagem passou a tela pela esquerda
    if (this.position.X < 24.0)
    {
        this.position.X = 624.0f;
    }
    // Verifica se o personagem passou a tela pela direita
    if (this.position.X > 624.0)
    {
        this.position.X = 24.0f;
    }
    // Atualiza a posição do personagem
    this.position += this.direction * this.speed;
    base.Update(gameTime);
}

O que fizemos então criamos uma variável que recebe a nova posição que o personagem vai receber, então verificamos esta posição dentro do nosso mapa de tiles se ela ocupa algum espaço que existe tiles impassíveis com a função CheckTileCollision(int x, int y)

Se você acompanhou corretamente todos os passos e não digitou nada errado e resultado é para ser um fantasma se movimentando na tela colidindo com o cenário, conforme imagem abaixo.


Para fazer download do código completo clique aqui.

Abraços a todos e espero que tenham gostado. Até a próxima.

[XNA] Colisão por BoundingSphere

O sistema de colisão por BoundingSphere, asssim como o sistema de BoundingBox é sistema simples, muito menos complexo que a colisão por pixel que já vimos. Na colisão por BoundingSphere é feita uma analise utilizando o centro do circulo e seu raio.

Este tipo de colisão não é excelente para esferas, além de ser um sistema prático e rápido de ser implementado, além de totalmente portável para o sistema tridimensional.

A estrutura do BoundingSphere é igual a estrutura doBoundingBox, mas vamos colocar aqui para você não ter que abrir o outro tutorial

  • Radius: Informa o raio do círculo.
  • Center: Retorna o centro da BoundigBox em formato Vector3.

  • Intersects: verifica se houve intersecção entre ele e outros objetos das estruturas Bounding.
  • Contains: retorna uma enumeração denominada ContainmentType (Contains, Disjoint ou Intersect).

Vamos então criar um exemplo e ver este método de colisão em ação. Nosso exemplo vai ter duas caixas (BoundingSphere) e para isso vamos precisar saber a posição delas, uma dessas caixas vai ficar parada e a outra vamos mover com o teclado, e caso aconteça uma colisão vamos pintar a tela de vermelho caso contrario deixamos com a cor azul padrão.

Crie um projeto e salve a imagem abaixo dentro da Content.

bola.png

Crie as variáveis necessárias, inicialize e carregue a imagem. Veja o código abaixo.

Agora vamos fazer desenhar nossas imagens na tela e depois ver como vai funcionar o método Update().

Enfim, vamos movimenta nossa bola verde pela tela usando as setas do teclado e após atualizarmos sua posição, vamos atualizar as BoundingSphere das bolas e checar se aconteceu ou não colisão.

Por fim, vamos compilar nosso projeto para ver o resultado final.

Caso você queira fazer a bola verde ficar sempre na frente da azul, é só inverter a ordem de desenho delas, desenhe primeiro a azul e depois a verde. Se você leu o artigo de Colisão por Pixel, pode notar o quanto é mais fácil fazer uma colisão por BoundingSphere.

Para fazer download do projeto clique aqui.

Espero que tenham gostado, até a próxima.

[XNA] Colisão por Bounding Box

O sistema de colisão por BoundingBox é um sistema simples, muito menos complexo que a colisão por pixel que vimos no tutorial passado. Na colisão por BoundingBox é feita uma analise utilizando o retângulo da imagem 2D (entende-se por retângulo da imagem a sua localização e tamanho), no qual verificamos se houve alguma sobreposição.

Este tipo de colisão não é uma das melhores formas, pois pode ocorrer das imagens não ocuparem o retângulo por completo, dando uma falsa impressão de colisão. Porém é um método prático e rápido de ser implementado. Uma das grandes vantagens deste sistema é que podemos utilizar o mesmo código em jogos 3D, pois o sistema do BoundingBox trabalha com coordenadas tridimensionais.

Vamos analisar a estrutura de um BoundingBox.

  • CornerCount: Informa a quantidade de vértices que a estrutura tem, ou seja 8 (cubo).
  • Min: é o ponto com componentes (x,y,z) que são menores ou iguais aos outros pontos que compões a BoundingBox.
  • Max: é o ponto com componentes (x,y,z) que são maiores ou iguais aos outros pontos que compões a BoundingBox.

  • Intersects: verifica se houve intersecção entre ele e outros objetos das estruturas Bounding.
  • Contains: retorna uma enumeração denominada ContainmentType (Contains, Disjoint ou Intersect).

Vamos então criar um exemplo e ver este método de colisão em ação. Nosso exemplo vai ter duas caixas (BoundingBox) e para isso vamos precisar saber a posição delas, uma dessas caixas vai ficar parada e a outra vamos mover com o teclado, e caso aconteça uma colisão vamos pintar a tela de vermelho caso contrario deixamos com a cor azul padrão.

Crie um projeto e salve a imagem abaixo dentro da Content.

caixa.png

Crie as variáveis necessárias, inicialize e carregue a imagem. Veja o código abaixo.

Agora vamos fazer desenhar nossas imagens na tela e depois ver como vai funcionar o método Update().

Enfim, vamos movimenta nossa caixa verde pela tela usando as setas do teclado e após atualizarmos sua posição, vamos atualizar as Bounding Box das caixas e checar se aconteceu ou não colisão.

Por fim, vamos compilar nosso projeto para ver o resultado final.

Caso você queira fazer a caixa verde ficar sempre na frente da azul, é só inverter a ordem de desenho delas, desenhe primeiro a azul e depois a verde. Se você leu o artigo de Colisão por Pixel, pode notar o quanto mais fácil é fazer uma colisão por BoundingBox.

Para fazer download do projeto clique aqui.

Espero que tenham gostado, até a próxima.

Referência:

Colisão por BoundingBox no XNA: http://www.sharpgames.net/Artigo/tabid/58/selectmoduleid/376/ArticleID/1060/reftab/54/Default.aspx, acessado em Novembro de 2009.

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

Join 29 other followers