sábado, 30 de julho de 2011

XNA 4.0 - Recuperando Altura de um terreno.

Vamos agora recuperar a altura de nosso terreno, é relativamente fácil.
Fornecemos as posições em forma de um vetor do tipo Vector3, Obtemos a posição na malha, comparamos com a posição do bloco inicial e verificamos se essa posição está mesmo dentro de nossa malha, caso sim calcula o quadrado que estamos, pega os 4 indices que formam esse quadrado, faz alguns calculos vetoriais e mostra exatamente a posição. O código é esse:

public float GetHeight(Vector3 position)
{
return GetHeight(position.X, position.Z);
}

private float GetHeight(float positionX, float positionZ)
{
float height = -999999.0f;
if (heightMap == null) return height;

// Obter a posicao do objeto na grade do terreno
Vector2 positionInGrid = new Vector2(
positionX - (StartPosition.X),
positionZ - (StartPosition.Y));

// Calcula a posicao do bloco inicial
Vector2 blockPosition = new Vector2(
positionInGrid.X / blockScale,
positionInGrid.Y / blockScale);

// Verifica se o objeto esta dentro da grade
if (blockPosition.X >= 0 && blockPosition.X < (vertexCountX - 1) && blockPosition.Y >= 0 && blockPosition.Y < (vertexCountZ - 1)) { Vector2 blockOffset = new Vector2( blockPosition.X - (int)blockPosition.X, blockPosition.Y - (int)blockPosition.Y); // Obteem a altura dos quatro vertices do bloco int vertexIndex = (int)blockPosition.X + (int)blockPosition.Y * vertexCountX; float height1 = heightMap[vertexIndex + 1]; float height2 = heightMap[vertexIndex]; float height3 = heightMap[vertexIndex + vertexCountX + 1]; float height4 = heightMap[vertexIndex + vertexCountX]; // Triangule de baixo float heightIncX, heightIncY; if (blockOffset.X > blockOffset.Y)
{
heightIncX = height1 - height2;
heightIncY = height3 - height1;
}
// Triangulo de cima
else
{
heightIncX = height3 - height4;
heightIncY = height4 - height2;
}

// Interpolacao linear para encontrar a altura do triangulo
float lerpHeight = height2 + heightIncX * blockOffset.X + heightIncY * blockOffset.Y;
height = lerpHeight * heightScale;
}

return height;
}

Criamos dois métodos, um para retornar a altura em uma determinada posição e outro cuida de todos os calculos envovendo isso.

Vamos inserir uma propriedade para que a posição inicial seja calculada tempo como base os vértices que escolhemos para ser o inicial. Adicione entre as variáveis e o mé todo Initialize():

public Vector2 StartPosition
{
get
{
float terrainHalfWidth = (vertexCountX - 1) * blockScale * 0.5f;
float terrainHalfDepth = (vertexCountZ - 1) * blockScale * 0.5f;

return new Vector2(-terrainHalfWidth, -terrainHalfDepth);
}
}

Agora um código para que a nossa câmera siga a altura do mapa, para termos a noção de relevo do mapa, vá ao arquivo "Game1.cs" no método Update() e insira, depois do CameraManager.ActiveCamera.addToPosition()

cameraManager.ActiveCamera.Position = new Vector3(cameraManager.ActiveCamera.Position.X,
terrain.GetHeight(cameraManager.ActiveCamera.Position) + 1.86f,
cameraManager.ActiveCamera.Position.Z
);

Esse código atualiza a posição do eixo Y tendo como relação a posição fornecida e é acrescida de 1.86f enquando as posições X e Z são mantidas.
1.86f é a minha altura, se você quiser uma escala de 1:1 ou seja, 1.0f em nosso mundo equivale a 1 metro de altura então temos que 1.86f representa 1 metro e 86 centimentros e que o nosso mapa tem 256m2, o que é espaço pra caramba para brincar.

XNA 4.0 - Criando Tangente e Binormal

A principio ia falar sobre efeitos em XNA bem superficialmente, mas decidi que seria legal colocar uma série separada, onde até eu mesmo irei aprender mais sobre isso.

Nós estamos usando efeitos básicos, que já vem com o XNA, e são muito simples, não permitem usar luzes pelo mapa e nada mais avançado, então se queremos usar efeitos avançado em nosso terreno teremos de calcular algumas coisas a mais. Efeitos em sua maioria requerem Tangente, BiNormal, Normal, Coordenada da Textura e Posição. Nós já fizemos e achamos a posição, a coordenada das texturas e a normal e para isso nós usamos o vertex: VertexPositiomNormal mas agora precisamos usar um outro tipo de vértice que junte tudo isso pra gente. Mas o XNA não fornece isso para gente, então vamos criar o VertexPositionNormalTangentBinormal. Na raiz do projeto crie uma pasta e nomeie-a como "Helpers", adicione um novo arquivo nessa parata, chamado de : "VertexPositionNormalTangentBinormal". O código é a estrutura interna do vértice, pelo nome e pelo o que contem é possível saber qual a estrutura dos outros tipos de Vertexs. O código do arquivo completo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Tutorial1.Helpers
{
public struct VertexPositionNormalTangentBinormal
{

#region Variaveis
public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
public Vector3 Tanget;
public Vector3 Binormal;
#endregion

#region Propriedades
public static int SizeInBytes
{
get
{
return (3 + 3 + 2 + 3 + 3 ) * sizeof(float);
}
}
#endregion

#region Construtor
public VertexPositionNormalTangentBinormal(Vector3 position, Vector3 normal, Vector2 textureCoordinate, Vector3 tanget, Vector3 binormal)
{
Position = position;
Normal = normal;
TextureCoordinate = textureCoordinate;
Tanget = tanget;
Binormal = binormal;
}
#endregion

#region Elementos do Vertex
public static VertexElement[] VertexElements = new VertexElement[] {
new VertexElement(0, VertexElementFormat.Vector3,
VertexElementUsage.Position, 0),
new VertexElement(12, VertexElementFormat.Vector3,
VertexElementUsage.Normal, 0),
new VertexElement(24, VertexElementFormat.Vector2,
VertexElementUsage.TextureCoordinate, 0),
new VertexElement(32, VertexElementFormat.Vector3,
VertexElementUsage.Tangent, 0),
new VertexElement(44, VertexElementFormat.Vector3,
VertexElementUsage.Binormal, 0),

};
#endregion
}
}

Volte no arquivo "Terrain.cs" aprete CTRL+H e substitua todas as ocorrências de VertexPositiomNormal para VertexPositionNormalTangentBinormal

No método GenerateTerrainMesh() substitua a declaração do vértice para essa

VertexDeclaration decl = new VertexDeclaration(VertexPositionNormalTangentBinormal.VertexElements);

No método GenerateTerrainVértices adicione a linha, a linha fica depois de "GenerateTerrainNormals(vertices, terrainIndices);"

GenerateTerrainTangentBinormal(vertices, terrainIndices);

Agora vamos criar o método GenerateTerrainTangentBinormal, a tangent é um vetor que aponta do atual vértice para o próximo. e a BiNormal é um vetor que é obtido atrávez do produto vetorial da Normal com a tangente, o código fica assim:
public void GenerateTerrainTangentBinormal(VertexPositionNormalTangentBinormal[] vertices, int[] indices)
{
for (int i = 0; i < vertexCountZ; i++) { for (int j = 0; j < vertexCountX; j++) { int vertexIndex = j + i * vertexCountX; Vector3 v1 = vertices[vertexIndex].Position; if (j < vertexCountX - 1) { Vector3 v2 = vertices[vertexIndex + 1].Position; vertices[vertexIndex].Tanget = (v2 - v1); } else { Vector3 v2 = vertices[vertexIndex - 1].Position; vertices[vertexIndex].Tanget = (v1 - v2); } vertices[vertexIndex].Tanget.Normalize(); vertices[vertexIndex].Binormal = Vector3.Cross(vertices[vertexIndex].Tanget, vertices[vertexIndex].Normal); } } }


Isso em nada vai mudar a nossa cena mas como usaremos efeitos avançados mais a frente é bom terminar a classe Terrain logo de uma vez;

sexta-feira, 29 de julho de 2011

XNA 4.0 - Parte 6 Terrenos

Baixe esse arquivo: http://www.megaupload.com/?d=MEROS15K

Ele possui algumas texturas que usaremos para criar nosso terreno e depois com efeitos avançados.

Extraia as imagens, Crie uma Pasta chamada "Textures" no content e adicione essas 4 imagens para dentro da pasta.

Para criarmos uma textura e ilumina-la precusamos calcular a "Normal" de cada vértice para que ele possa ser iluminado. Geralmente um Vector3.Up é o suficiente para criar a normal do vértice mas vamos fazer algo um pouco mais trabalhado. Vamos Criar um novo método antes do método Update(), o método se chamara GenerateTerrainNormals() ele vai receber uma lista de vértices do Tipo VertexPositionColor.

private void GenerateTerrainNormals(VertexPositionColor[] vertices, int[] indices)
{
for (int i = 0; i < indices.Length; i += 3) { Vector3 v1 = vertices[indices[i]].Position; Vector3 v2 = vertices[indices[i + 1]].Position; Vector3 v3 = vertices[indices[i + 2]].Position; Vector3 vu = v3 - v1; Vector3 vt = v2 - v1; Vector3 normal = Vector3.Cross(vu, vt); normal.Normalize(); vertices[indices[i]].Normal += normal; vertices[indices[i + 1]].Normal += normal; vertices[indices[i + 2]].Normal += normal; } }



Ele pega as "normais" dos outros dois vértices e criar algo mais elaborado.

Agora vá no método CreateTerrainVertices e adicione os eguinte código antes de "return vertices"

// Cria a normal do vertice
GenerateTerrainNormals(vertices, terrainIndices);

Fazendo algumas alterações no código, atualmente usamos "VertexPositionColor", foi legal enquanto durou mas queremos usar texturas e luzes, vamos alterar todas as chamadas de VertexPositionColor para VertexPositionNormalTexture, vamos usar o atalho CRTL+H, substitua todas, no total são 7. Vamos Substituir todas as ocorrências de BasicEffect para EnvironmentMapEffect, no total serão 2.

Ainda no método CreateTerrainVertices() altere a referência de cores para coordenada de textura. Então tinhamos:

vertices[vertexCount].Color = Color.Yellow;
if((int)heightMap[vertexCount] < 80) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 200)
vertices[vertexCount].Color = Color.Red;

E agora teremos:

vertices[vertexCount].TextureCoordinate = new Vector2(j, i);

Vou colocar o método inteiro para ficar mais claro:

private VertexPositionNormalTexture[] GenerateTerrainVertices(int[] terrainIndices)
{
float halfTerrainWidth = (vertexCountX - 1) * blockScale * 0.5f;
float halfTerrainDepth = (vertexCountZ - 1) * blockScale * 0.5f;

int vertexCount = 0;
VertexPositionNormalTexture[] vertices =
new VertexPositionNormalTexture[vertexCountX * vertexCountZ];

// Define a posicao dos vertices e a coordenada de textura
for (float i = -halfTerrainDepth; i <= halfTerrainDepth; i += blockScale) { for (float j = -halfTerrainWidth; j <= halfTerrainWidth; j += blockScale) { vertices[vertexCount].Position = new Vector3(j, heightMap[vertexCount] * heightScale, i); vertices[vertexCount].TextureCoordinate = new Vector2(j, i); vertexCount++; } } // Gera as normais dos indices GenerateTerrainNormals(vertices, terrainIndices); return vertices; }



No método Load(), na ultima linha substitua o "effect.VertexColorEnabled = true;" para "effect.EnableDefaultLighting();" e logo após adicione a se guinte linha:

effect.Texture = Game.Content.Load("Textures/Terrain2");

Você pode alterar essa linha para:

effect.Texture = Game.Content.Load("Textures/Terrain1");

mudando o ultimo parametro, você pode adicionar as suas próprias texturas, adicionando-as a pasta Textures e depois substituindo o nome dele no ultimo parametro:

effect.Texture = Game.Content.Load("Textures/nomeSuaTexturaSemOTipoDeArquivo");

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Tutorial1.GameBase.Cameras;
using System.IO;

namespace Tutorial1.GameBase.Shapes
{
public class Terrain : DrawableGameComponent
{
#region Variáveis
//Variável que guardará a altura do mapa
byte[] heightMap;

//Variáveis de renderização
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;

//Guarda o número de Vértices e de triângulos
int numVertices;
int numTriangles;

//Quantidade de vértices nos eixos X e Z
int vertexCountX;
int vertexCountZ;

//Escala do nosso mapa
float blockScale;
float heightScale;

//Efeito que será usado na cena
EnvironmentMapEffect effect;

//Serviços necessários
bool isInitialized;
CameraManager cameraManager;

#endregion

#region Construtor()
public Terrain(Game game)
: base(game)
{
isInitialized = false;
}
#endregion

#region initialize()
//Inicializa todos os nossos serviços e depois de iniciado defini nossa flag como true, permitindo que outros
//métodos possam se executar sem perda de dados
public override void Initialize()
{
cameraManager = Game.Services.GetService(typeof(CameraManager)) as CameraManager;

//Define o nosso RenderState
//Rasterize.FillMode =
//Pode variar entre FillMode.Solid - Para mostrar os objetos da cena de momo completo
//FillMode.WireFrame - Para mostrar os triângulod que formam os objetos na cena
RasterizerState rasterizeState = new RasterizerState();
rasterizeState.FillMode = FillMode.Solid;
Game.GraphicsDevice.RasterizerState = rasterizeState;

isInitialized = true;

base.Initialize();
}
#endregion

#region Load()
public void Load(string heightmapFileName, int vertexCountX, int vertexCountZ, float blockScale, float heightScale)
{
//Cuida para que tudo seja iniciado
if (!isInitialized)
Initialize();


//Passamos alguns parametros para definir o tamanho do Mapa em X e Z e tambem a escala de
//Tamanho e de altura
this.vertexCountX = vertexCountX;
this.vertexCountZ = vertexCountZ;
this.blockScale = blockScale;
this.heightScale = heightScale;

// Carrega o arquivo do mapa de altura
FileStream fileStream = File.OpenRead(Game.Content.RootDirectory + "/Terrains/" + heightmapFileName + ".raw");

int heightmapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightmapSize];
fileStream.Read(heightMap, 0, heightmapSize);
fileStream.Close();

GenerateTerrainMesh();

effect = new EnvironmentMapEffect(Game.GraphicsDevice);
effect.EnableDefaultLighting();
effect.Texture = Game.Content.Load("Textures/Terrain2");
}
#endregion

#region GenerateTerrainMesh()
private void GenerateTerrainMesh()
{
//Vamos definir o número de vértices e triângulo para renderizar todos eles
numVertices = vertexCountX * vertexCountZ;
numTriangles = (vertexCountX - 1) * (vertexCountZ - 1) * 2;

//Criando nosso índices
int[] indices = GenerateTerrainIndices();

VertexPositionNormalTexture[] vertices = GenerateTerrainVertices(indices);

//Vamos criar a declaração dos vértices
VertexDeclaration decl = new VertexDeclaration(VertexPositionNormalTexture.VertexDeclaration.GetVertexElements());

// Cria o buffer de vertices
vertexBuffer = new VertexBuffer(GraphicsDevice, decl, numVertices, BufferUsage.WriteOnly);

//Adiciona nossos vértices ao Buffer
vertexBuffer.SetData(vertices);

//Cria o Buffer de índices
indexBuffer = new IndexBuffer(Game.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);

//Adiciona nossos índices ao Buffer
indexBuffer.SetData(indices);
}
#endregion

#region GenerateTerrainIndices()
private int[] GenerateTerrainIndices()
{
//Cria todos os índices, como cada triângulo possui 3 vértices
int numIndices = numTriangles * 3;
int[] indices = new int[numIndices];

int indicesCount = 0;

//Cria dois indices no mesmo segmento de indices e outro indice no próximo segmento
//Faz isso 2 vezes, com dois triângulos formando um quadrado
for (int i = 0; i < (vertexCountZ - 1); i++)
{
for (int j = 0; j < (vertexCountX - 1); j++)
{
int index = j + i * vertexCountZ;
indices[indicesCount++] = index;
indices[indicesCount++] = index + 1;
indices[indicesCount++] = index + vertexCountX + 1;

indices[indicesCount++] = index + vertexCountX + 1;
indices[indicesCount++] = index + vertexCountX;
indices[indicesCount++] = index;

}
}

return indices;
}
#endregion

#region GenerateTerrainVertices()
private VertexPositionNormalTexture[] GenerateTerrainVertices(int[] terrainIndices)
{
float halfTerrainWidth = (vertexCountX - 1) * blockScale * 0.5f;
float halfTerrainDepth = (vertexCountZ - 1) * blockScale * 0.5f;

int vertexCount = 0;
VertexPositionNormalTexture[] vertices =
new VertexPositionNormalTexture[vertexCountX * vertexCountZ];

// Define a posicao dos vertices e a coordenada de textura
for (float i = -halfTerrainDepth; i <= halfTerrainDepth; i += blockScale)
{
for (float j = -halfTerrainWidth; j <= halfTerrainWidth; j += blockScale)
{
vertices[vertexCount].Position = new Vector3(j, heightMap[vertexCount] * heightScale, i);
vertices[vertexCount].TextureCoordinate = new Vector2(j, i);

vertexCount++;
}

}
// Gera as normais dos indices
GenerateTerrainNormals(vertices, terrainIndices);

return vertices;
}
#endregion

#region GenerateTerrainNormals()
private void GenerateTerrainNormals(VertexPositionNormalTexture[] vertices, int[] indices)
{
for (int i = 0; i < indices.Length; i += 3)
{
Vector3 v1 = vertices[indices[i]].Position;
Vector3 v2 = vertices[indices[i + 1]].Position;
Vector3 v3 = vertices[indices[i + 2]].Position;

Vector3 vu = v3 - v1;
Vector3 vt = v2 - v1;
Vector3 normal = Vector3.Cross(vu, vt);
normal.Normalize();

vertices[indices[i]].Normal += normal;
vertices[indices[i + 1]].Normal += normal;
vertices[indices[i + 2]].Normal += normal;
}
}
#endregion

#region Update()
public override void Update(GameTime gameTime)
{
effect.View = cameraManager.ActiveCamera.View;
effect.Projection = cameraManager.ActiveCamera.Projection;
base.Update(gameTime);
}
#endregion

#region Draw()
public override void Draw(GameTime gameTime)
{

//Adicionamos o nossos indices e vertices ao nosso dispositivso para desenhar
Game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
Game.GraphicsDevice.Indices = indexBuffer;

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
//Adiciona e aplica o efeito
pass.Apply();
//Desenha a lista de linhas
Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numVertices, 0, numTriangles);
}

base.Draw(gameTime);
}
#endregion
}
}
Baixe esse arquivo: http://www.megaupload.com/?d=JB5UCFGU

Nele tem dois arquivos de 8-Bits no formato .raw - Você pode usar o google imagens, procurar por imagens e preto e branco e depois salvar o arquivo no tipo .raw, eu uso o PhotoShop mas existem algumas ferramentas que fazer mapas para você, TerraGen e EathSculptor.

Extraia os arquivos onde quiser. Em tutorial1.Content(Content) crie uma nova pasta, chame-a de "Terrains", Clique com o botão direito sobre a pasta criada -> Add -> Existing itens e selecione as duas imagens.

Selecione cada uma de uma vez, abra a aba de propeiedades. Mude Build Action para "none" e CopyToOutputDirectory para Copy if newer. Isso acontece porque o XNA transforma as imagens para 32Bits, o que não queremos.

Volte no arquivo "Terrain.cs" adicione a seguinte using "using System.IO;", no Método Load() apague as seguintes linhas


"Random random = new Random();" e

"//Definimos o tamanho total do mapa, usando parametros anteriormente declarados
int heightMapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightMapSize];
//Geramos valores aleatórios para a alutura de nooso mapa
for (int i = 0; i < heightMapSize; i++) heightMap[i] = (byte)random.Next(0, 30);" e cole o seguinte código:
// Carrega o arquivo do mapa de altura
FileStream fileStream = File.OpenRead(Game.Content.RootDirectory + "/Terrains/" + heightmapFileName + ".raw");

int heightmapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightmapSize];
fileStream.Read(heightMap, 0, heightmapSize);
fileStream.Close();

Agora altere os parametros do método Load para
public void Load(string heightmapFileName, int vertexCountX, int vertexCountZ, float blockScale, float heightScale) - Adicionamos o

"string heightmapFileName"

No inicio dos parametros.

No arquivo "Game1.cs" Ache novamente o método terrain.Load() e mude-o para:

terrain.Load("Terrain1", 256, 256, 1.0f, 0.1f); ou
terrain.Load("Australia", 256, 256, 1.0f, 0.1f);

Por Ultimo vamos alterar a cor, no método CreateTerrainVertices mude a altura de cada cor para

if((int)heightMap[vertexCount] < 80) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 200)
vertices[vertexCount].Color = Color.Red;

Você pose mudar o FillMode para Solid no método Initialize();

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Tutorial1.GameBase.Cameras;
using System.IO;

namespace Tutorial1.GameBase.Shapes
{
public class Terrain : DrawableGameComponent
{
#region Variáveis
//Variável que guardará a altura do mapa
byte[] heightMap;

//Variáveis de renderização
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;

//Guarda o número de Vértices e de triângulos
int numVertices;
int numTriangles;

//Quantidade de vértices nos eixos X e Z
int vertexCountX;
int vertexCountZ;

//Escala do nosso mapa
float blockScale;
float heightScale;

//Efeito que será usado na cena
BasicEffect effect;

//Serviços necessários
bool isInitialized;
CameraManager cameraManager;

#endregion

#region Construtor()
public Terrain(Game game)
: base(game)
{
isInitialized = false;
}
#endregion

#region initialize()
//Inicializa todos os nossos serviços e depois de iniciado defini nossa flag como true, permitindo que outros
//métodos possam se executar sem perda de dados
public override void Initialize()
{
cameraManager = Game.Services.GetService(typeof(CameraManager)) as CameraManager;

//Define o nosso RenderState
//Rasterize.FillMode =
//Pode variar entre FillMode.Solid - Para mostrar os objetos da cena de momo completo
//FillMode.WireFrame - Para mostrar os triângulod que formam os objetos na cena
RasterizerState rasterizeState = new RasterizerState();
rasterizeState.FillMode = FillMode.WireFrame;
Game.GraphicsDevice.RasterizerState = rasterizeState;

isInitialized = true;

base.Initialize();
}
#endregion

#region Load()
public void Load(string heightmapFileName, int vertexCountX, int vertexCountZ, float blockScale, float heightScale)
{
//Cuida para que tudo seja iniciado
if (!isInitialized)
Initialize();


//Passamos alguns parametros para definir o tamanho do Mapa em X e Z e tambem a escala de
//Tamanho e de altura
this.vertexCountX = vertexCountX;
this.vertexCountZ = vertexCountZ;
this.blockScale = blockScale;
this.heightScale = heightScale;

// Carrega o arquivo do mapa de altura
FileStream fileStream = File.OpenRead(Game.Content.RootDirectory + "/Terrains/" + heightmapFileName + ".raw");

int heightmapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightmapSize];
fileStream.Read(heightMap, 0, heightmapSize);
fileStream.Close();

GenerateTerrainMesh();

effect = new BasicEffect(Game.GraphicsDevice);
effect.VertexColorEnabled = true;
}
#endregion

#region GenerateTerrainMesh()
private void GenerateTerrainMesh()
{
//Vamos definir o número de vértices e triângulo para renderizar todos eles
numVertices = vertexCountX * vertexCountZ;
numTriangles = (vertexCountX - 1) * (vertexCountZ - 1) * 2;

//Criando nosso índices
int[] indices = GenerateTerrainIndices();

VertexPositionColor[] vertices = GenerateTerrainVertices(indices);

//Vamos criar a declaração dos vértices
VertexDeclaration decl = new VertexDeclaration(VertexPositionColor.VertexDeclaration.GetVertexElements());

// Cria o buffer de vertices
vertexBuffer = new VertexBuffer(GraphicsDevice, decl, numVertices, BufferUsage.WriteOnly);

//Adiciona nossos vértices ao Buffer
vertexBuffer.SetData(vertices);

//Cria o Buffer de índices
indexBuffer = new IndexBuffer(Game.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);

//Adiciona nossos índices ao Buffer
indexBuffer.SetData(indices);
}
#endregion

#region GenerateTerrainIndices()
private int[] GenerateTerrainIndices()
{
//Cria todos os índices, como cada triângulo possui 3 vértices
int numIndices = numTriangles * 3;
int[] indices = new int[numIndices];

int indicesCount = 0;

//Cria dois indices no mesmo segmento de indices e outro indice no próximo segmento
//Faz isso 2 vezes, com dois triângulos formando um quadrado
for (int i = 0; i < (vertexCountZ - 1); i++) { for (int j = 0; j < (vertexCountX - 1); j++) { int index = j + i * vertexCountZ; indices[indicesCount++] = index; indices[indicesCount++] = index + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX; indices[indicesCount++] = index; } } return indices; } #endregion #region GenerateTerrainVertices() private VertexPositionColor[] GenerateTerrainVertices(int[] terrainIndices) { float halfTerrainWidth = (vertexCountX - 1) * blockScale * 0.5f; float halfTerrainDepth = (vertexCountZ - 1) * blockScale * 0.5f; int vertexCount = 0; VertexPositionColor[] vertices = new VertexPositionColor[vertexCountX * vertexCountZ]; // Define a posicao dos vertices e a coordenada de textura for (float i = -halfTerrainDepth; i <= halfTerrainDepth; i += blockScale) { for (float j = -halfTerrainWidth; j <= halfTerrainWidth; j += blockScale) { vertices[vertexCount].Position = new Vector3(j, heightMap[vertexCount] * heightScale, i); vertices[vertexCount].Color = Color.Yellow; if((int)heightMap[vertexCount] < 80) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 200)
vertices[vertexCount].Color = Color.Red;

vertexCount++;
}

}

return vertices;
}
#endregion

#region Update()
public override void Update(GameTime gameTime)
{
effect.View = cameraManager.ActiveCamera.View;
effect.Projection = cameraManager.ActiveCamera.Projection;
base.Update(gameTime);
}
#endregion

#region Draw()
public override void Draw(GameTime gameTime)
{

//Adicionamos o nossos indices e vertices ao nosso dispositivso para desenhar
Game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
Game.GraphicsDevice.Indices = indexBuffer;

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
//Adiciona e aplica o efeito
pass.Apply();
//Desenha a lista de linhas
Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numVertices, 0, numTriangles);
}

base.Draw(gameTime);
}
#endregion
}
}

XNA 4.0 - Parte 6 - Terrenos

Vamos agora criar terrenos de verdade, terrenos grandes, com altura. Nesse primeiro momento apenas o básico. No final terá o código completo.

Abra o arquivo "Terrain.cs" e apague todos os métodos, deixe apenas a declaração da classe e os usings:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Tutorial1.GameBase.Cameras;

namespace Tutorial1.GameBase.Shapes
{
public class Terrain : DrawableGameComponent
{
}
}

Vamos definir nossas variáveis, a primera uma array do tipo byte para guardar a altura de cada vértice de nosso mapa, seguido da declaração de nosso vertex e indice buffer.

//Variável que guardará a altura do mapa
byte[] heightMap;

//Variáveis de renderização
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;

Agora variáveis do mundo, são elas

//Guarda o número de Vértices e de triângulos
int numVertices;
int numTriangles;

//Quantidade de vértices nos eixos X e Z
int vertexCountX;
int vertexCountZ;

//Escala do nosso mapa
float blockScale;
float heightScale;

E pra terminar Variáveis de efeito e de serviços

//Efeito que será usado na cena
BasicEffect effect;

//Serviços necessários
bool isInitialized;
CameraManager cameraManager;

Terminado as declarações vamos fazer o método construtor:

public Terrain(Game game)
: base(game)
{
isInitialized = false;
}

Agora vamos fazer o método Initialize() dentro dele terá uma coisa nova, o FillMode e o RenderState, o primeiro,o FillMode, diz como a cena será desenhada, em Modo Solid ou WireFrame, sólido ou em triângulo, vamos definir como triangulos, você pode altera-lo depois para FillMode.Solid;

//Inicializa todos os nossos serviços e depois de iniciado defini nossa flag como true, permitindo que outros
//métodos possam se executar sem perda de dados
public override void Initialize()
{
cameraManager = Game.Services.GetService(typeof(CameraManager)) as CameraManager;

//Define o nosso RenderState
//Rasterize.FillMode =
//Pode variar entre FillMode.Solid - Para mostrar os objetos da cena de momo completo
//FillMode.WireFrame - Para mostrar os triângulod que formam os objetos na cena
RasterizerState rasterizeState = new RasterizerState();
rasterizeState.FillMode = FillMode.WireFrame;
Game.GraphicsDevice.RasterizerState = rasterizeState;

isInitialized = true;

base.Initialize();
}

Vamos partir direto para o método Load, ele vai receber o tamanho do mapa, X e Z e tambem as escala, escala de tamanho e de altura:

public void Load(int vertexCountX, int vertexCountZ, float blockScale, float heightScale)
{
//Cuida para que tudo seja iniciado
if (!isInitialized)
Initialize();

Random random = new Random();
//Passamos alguns parametros para definir o tamanho do Mapa em X e Z e tambem a escala de
//Tamanho e de altura
this.vertexCountX = vertexCountX;
this.vertexCountZ = vertexCountZ;
this.blockScale = blockScale;
this.heightScale = heightScale;

//Definimos o tamanho total do mapa, usando parametros anteriormente declarados
int heightMapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightMapSize];

//Geramos valores aleatórios para a alutura de nooso mapa
for (int i = 0; i < heightMapSize; i++) heightMap[i] = (byte)random.Next(0, 30); GenerateTerrainMesh(); effect = new BasicEffect(Game.GraphicsDevice); effect.VertexColorEnabled = true; }




O código acima chama o método GenerateTerrainmesh(), vamos criá-lo agora, nós ja usamos ele quando criamos nosso Eixos3D lá atrás, então não há muita novidade:

private void GenerateTerrainMesh()
{
//Vamos definir o número de vértices e triângulo para renderizar todos eles
numVertices = vertexCountX * vertexCountZ;
numTriangles = (vertexCountX - 1) * (vertexCountZ - 1) * 2;

//Criando nosso índices
int[] indices = GenerateTerrainIndices();

VertexPositionColor[] vertices = GenerateTerrainVertices(indices);

//Vamos criar a declaração dos vértices
VertexDeclaration decl = new VertexDeclaration(VertexPositionColor.VertexDeclaration.GetVertexElements());

// Cria o buffer de vertices
vertexBuffer = new VertexBuffer(GraphicsDevice, decl, numVertices, BufferUsage.WriteOnly);

//Adiciona nossos vértices ao Buffer
vertexBuffer.SetData(vertices);

//Cria o Buffer de índices
indexBuffer = new IndexBuffer(Game.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);

//Adiciona nossos índices ao Buffer
indexBuffer.SetData(indices);
}

o código acima chama mais 2 outros métodos, CreateTerrainindice e CreateTerrainIndices. O primeiro CreateTerrainindice() vai gerar nossos índices, o que ele faz é pegar os indices de 2 quadrados por vez, pegando 2 indices de um segmento e depois outro do próximo segmento, e a segunda parte pega para formar um único quadrado.

private int[] GenerateTerrainIndices()
{
//Cria todos os índices, como cada triângulo possui 3 vértices
int numIndices = numTriangles * 3;
int[] indices = new int[numIndices];

int indicesCount = 0;

//Cria dois indices no mesmo segmento de indices e outro indice no próximo segmento
//Faz isso 2 vezes, com dois triângulos formando um quadrado
for (int i = 0; i < (vertexCountZ - 1); i++) { for (int j = 0; j < (vertexCountX - 1); j++) { int index = j + i * vertexCountZ; indices[indicesCount++] = index; indices[indicesCount++] = index + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX; indices[indicesCount++] = index; } } return indices; }




E agora o método CreateTerrainVertices() - Que vai criar toda a malha, usando os índices já criados e pegando a altura de cada vértice.

private VertexPositionColor[] GenerateTerrainVertices(int[] terrainIndices)
{
float halfTerrainWidth = (vertexCountX - 1) * blockScale * 0.5f;
float halfTerrainDepth = (vertexCountZ - 1) * blockScale * 0.5f;

int vertexCount = 0;
VertexPositionColor[] vertices =
new VertexPositionColor[vertexCountX * vertexCountZ];

// Define a posicao dos vertices e a coordenada de textura
for (float i = -halfTerrainDepth; i <= halfTerrainDepth; i += blockScale) { for (float j = -halfTerrainWidth; j <= halfTerrainWidth; j += blockScale) { vertices[vertexCount].Position = new Vector3(j, heightMap[vertexCount] * heightScale, i); vertices[vertexCount].Color = Color.Yellow; if((int)heightMap[vertexCount] < 10) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 20)
vertices[vertexCount].Color = Color.Red;

vertexCount++;
}

}

return vertices;
}

E pra terminar os já conhecidos métodos Update() e Draw().

public override void Update(GameTime gameTime)
{
effect.View = cameraManager.ActiveCamera.View;
effect.Projection = cameraManager.ActiveCamera.Projection;
base.Update(gameTime);
}

public override void Draw(GameTime gameTime)
{

//Adicionamos o nossos indices e vertices ao nosso dispositivso para desenhar
Game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
Game.GraphicsDevice.Indices = indexBuffer;

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
//Adiciona e aplica o efeito
pass.Apply();
//Desenha a lista de linhas
Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numVertices, 0, numTriangles);
}

base.Draw(gameTime);
}

Como estamos usando classes e como eu já disse que classes cuidam de tudo que se refere a ela nós só precisaremos umar uma única coisa no arquivo "Game1.cs", no método load complemente o terrain.Load e vai ficar assim:

terrain.Load(256, 256, 1.0f, 0.1f);

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Tutorial1.GameBase.Cameras;

namespace Tutorial1.GameBase.Shapes
{
public class Terrain : DrawableGameComponent
{
#region Variáveis
//Variável que guardará a altura do mapa
byte[] heightMap;

//Variáveis de renderização
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;

//Guarda o número de Vértices e de triângulos
int numVertices;
int numTriangles;

//Quantidade de vértices nos eixos X e Z
int vertexCountX;
int vertexCountZ;

//Escala do nosso mapa
float blockScale;
float heightScale;

//Efeito que será usado na cena
BasicEffect effect;

//Serviços necessários
bool isInitialized;
CameraManager cameraManager;

#endregion

#region Construtor()
public Terrain(Game game)
: base(game)
{
isInitialized = false;
}
#endregion

#region initialize()
//Inicializa todos os nossos serviços e depois de iniciado defini nossa flag como true, permitindo que outros
//métodos possam se executar sem perda de dados
public override void Initialize()
{
cameraManager = Game.Services.GetService(typeof(CameraManager)) as CameraManager;

//Define o nosso RenderState
//Rasterize.FillMode =
//Pode variar entre FillMode.Solid - Para mostrar os objetos da cena de momo completo
//FillMode.WireFrame - Para mostrar os triângulod que formam os objetos na cena
RasterizerState rasterizeState = new RasterizerState();
rasterizeState.FillMode = FillMode.WireFrame;
Game.GraphicsDevice.RasterizerState = rasterizeState;

isInitialized = true;

base.Initialize();
}
#endregion

#region Load()
public void Load(int vertexCountX, int vertexCountZ, float blockScale, float heightScale)
{
//Cuida para que tudo seja iniciado
if (!isInitialized)
Initialize();

Random random = new Random();
//Passamos alguns parametros para definir o tamanho do Mapa em X e Z e tambem a escala de
//Tamanho e de altura
this.vertexCountX = vertexCountX;
this.vertexCountZ = vertexCountZ;
this.blockScale = blockScale;
this.heightScale = heightScale;

//Definimos o tamanho total do mapa, usando parametros anteriormente declarados
int heightMapSize = vertexCountX * vertexCountZ;
heightMap = new byte[heightMapSize];

//Geramos valores aleatórios para a alutura de nooso mapa
for (int i = 0; i < heightMapSize; i++) heightMap[i] = (byte)random.Next(0, 30); GenerateTerrainMesh(); effect = new BasicEffect(Game.GraphicsDevice); effect.VertexColorEnabled = true; } #endregion #region GenerateTerrainMesh() private void GenerateTerrainMesh() { //Vamos definir o número de vértices e triângulo para renderizar todos eles numVertices = vertexCountX * vertexCountZ; numTriangles = (vertexCountX - 1) * (vertexCountZ - 1) * 2; //Criando nosso índices int[] indices = GenerateTerrainIndices(); VertexPositionColor[] vertices = GenerateTerrainVertices(indices); //Vamos criar a declaração dos vértices VertexDeclaration decl = new VertexDeclaration(VertexPositionColor.VertexDeclaration.GetVertexElements()); // Cria o buffer de vertices vertexBuffer = new VertexBuffer(GraphicsDevice, decl, numVertices, BufferUsage.WriteOnly); //Adiciona nossos vértices ao Buffer vertexBuffer.SetData(vertices);

//Cria o Buffer de índices
indexBuffer = new IndexBuffer(Game.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);

//Adiciona nossos índices ao Buffer
indexBuffer.SetData(indices);
}
#endregion

#region GenerateTerrainIndices()
private int[] GenerateTerrainIndices()
{
//Cria todos os índices, como cada triângulo possui 3 vértices
int numIndices = numTriangles * 3;
int[] indices = new int[numIndices];

int indicesCount = 0;

//Cria dois indices no mesmo segmento de indices e outro indice no próximo segmento
//Faz isso 2 vezes, com dois triângulos formando um quadrado
for (int i = 0; i < (vertexCountZ - 1); i++) { for (int j = 0; j < (vertexCountX - 1); j++) { int index = j + i * vertexCountZ; indices[indicesCount++] = index; indices[indicesCount++] = index + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX + 1; indices[indicesCount++] = index + vertexCountX; indices[indicesCount++] = index; } } return indices; } #endregion #region GenerateTerrainVertices() private VertexPositionColor[] GenerateTerrainVertices(int[] terrainIndices) { float halfTerrainWidth = (vertexCountX - 1) * blockScale * 0.5f; float halfTerrainDepth = (vertexCountZ - 1) * blockScale * 0.5f; int vertexCount = 0; VertexPositionColor[] vertices = new VertexPositionColor[vertexCountX * vertexCountZ]; // Define a posicao dos vertices e a coordenada de textura for (float i = -halfTerrainDepth; i <= halfTerrainDepth; i += blockScale) { for (float j = -halfTerrainWidth; j <= halfTerrainWidth; j += blockScale) { vertices[vertexCount].Position = new Vector3(j, heightMap[vertexCount] * heightScale, i); vertices[vertexCount].Color = Color.Yellow; if((int)heightMap[vertexCount] < 10) vertices[vertexCount].Color = Color.Green; if ((int)heightMap[vertexCount] > 20)
vertices[vertexCount].Color = Color.Red;

vertexCount++;
}

}

return vertices;
}
#endregion

#region Update()
public override void Update(GameTime gameTime)
{
effect.View = cameraManager.ActiveCamera.View;
effect.Projection = cameraManager.ActiveCamera.Projection;
base.Update(gameTime);
}
#endregion

#region Draw()
public override void Draw(GameTime gameTime)
{

//Adicionamos o nossos indices e vertices ao nosso dispositivso para desenhar
Game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
Game.GraphicsDevice.Indices = indexBuffer;

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
//Adiciona e aplica o efeito
pass.Apply();
//Desenha a lista de linhas
Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, numVertices, 0, numTriangles);
}

base.Draw(gameTime);
}
#endregion
}
}

quinta-feira, 28 de julho de 2011

XNA 4.0 - Parte 6 Terrenos

Vamos falar aqui sobre o básico sobre terrenos, será um terreno específico, de campo aberto, quadrado e com uma altura, depois vamos aprender como importar terrenos a partir de um arquivo. Esses terrenos serão no formato de arquivo .RAW, são imagens preto e branco de 8-bits e o arquivo não tem cabeçalho, então são apenas os bits e que serão usados para saber a altura de cada posição no mundo.

Para fazer esse tipo de mapa usaremos uma série de triângulo, quando criamos os nossos eixos 3d usamos primitivas do tipo PrimitiveType.LineList e agora usaremos PrimitiveType.TriangleList, uma lista de triâmgulos que criarão o nosso mundo.

Nos utilizaremos tambem um outro tipo de métodro Draw, substituiremos o DrawPrimitives para DrawIndexedPrimitives, agora alem de desenhar os triângulos cara triângulo terá um índice para cada vértice, então cada triângulo terá 3 índices. Vou usar mais umas imagens para ilustrar melhor, caso queiramos desenhas 2 triângulos teremos:


Veja que precisamos de 6 vértices para definir apenas 2 triângulos, os vétices 2 e 3 são definidos 2 vezes o que seria muito ruim, nossa placa de video pediria arrego se para cada 2 triângulos tivessemnos que devinir 2 vértices, um por cima do outro. O uso de índices nos ajuda a criar uma série de triângulos sem que seja preciso definir o mesmo vértice 2 vezes.


Viu?, anteriormente teriamos de definir 6 vértices, 3 para cada triângulo, agora definimos 6, um a menos e sem repetiçoes pois usamos 5 índices que são pontos únicos, para entender melhor:


Veja que temos 12 triângulo, e apenas 12 índices, se precisassemos delarar todos os vértices teriamos 36 no total, veja que se pegarmos um vértice no meio, esse vértice compartilha-se com 8 triângulos e que precisariam ser declarados 8 vezes.

Então para desenharmos nossa malha de terreno precisamos calcular os indices e depois criar os vértices, sem que precisem ser escritos varias vezes.

Ainda nesse capitulo teremos: Mostrar o mundo em Modo WireFrame, carregar mapa de altura, Texturas, Efeito Básico, Efeito Avançado.

quarta-feira, 27 de julho de 2011

XNA 4.0 - Parte 5 Integrando tudo

Vamos agora integrar tudo, fazer com que nossa câmera ganhe vida, para isso vamos configurar nossos objetos. Abra o arquivo "Game1.cs", adicione duas novas variáveis como a seguir: Será preciso adicionar a seguinte using

using Tutorial1.GameBase.Cameras;

CameraManager cameraManager;
MouseCamera camera1;

No método LoadContent() adicione

cameraManager = new CameraManager();
camera1 = new MouseCamera(aspectRatio);

Continuando em loadContent(), agora vamos aprender uma coisa nova, os componentes e os serviços do XNA. Um serviço é uma aréa especial onde tudo o que é adicionado pode ser recuperado por uma outra classe. Vamos ao exemplo para melhor entendimento, adicione a seguinte linhas:

Services.AddService(typeof(CameraManager), cameraManager);

Adicionamos um serviço do tipo CameraManager e adicionamos um onjeto a esse serviço, que é nossa cameraManager. Então a partir de agora uma outra classe pode requisitar esse serviço, e tudo que adicionarmos no nosso cameraManager estará visível para essa classe, inclusive posteriores alterações. Após isso adicione o seguinte código:

camera1.SetLookAt(new Vector3(5.0f, 5.0f, 5.0f), Vector3.Zero, Vector3.Up);
camera1.NearPlane = 1.0f;
camera1.FarPlane = 100.0f;
camera1.Fovy = MathHelper.ToRadians(45.0f);

cameraManager.Add("Camera1", camera1);

Então configuramos e adicionamos uma câmera ao nosso cameraManager, então todas as outras classes podem acessar essa camera se ela quiser.

No método Update() apague os imputs que tinhamos feito anteriormente e adicione o seguinte:

Vector3 moveVector = new Vector3(0, 0, 0);
float timeDifference = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;

A variável moveVector é o que vai nos dizer a direção que nossa câmera vai se mover. e a timeDifference é uma variável que será usada pelo mouse. Adicione os seguites métodos de imput mais a atualização e inserção do addToCameraPosition seguida do Update:

KeyboardState keyState = Keyboard.GetState();
GamePadState padState = GamePad.GetState(PlayerIndex.One);

if (keyState.IsKeyDown(Keys.W))
moveVector += new Vector3(0, 0, -1);
if (keyState.IsKeyDown(Keys.S))
moveVector += new Vector3(0, 0, 1);
if (keyState.IsKeyDown(Keys.D))
moveVector += new Vector3(1, 0, 0);
if (keyState.IsKeyDown(Keys.A))
moveVector += new Vector3(-1, 0, 0);
if (keyState.IsKeyDown(Keys.Q))
moveVector += new Vector3(0, 1, 0);
if (keyState.IsKeyDown(Keys.Z))
moveVector += new Vector3(0, -1, 0);
if (keyState.IsKeyDown(Keys.Enter))
camera1.Shake(0.2f, 1.0f);

moveVector += new Vector3(0, padState.ThumbSticks.Left.Y, padState.ThumbSticks.Left.X);

cameraManager.ActiveCamera.addToCameraPosition(moveVector * timeDifference);

cameraManager.ActiveCamera.Update(gameTime);

Se você já pegou os macetes de programar em XNA vai saber que usaremos aas teclas W e S para nos movermos no eixo Z, as teclas A e D para nos movermos no eixo X, Q e Z no eixo Y e por fim que a tecla ENTER tremerá a tela.

Se o Mouse guiará nossa câmera então ele não pode deixar a posição central da tela, então para sabermos se ele se mecheu precisamos pegar onde ele estava e comparar com onde ele deveria estar. Adicione a seguinte variavel, lá em cimão, embaixo de MouseCamera camera1;

MouseState originalMouseState;

No método LoadContent(), denovo onde tem uma referência ao camera1 adicione

Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
originalMouseState = Mouse.GetState();

Na primeira linha travamos o mouse na posição central da tela e na segunda pegamos o atual estado do mouse. Atenção, como o mouse está travado teremos que usar ALT+F4 para fechar a janela do jogo. Agora no método Update() cole o seguinte código abaico de float timedifference...

MouseState currentMouseState = Mouse.GetState();
if (currentMouseState != originalMouseState)
{
float xDifference = currentMouseState.X - originalMouseState.X;
float yDifference = currentMouseState.Y - originalMouseState.Y;
camera1.LeftrightRot -= camera1.RotationSpeed * xDifference * timeDifference;
camera1.UpdownRot -= camera1.RotationSpeed * yDifference * timeDifference;
Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
}

O que esse pedaço de código faz, é pegar a diferença de onde o mouse está, onde deferia estar e depois recoloca o mouse no centro da tela, aqui temos que tomar cuidado, pois esse pedaço de código só funciona se a camera for do tipo MouseCamera, senão não deve executar mas não vamos nos preocupar com isso agora. Terminamos o arquivo "Game1.cs". Agora vamos para o arquivo "Terrain.cs".

Adicione uma variável global para cameraManager

CameraManager cameraManager;

Agora vamos resgatar esse componemte, no método Load() digite:

cameraManager = Game.Services.GetService(typeof(CameraManager)) as CameraManager;

Resgatamos nosso Componente, agora no método Update() coloque

effect.View = cameraManager.ActiveCamera.View;
effect.Projection = cameraManager.ActiveCamera.Projection;

Rode o programa e teste o teclado e o Mouse.

Xna 4.0 - Parte 5 - CameraManager

Crie uma nova Câmera na pasta Cameras. Chamada de CameraManager.

Essa classe vai receber todas as câmeras que criarmos e nos dará a possibilidade de trabalharmos mais facimente com as nossas Câmeras. É bem simple e como o código está totalmente comentado vou colocar a classe direto aqui, no próximo Tuto vou ensinar a controlar a câmera usando teclado e mouse:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Tutorial1.GameBase.Cameras
{
public class CameraManager
{
#region Variaveis
// Armazena a Câmera ativa
int activeCameraIndex;
CameraBase activeCamera;

// Lista contendo todas as cameras
SortedList cameras;
#endregion

#region Properties

// Indice da camera ativa
public int ActiveCameraIndex
{
get
{
return activeCameraIndex;
}
}

public CameraBase ActiveCamera
{
get
{
return activeCamera;
}
}

public CameraBase this[int index]
{
get
{
return cameras.Values[index];
}
}

public CameraBase this[string id]
{
get
{
return cameras[id];
}
}

public int Count
{
get
{
return cameras.Count;
}
}
#endregion

#region Construtor

public CameraManager()
{
cameras = new SortedList(4);
activeCameraIndex = -1;
}
#endregion

#region Metodos
//Defini qual é a camera ativa
public void SetActiveCamera(int cameraIndex)
{
activeCameraIndex = cameraIndex;
activeCamera = cameras[cameras.Keys[cameraIndex]];
}
//Defini qual é a camera ativa
public void SetActiveCamera(string id)
{
activeCameraIndex = cameras.IndexOfKey(id);
activeCamera = cameras[id];
}

//remove todas as Câmeras e reseta o método
public void Clear()
{
cameras.Clear();
activeCamera = null;
activeCameraIndex = -1;
}

//Adiciona uma Câmera
public void Add(string id, CameraBase camera)
{
cameras.Add(id, camera);

if (activeCamera == null)
{
activeCamera = camera;
activeCameraIndex = cameras.IndexOfKey(id);
}
}

//Remove uma Câmera
public void Remove(string id)
{
cameras.Remove(id);
}
#endregion
}
}

XNA 4.0 - Parte 5, Camera livre.

Vamos agora criar uma câmera livre, que pode ir a qualquer lugar do mapa e é controlada por mouse e teclado. Crie uma classe MouseCamera dentro da pasta Cameras, já foi dito como se cria classes. Vamos adicionar using Microsoft.Xna.Framework;

A classe MouseCamera extende de BaseCamera então nossa classe está assim:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Tutorial1.GameBase.Cameras
{
class MouseCamera : CameraBase
{

}
}

Como a classe MouseCamera extende de BaseCamera, que é publica, então nossa nova classe será publica sem precisar especificarmos.

Nossa classe precisa de uma variável para definir o quando ela está rodando para os lados e outra para cima e precisa tambem de 2 constantes, uma de velocidade da câmera e outra de velocidade de rotação.

float leftrightRot = MathHelper.PiOver2;
float updownRot = -MathHelper.Pi / 10.0f;
const float rotationSpeed = 0.3f;
const float moveSpeed = 3.0f;

Use o procedimento "Encapsulate Field" para as 4, as duas ultimas que são constantes não terão o método set{}, pois são constantes e esses valores não devem mudar.

Vamos agora criar o método construtor da nossa classe.

public MouseCamera(float aspectRatio)
: base()
{
SetLookAt(new Vector3(8.0f, 0.0f, 0.0f), Vector3.Zero, Vector3.Up);
SetPerspectiveFov(MathHelper.ToRadians(this.Fovy), aspectRatio, this.NearPlane, this.FarPlane);
}

Nós já passamos por essas linhas algumas vezes e já sabemos o que elas fazem, cria a posição de visão e tudo o que será visto. E pra finalizar vamos sobreescrever os métodos Update() e addcameraPosition(), usaremos "override" para sobreescrever.

public override void Update(GameTime gametime)
{
Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);

Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
Vector3 cameraFinalTarget = Position + cameraRotatedTarget;

Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);
Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

SetLookAt(new Vector3(Position.X, Position.Y, Position.Z), cameraFinalTarget, cameraRotatedUpVector);
base.Update(gametime);
}

E aqui novamente já sabemos o que cada linha faz. A primeira linha cria a rotação nos eixos X e Y e o restante das linhas atualizão a câmera. Agora o método addToCameraPositio()

public override void addToCameraPosition(Vector3 vectorToAdd)
{
Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);
Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation);
Position += moveSpeed * rotatedVector;
}

Esse método recebe um valor e atualiza a posição da câmera de acordo com o que é passado.

O código de Nossa Camêra fica assim:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Tutorial1.GameBase.Cameras
{
class MouseCamera : CameraBase
{

#region Variáveis

float leftrightRot = MathHelper.PiOver2;
float updownRot = -MathHelper.Pi / 10.0f;
const float rotationSpeed = 0.3f;
const float moveSpeed = 3.0f;
#endregion

#region Propriedades

public float LeftrightRot
{
get { return leftrightRot; }
set { leftrightRot = value; }
}

public float UpdownRot
{
get { return updownRot; }
set { updownRot = value; }
}

public float RotationSpeed
{
get { return rotationSpeed; }
}

public float MoveSpeed
{
get { return moveSpeed; }
}

#endregion

#region Método Construtor da Classe
public MouseCamera(float aspectRatio)
: base()
{
SetLookAt(new Vector3(8.0f, 0.0f, 0.0f), Vector3.Zero, Vector3.Up);
SetPerspectiveFov(MathHelper.ToRadians(this.Fovy), aspectRatio, this.NearPlane, this.FarPlane);
}
#endregion

#region Métodos Update e addToCameraPosition
public override void Update(GameTime gametime)
{
Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);

Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
Vector3 cameraFinalTarget = Position + cameraRotatedTarget;

Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);
Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);

SetLookAt(new Vector3(Position.X, Position.Y, Position.Z), cameraFinalTarget, cameraRotatedUpVector);
base.Update(gametime);
}

public override void addToCameraPosition(Vector3 vectorToAdd)
{
Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);
Vector3 rotatedVector = Vector3.Transform(vectorToAdd, cameraRotation);
Position += moveSpeed * rotatedVector;
}
#endregion

}
}

terça-feira, 26 de julho de 2011

XNA 4.0 - Parte 5+ - Criando Nossa CâmeraBase

Adicione uma pasta a GameBase e chame-a de "Cameras", adicione um novo item a "Cameras", selecione Class e dê o nome de "CameraBase.cs". Como falamos anteriormente, essa classe vai ser a base para todas as outras então vamos alteralá para uma classe pública e do tipo abstrata, ela não poderá ser implementada. Adicone a linha using Microsoft.Xna.Framework; O código fica assim:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Tutorial1.GameBase.Cameras
{
public abstract class CameraBase
{

}
}

Adicione uma nova variável, to tipo float com o nove fovy:

float fovy;

Clique com o botão direito em "fovy" escolha o menu "refactor" -> "Encapsulate Field". Dê aceite e depois aplique sem mudar nada. O que o VS fez foi criar dois métodos para que possamos trabalhar melhor essa variável, depois de foat fovy; adicione mais uma variavel, float aspectRatio; faça o mesmo procedimento acima. Faça isso mais duas vezes, agora com as vaiáveis nearPlane e farPlane, ambas do tipo float.

Depois dessas 4 variaveis adicione outras duas do tipo Vector3, são elas position e target. Use o sis tema de encapsulate field mostrado anteriormente nas duas.

Até agora são variáveis que você já sabia que existiam, logo na sequência você vai adicionar mais 3 variáveis do tipo Vector3, headingVec, strafeVec e upVec. Utilize de novo o "Encapsulate field". Essas 3 variáveis são variáveis de orientação, headingVec é a direção entre a posição da câmera e a posição do alvo. o VecUp é utilizado para mostra onde fica o "pra cima" e o strafe é a perpendicular a headingVec e upVec.

Agora uma jogada para organizar o código, na linha abaixo de "Vector3 upVec;" adicione o seguinte código: "#region Propriedades" e no final do ultimo método coloque: "#endregion" suba até o "#region Propriedades" e bem do lado esquerdo, ao lado de uma linha verde, terá um quadrado com o simbolo de menos, clique nele e todas as propriedades vão se esconder dentro de uma caixa cinza escrito Propriedades, faça o mesmo mas agora pegando todas as variáveis e troque o nome de "Propriedades" para "Variaveis". No final vou fornecer o arquivo inteiro.

Logo na sequência adicone mais um bloco "#region Matrizes e Controles" e depois um "endregion", não feche ainda. Vamos adicionar mais 3 variáveis:

protected bool needUpdateProjection;
protected bool needUpdateFrustum;
protected bool needUpdateView;

Essas variáveis são protegidas, então nada de fora da classe vai poder acessar elas diretamente, são usadas quando elas devem sofrer algum tipo de manipulação antes de receber algum valor. Agora mais duas variáveis do tipo matrix:

protected Matrix viewMatrix;
protected Matrix projectionMatrix;

Já pode fechar o bloco de código Matrixes e Controles. Na sequência vamos criar mais dois métodos, esses métodos só terão o "get" dentro deles.

public Matrix Projection
{
get
{
if (needUpdateProjection) UpdateProjection();
return projectionMatrix;
}
}

public Matrix View
{
get
{
if (needUpdateView) UpdateView();
return viewMatrix;
}
}

Dentro de cada método temos putros métodos no primeiro o método UpdateProjection(); e no segundo o método UpdateView(); vamos cria-los agora:

protected virtual void UpdateProjection()
{
//Cria a matriz de visão da perspectiva
projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f), aspectRatio, 0.1f, 10.0f);
needUpdateProjection = false;
needUpdateFrustum = true;
}


protected virtual void UpdateView()
{
viewMatrix = Matrix.CreateLookAt(position, target, upVec);
needUpdateView = false;
needUpdateFrustum = true;
}

Nós ja os vimos anteriormente, agora mudamos nossas variavéis para que atualizem ou deixem de atualizar a nossa matriz View e Projection.

Agora vamos criar mais dois métodos para inicializar nossas variaávieis e definir corretamente nossas matrizes

//Atribui Projeção a Camera
public void SetPerspectiveFov(float fovy, float aspectratio, float nearPlane, float farPlane)
{
this.fovy = fovy;
this.aspectRatio = aspectratio;
this.nearPlane = nearPlane;
this.farPlane = farPlane;
needUpdateProjection = true;
}

//Configura a visão da Camera
public void SetLookAt(Vector3 cameraPos, Vector3 cameraTarget, Vector3 CameraUp)
{
this.position = cameraPos;
this.target = cameraTarget;
this.upVec = CameraUp;
//Calcura os eixos da Camera
headingVec = cameraTarget - cameraPos;
headingVec.Normalize();
upVec = CameraUp;
strafeVec = Vector3.Cross(headingVec, upVec);
needUpdateView = true;
}

E para finalizar mais dois métodos, um que atualiza tudo o que foi feito e outro para atualizar a posição da câmera, assim como target e fovy.

public virtual void Update(GameTime gametime)
{
UpdateView();
}

public virtual void addToCameraPosition(Vector3 vectorToAdd)
{
//A implementar
}

o método addToCameraPosition(Vector3 vectorToAdd) só tem uma linha //A implementar, o que significa que teremos que definilo na hora de criar os outros tipos de câmera. Meu jogo vai ter "mapas" de todo o mundo e em alguns lugares existem terremotos, tremores de terra, mas você pode usar as seguintes implementações pra tremer a câmera para representar que o personagem levou um tiro, ou ocorreu uma explosão... O arquivo completo.

No caso do terremoto é mais fácil mover a câmera do que o mundo todo. No incio das variáveis declare:

//Variáveis do Terremoto
protected static readonly Random random = new Random(); // Criará um número aleatório
private bool shaking = false; //Controle se já esta tremendo
private float shakeMagnitude; //Magnitude do Terremoto
private float shakeDuration; //Duração do Terremoto
private float shakeTimer; //Timer de terremotos
private Vector3 shakeOffset; //Vector de distancia

Dentro do método UpdateView adicione

if (shaking)
{
position += shakeOffset;
target += shakeOffset;
}

No método Update() insira:

// Se estamos tremendo
if (shaking)
{
// Ajusta nosso timer de acordo com o tempo passado
shakeTimer += (float)gametime.ElapsedGameTime.TotalSeconds;

// Se estivermos no limite de tempo desliga o terremoto
if (shakeTimer >= shakeDuration)
{
shaking = false;
shakeTimer = shakeDuration;
}

// Compute our progress in a [0, 1] range
float progress = shakeTimer / shakeDuration;

// Compute our magnitude based on our maximum value and our progress. This causes
// the shake to reduce in magnitude as time moves on, giving us a smooth transition
// back to being stationary. We use progress * progress to have a non-linear fall
// off of our magnitude. We could switch that with just progress if we want a linear
// fall off.
float magnitude = shakeMagnitude * (1f - (progress * progress));

// Generate a new offset vector with three random values and our magnitude
shakeOffset = new Vector3(NextFloat(), NextFloat(), NextFloat()) * magnitude;
}

Por fim adicione mais 2 métodos:

public void Shake(float magnitude, float duration)
{
// Estamos no meio de um terremoto
shaking = true;

// Guarda nossa magnitude e Duração
shakeMagnitude = magnitude;
shakeDuration = duration;

// Reseta nosso Timer
shakeTimer = 0f;
}

//Gera um número entre -1 e 1;
private float NextFloat()
{
return (float)random.NextDouble() * 2f - 1f;
}

E o código final fica assim:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Tutorial1.GameBase.Cameras
{
public abstract class CameraBase
{
#region Variáveis

//Variáveis do Terremoto
protected static readonly Random random = new Random(); // Criará um número aleatório
private bool shaking = false; //Controle se já esta tremendo
private float shakeMagnitude; //Magnitude do Terremoto
private float shakeDuration; //Duração do Terremoto
private float shakeTimer; //Timer de terremotos
private Vector3 shakeOffset; //Vector de distancia

//Parametros para a projeção
float fovy;
float aspectRatio;
float nearPlane;
float farPlane;

//posição e alvo
Vector3 position;
Vector3 target;

//Vetore de orientação
Vector3 headingVec;
Vector3 strafeVec;
Vector3 upVec;
#endregion

#region Propriedades
public Vector3 Target
{
get { return target; }
set { target = value; }
}

public Vector3 Position
{
get { return position; }
set { position = value; }
}

public float FarPlane
{
get { return farPlane; }
set { farPlane = value; }
}

public float NearPlane
{
get { return nearPlane; }
set { nearPlane = value; }
}

public float AspectRation
{
get { return aspectRatio; }
set { aspectRatio = value; }
}

public float Fovy
{
get { return fovy; }
set { fovy = value; }
}
#endregion

#region Matrizes e Controles

//Controles/Flags
protected bool needUpdateProjection;
protected bool needUpdateFrustum;
protected bool needUpdateView;
//Matrizes
protected Matrix viewMatrix;
protected Matrix projectionMatrix;

#endregion

#region Atualizar View e Projection
//Obtem Matriz de Projeção
public Matrix Projection
{
get
{
if (needUpdateProjection) UpdateProjection();
return projectionMatrix;
}
}
//Obtem a Matriz de Visão da CAmera
public Matrix View
{
get
{
if (needUpdateView) UpdateView();
return viewMatrix;
}
}

//Atualiza a matriz de projeção Perspectiva
protected virtual void UpdateProjection()
{
//Cria a matriz de visão da perspectiva
projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f), aspectRatio, 0.1f, 10.0f);
needUpdateProjection = false;
needUpdateFrustum = true;
}

//Atualiza a visão da Camera
protected virtual void UpdateView()
{
if (shaking)
{
position += shakeOffset;
target += shakeOffset;
}
viewMatrix = Matrix.CreateLookAt(position, target, upVec);
needUpdateView = false;
needUpdateFrustum = true;
}
#endregion

#region Defini nossos View e Projection
//Atribui Projeção a Camera
public void SetPerspectiveFov(float fovy, float aspectratio, float nearPlane, float farPlane)
{
this.fovy = fovy;
this.aspectRatio = aspectratio;
this.nearPlane = nearPlane;
this.farPlane = farPlane;
needUpdateProjection = true;
}

//Configura a visão da Camera
public void SetLookAt(Vector3 cameraPos, Vector3 cameraTarget, Vector3 CameraUp)
{
this.position = cameraPos;
this.target = cameraTarget;
this.upVec = CameraUp;
//Calcura os eixos da Camera
headingVec = cameraTarget - cameraPos;
headingVec.Normalize();
upVec = CameraUp;
strafeVec = Vector3.Cross(headingVec, upVec);
needUpdateView = true;
}
#endregion

#region Update, addToCameraPosition, Shack e nemDouble
//Atualiza todos os Nossos componentes
public virtual void Update(GameTime gametime)
{
// Se estamos tremendo
if (shaking)
{
// Ajusta nosso timer de acordo com o tempo passado
shakeTimer += (float)gametime.ElapsedGameTime.TotalSeconds;

// Se estivermos no limite de tempo desliga o terremoto
if (shakeTimer >= shakeDuration)
{
shaking = false;
shakeTimer = shakeDuration;
}

// Compute our progress in a [0, 1] range
float progress = shakeTimer / shakeDuration;

// Compute our magnitude based on our maximum value and our progress. This causes
// the shake to reduce in magnitude as time moves on, giving us a smooth transition
// back to being stationary. We use progress * progress to have a non-linear fall
// off of our magnitude. We could switch that with just progress if we want a linear
// fall off.
float magnitude = shakeMagnitude * (1f - (progress * progress));

// Generate a new offset vector with three random values and our magnitude
shakeOffset = new Vector3(NextFloat(), NextFloat(), NextFloat()) * magnitude;
}

UpdateView();
}

//Atualiza a nossa posição de câmera, assim como target, fovy...
public virtual void addToCameraPosition(Vector3 vectorToAdd)
{
//A implementar - Devemos implementar na hora de criar os diferentes tipos de câmera
}

public void Shake(float magnitude, float duration)
{
// Estamos no meio de um terremoto
shaking = true;

// Guarda nossa magnitude e Duração
shakeMagnitude = magnitude;
shakeDuration = duration;

// Reseta nosso Timer
shakeTimer = 0f;
}

//Gera um número entre -1 e 1;
private float NextFloat()
{
return (float)random.NextDouble() * 2f - 1f;
}
#endregion
}
}

XNA 4.0 - Parte 5 - Câmeras na teoria e na prática.

O que é uma câmera, uma câmera o jogo? Seria a nossa tela de jogo, nós estariamos "filmando" tudo dentro do jogo e depois é repassado ao nosso monitor.

Uma câmera tem sua posição no espaço, o que estamos filmando, teriamos tambem o nosso ângulo de visão, quando damos zoom o nosso ângulo se fecha, não é mesmo?

E na prática, o que é Câmera no XNA? Nos tutoriais passados já passamos por algo parecido a uma câmera. Lembram do "effect.view"? Pois bem, talvez não tenha mencionado mas tudo que desenharmos no XNA estará ligado ao seu próprio efeito, então cada objeto terá o seu effect.view, então teriamos uma centena de views para configurar e para que tudo ficasse certinho no mundo teriamos de configurar tudo igual, e caso mudassemos a posição de nossa câmera teriamos de mudar a posição em todos os objetos.

Até porque não existem câmeras, você pode repassar diferentes valores aos effects.view e effect.World e esses objetos se desenhariam de formas diferentes em nossa tela, seria um caos total. Ao criarmos o que seria nossa câmera acabariamos passando aos nosso objetos um único valor, então organizariamos todos esses valores e destribuiriamos os objetos de forma que eles fiquem iguais e coêrentes com o mundo que queremos criar.

Agora o que é Câmera na linha de código. Nós ja declaramos os seguintes valores no effect no arquivo "terrain.cs" e foram:

View: Passamos a ele tem Vetores, um da Posição, um do alvo, e um indicano onde é o "pra cima".

Projection: Passamos uma matriz do tipo CreatePerspectiveFieldOfView e passamos nosso ângulo de visão, o aspectoRation, dicisão da largura da tela pela altura para que tudo pareça normal, plano proximo e o plano distante, que a area que será desenhada, ignorando tudo que esteja antes e depois desses dois planos.

Assim criariamos uma câmera mas seria importante adicionar elementos de atualização. Vamos pensar mais a frente, o meu jogo, SimFarm, terá uma camera no estilo RTS, uma câmera que dê uma visão de cimas mas eu tambem gostária de ter uma câmera para explorar o meu mundo e uma que siga uma vaca em especifico ou um porco e etc... Então eu teria de criar 3 tipos de câmera. Já vimos que uma câmera tem vários elementos e que devem se repetir, então para facilitar o ato de programar usaremos herança e para isso criaremos uma classe básica, BaseCamera e depois criaremos os 3 tipos de câmera, uma câmera RTS, uma Câmera livre, estilo jogo de nava espacial e uma câmera em terceira pessoa que derivam da classe CameraBase e é ai que começaremos a construir a CameraBase...

XNA 4.0 - Imput de teclado e de Controle do XBOX

Tutorialzinho básico sobre Imputs. No meu exemplo vou fazer com que o nosso mundo gire para a esquerda ou para a direita de acordo com a tecla que é pressionada.

Vamos abrir o arquivo "Terrain.cs" e adicionar o seguinte método depois do método Draw().

public void addRotation(float amount)
{
effect.World *= Matrix.CreateRotationY(amount);
}

Esse método recebe um argumento, "amount", que será a velocidade em que o eixo Y ratacionará, caso esteja confiante você pode mudar a "Matrix.CreateRotationY" para qualquer tipo de rotação que você queira.

Agora no Arquivo "Game1.cs", vá até o método Update() e adicione o seguinte código antes de base.Update();

//Pega o estado das teclas de nosso teclado de do pad do XBOX 360
KeyboardState keyState = Keyboard.GetState();
GamePadState padState = GamePad.GetState(PlayerIndex.One);

//Se a Tecla Right ou o botão RigthTrigger estiverem pressionadas então ordenaremos ao objeto
//terrain executar o metodo addRotation e passamos um valor como argumento
if(keyState.IsKeyDown(Keys.Right) || padState.IsButtonDown(Buttons.RightTrigger))
terrain.addRotation(-0.01f);

//O mesmo de cima mas agora para a esquerda
if (keyState.IsKeyDown(Keys.Left) || padState.IsButtonDown(Buttons.LeftShoulder))
terrain.addRotation(0.01f);

As linhas estão comentadas e o que elas fazem são bem intuitivas, "EstadoDaTecla.EstaPressionada(Tecla.Direita)". "||" seguinifica "ou"´, se a tecla está pressionada ou se um botão está pressionado.

Você pode alterar o "0.01f" que passamos como parametro para qualquer outro valos, diminuindo ou aumentando a velocidade de giro.

A classe Terrain atual:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Tutorial1.GameBase.Shapes
{
class Terrain : DrawableGameComponent
{
//Variaveis de Renderização
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;
BasicEffect effect;

public Terrain(Game game)
: base(game)
{

}

public void Load()
{
//Cria o efeito que será usado para desenhar os eixos
effect = new BasicEffect(Game.GraphicsDevice);

//Aspect Ratio da nossa janela Largura/Altura
float aspectRatio = (float)Game.GraphicsDevice.Viewport.Width / (float)Game.GraphicsDevice.Viewport.Height;

//Tamanho dos eixos 3D
float axisLenght = 2f;

//Numero de Vertices Usados
int vertexCount = 22;

//Os indices de nossas linhas
int[] indices = new int[6];

//Nossa declaração de Vertex
VertexDeclaration decl = new VertexDeclaration(VertexPositionColor.VertexDeclaration.GetVertexElements());

//Criando os nossos vertices
VertexPositionColor[] vertices = new VertexPositionColor[vertexCount];

//Vamos preencher nossos vertices e nossos indices
//Eixo x
vertices[0] = new VertexPositionColor(new Vector3(-axisLenght, 0.0f, 0.0f), Color.White);
vertices[1] = new VertexPositionColor(new Vector3(axisLenght, 0.0f, 0.0f), Color.White);

indices[0] = 0;
indices[1] = 1;

//Eixo y
vertices[2] = new VertexPositionColor(new Vector3(0.0f, -axisLenght, 0.0f), Color.Red);
vertices[3] = new VertexPositionColor(new Vector3(0.0f, axisLenght, 0.0f), Color.Red);

indices[2] = 2;
indices[3] = 3;

//Eixo z
vertices[4] = new VertexPositionColor(new Vector3(0.0f, 0.0f, -axisLenght), Color.White);
vertices[5] = new VertexPositionColor(new Vector3(0.0f, 0.0f, axisLenght), Color.White);

indices[4] = 4;
indices[5] = 5;

//Mais grades X
vertices[6] = new VertexPositionColor(new Vector3(-axisLenght, 0.0f, -2.0f), Color.White);
vertices[7] = new VertexPositionColor(new Vector3(axisLenght, 0.0f, -2.0f), Color.White);

vertices[8] = new VertexPositionColor(new Vector3(-axisLenght, 0.0f, -1.0f), Color.White);
vertices[9] = new VertexPositionColor(new Vector3(axisLenght, 0.0f, -1.0f), Color.White);

vertices[10] = new VertexPositionColor(new Vector3(-axisLenght, 0.0f, 1.0f), Color.White);
vertices[11] = new VertexPositionColor(new Vector3(axisLenght, 0.0f, 1.0f), Color.White);

vertices[12] = new VertexPositionColor(new Vector3(-axisLenght, 0.0f, 2.0f), Color.White);
vertices[13] = new VertexPositionColor(new Vector3(axisLenght, 0.0f, 2.0f), Color.White);

//Mais grades Y
vertices[14] = new VertexPositionColor(new Vector3(-2.0f, 0.0f, -axisLenght), Color.White);
vertices[15] = new VertexPositionColor(new Vector3(-2.0f, 0.0f, axisLenght), Color.White);

vertices[16] = new VertexPositionColor(new Vector3(-1.0f, 0.0f, -axisLenght), Color.White);
vertices[17] = new VertexPositionColor(new Vector3(-1.0f, 0.0f, axisLenght), Color.White);

vertices[18] = new VertexPositionColor(new Vector3(1.0f, 0.0f, -axisLenght), Color.White);
vertices[19] = new VertexPositionColor(new Vector3(1.0f, 0.0f, axisLenght), Color.White);

vertices[20] = new VertexPositionColor(new Vector3(2.0f, 0.0f, -axisLenght), Color.White);
vertices[21] = new VertexPositionColor(new Vector3(2.0f, 0.0f, axisLenght), Color.White);

//Preencher o vertexBuffer com os vértices
vertexBuffer = new VertexBuffer(Game.GraphicsDevice, decl, vertexCount, BufferUsage.WriteOnly);
vertexBuffer.SetData(vertices);

//Preenche o indexBuffer com os indices
indexBuffer = new IndexBuffer(Game.GraphicsDevice, IndexElementSize.SixteenBits, 20 * sizeof(int), BufferUsage.WriteOnly);
indexBuffer.SetData(indices);

//Criamos nossa câmera, com posição, alvo e indicando para câmera onde fica o "pra cima"
effect.View = Matrix.CreateLookAt(new Vector3(5.0f, 5.0f, 5.0f), Vector3.Zero, Vector3.Up);

//Criamos a nossa Projeção, com aspectRatio, nearPlane e farPlane
effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 15.0f);

effect.VertexColorEnabled = true;
}

public override void Draw(GameTime gameTime)
{
//Adicionamos o nossos indices e vertices ao nosso dispositivso para desenhar
Game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
Game.GraphicsDevice.Indices = indexBuffer;
//Essa linha procura por todos os efeitos que vamos aplicar aos eixos
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
//Adiciona e aplica o efeito
pass.Apply();

//Desenha a lista de linhas
Game.GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 0, 50);
}
base.Draw(gameTime);
}

public void addRotation(float amount)
{
effect.World *= Matrix.CreateRotationY(amount);
}
}
}

segunda-feira, 25 de julho de 2011

XNA 4.0 - Criar jogos para PC e XBOX360 - Parte 3++

Se pegarmos o código do último Tutorial e formos até a última linha do arquivo "Game1.cs" veremos que temos 194 linhas e tudo o que fizemos foi um simples sistemas de eixos, agora imagina se acrescentarmos câmeras, modelos 3D, Inteligência artificial, HUD, tela de opções e tudo o que tem a mais em um jogo... teriamos um inferno formado por linhas de código.

Então devemos melhorar nossa arumação. Vamos criar uma classe, a classe "terrain", essa classe vai cuidar tudo sobre o terreno e tudo o que for ligado a terreno estará dentro dessa classe. Encapsulamento. A partir de agora tudo o que fizermos terá sua classe especifica.

Clique com o botão direito na Solução "Tutorial1", -> "Add" -> "New Folder", dê o nome para essa paste de "GameBase", a seguir clique, de novo com o botão direito, sobre "GameBase" -> "Add" -> "New Folder" e coloque o nome "Shapes". Novamente só que agora sobre a pasta "Shapes" -> "Add" -> "New item". Selecione "Class" e dê o nome de "Terrain.cs".

Vai abrir o arquivo "Terrain.cs" e a seguir a primeira coisa a fazer é procurar a linha "class Terrain" e altera-la para

"public class Terrain : DrawableGameComponent"

Dizemos que nossa classe é publica e que ela "se desenha".

Vamos agora botar tudo o que é ligado ao terreno, volte ao arquivo "Game1.cs" e copie para o arquivo "Terrain.cs" as variáveis de renderização e depois apague-as do arquivo "Game1.cs": São elas VertexBuffer, IndexBuffer e BasicEffect.

O nosso arquivo "Terrain.cs" deve estar parecido com isso:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Tutorial1.GameBase.Shapes
{
public class Terrain : DrawableGameComponent
{
//Variaveis de Renderização
VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;
BasicEffect effect;
}

Volte ao arquivo "Terrain.cs" posicione o cursos depois do BasicEffect effect; aperte enter 2 vezes e crie um novo método como o a seguir:

public Terrain(Game game)
: base(game)
{

}

e depois outro método:

public void Load()
{

}

Vá ao arquivo "Game1.cs" no método LoadContent() e copie e depois apague tudo, menos a primeira linha. Então a linha do SpriteBatch fica onde esta.

Cole tudo no método Load() do arquivo "terrain.cs". Algumas linhas vão ficar sublinhadas em vermelho são elas:

effect = new BasicEffect(GraphicsDevice);
float aspectRatio = (float)GraphicsDevice.Viewport.Width / (float)GraphicsDevice.Viewport.Height;
vertexBuffer = new VertexBuffer(GraphicsDevice, decl, vertexCount, BufferUsage.WriteOnly);
indexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.SixteenBits, 20 * sizeof(int), BufferUsage.WriteOnly);

Como mudamos elas de lugar a propriedade "GraphicsDevice" para de funcionar pois só é acessível da classe que devia de Game, então para corrigir devemos alterar para "Game.GraphicsDevide" todas as entradas, aperte ctrl + H, coloque em find what: "GraphicsDevice" e em replace with "Game.GraphicsDevide". Depois "Replace All".

agora vamos criar outro método digide "public overrride" aperte espaço e vai aparecer varias opções de métodos para serem criados, escolha o método Draw() e dê enter, o método será criado automaticamente. Volte a "Game1.cs" e copie tudo o que está no método Draw() deixando apenas a linha "GraphicsDevice.Clear()" apague o resto e cole tudo dentro do método Draw() do arquivo "Terrain.cs". Você deve fazer a mesma alteração que foi feita acima para que tudo fique "Game.GraphicsDevice".

Agora para finalizar vamos fazer as chamadas no "Game1.cs". Adicione a seguinte entrada no final das linhas "using"

using Tutorial1.GameBase.Shapes;

Vai indicar ao nosso jogo onde esta a classe Terrain.

Adicione a seguinte variável:

Terrain terrain

No método Initialize adicione

terrain = new Terrain(this);

Estamos criando uma nova instância da classe Terraim e passando como argumento "this" referindo-se ao nosso jogo. Agora no Metodo LoadContent adicione

terrain.Load();

Carregar os efeitos do terrain, o effect e tudo o que tiver dentro de Load(0

E para finalizar adicione no método Draw()

terrain.Draw(gameTime);

Pronto, seu programa agora está pronto mas só faz as mesmas coisas de antes, mas agora dá pra saber tudo o que é ligado ao terreno.

Aqui esta os arquivos do ultimo tutorial:
http://www.megaupload.com/?d=D4SW3B3K