Segue a parte 1/3 do video da apresentacao no Riojug
em abril de 2009 sobre a criacao de jogos usando JavaFX
JavaFX para criação de Jogos 1/3 from Hamilton Lima on Vimeo.
Segue a parte 1/3 do video da apresentacao no Riojug
em abril de 2009 sobre a criacao de jogos usando JavaFX
JavaFX para criação de Jogos 1/3 from Hamilton Lima on Vimeo.
Temos grandes novidades neste preview:
Ufa … os novos graficos ainda nao estao lah, mas estao no forno !!
este eh o link para os arquivos do preview
http://svn.vacavitoria.com/cabecudinhos_1/cabecudinhos_1/preview/2009-03-18/
e este eh o video

Olas ! estamos quase terminando o jogo dos cabecudinhos, episodio I o ataque dos mosquitos ! as partes graficas estao saindo do forno, valeu Mariana !! as coisas estao se encaixando, e em breve vai rolar o envio de pontuacao para o servidor.
Coisas novas bem legais estao neste preview :
baixe o arquivo de webstart da implementacao atual
cabecudinhos_episodio_um.jnlp
curta o video do preview
e aproveite a moleza do codigo fonte disponivel no subversion
Uia ! implementei meus rabiscos de gravidade e não eh q funcionou !! hehehe
como tinha dito em javafx-planejando-gravidade são 3 classes envolvidas:
Para os catucadores de plantão segue o endereço do subversion
http://svn.vacavitoria.com/cabecudinhos_1/cabecudinhos_1/
E alguns fragmentos do codigo
esta eh a Scene em que usei a GravityManager
public class GameScene extends Scene, Updatable{
...
public var gravityManager: GravityManager;
// set the need info for the Scene work
public function setup(){
gameSoundtrack.play();
background =
lookup(BACKGROUND_ID) as NodeFromFXZ;
player1 =
lookup(PLAYER1_ID) as MovableChar;
player1.background = background;
player1.requestFocus();
gravityManager = GravityManager{
gravity: 5
content: [
GNode{
node: bind player1
}
]
}
}
public override function update(){
gravityManager.update();
}
override var content = [
NodeFromFXZ{
id: bind BACKGROUND_ID
source: "{__DIR__}media/background.fxz"
}
MovableChar{
id : bind PLAYER1_ID
source: "{__DIR__}media/boy.fxz"
x: 50
y: 450
speed: 3
colision: ["casa1", "casa2", "casa3","casa4", "casa5", "casa6", "casa7", "casa8",
"caixa1", "caixa2", "caixa3","caixa4", "caixa5", "caixa6",
"caixa7", "caixa7b", "caixa8", "caixa8b",
"left", "right", "bottom","top", "plataforma1"]
onMouseClicked: function(e){
}
onKeyReleasedDelegate: function(current:MovableChar, key: KeyCode){
if( key == KeyCode.VK_SPACE and current.hasColision){
fire.play();
gravityManager.addForce( current.id, Force {
y: -22;
});
}
}
onUpdate: function(current:MovableChar){
current.checkIfScrollBackground();
}
}
]
}
Segue o codigo das classes para o controle da gravidade
GravityManager
public class GravityManager extends Updatable{
public var gravity: Integer;
public var content: GNode[];
public function addForce(id:String, force: Force){
for( gNode in content){
if( gNode.node.id.equals(id) ) {
insert force into gNode.forces;
}
}
}
public override function update(){
// iterate over all gnodes
for( gNode in content){
var x: Integer = 0;
var y: Integer = gravity;
// iterate over all Force from the GNode
for( force in gNode.forces){
x = x + force.x as Integer;
y = y + force.y as Integer;
force.applyGravity(gravity);
if( force.isDisposable()){
delete force from gNode.forces;
}
}
// try to move the Node with the increase of x and y
gNode.node.moveX(x);
gNode.node.moveY(y);
}
}
}
Force.fx
public class Force {
public var x: Number;
public var y: Number;
public var percentGravity: Number = 0.15;
// reduce the x and y value with the gravity
public function applyGravity(gravity:Integer){
if( x < 0 ){
x = x + (gravity * percentGravity);
if( x > 0 ){
x = 0;
}
}
if( y < 0 ){
y = y + (gravity * percentGravity);
if( y > 0 ){
y = 0;
}
}
}
public function isDisposable():Boolean{
return x == 0 and y == 0;
}
}
GNode
public class GNode {
public var node: MovableChar;
public var forces: Force[];
}
E finalmente o video com o resultado saltitante ![]()
mor legal este site http://www.umpi.com.br/index.html
Nesta jornada de explorar JavaFx, e na migração do jogo que fizemos no evento do global game jam 2009, estou agora implementando a gravidade para o side scrolling.
Ou seja preciso de um mecanismo para manter os personagens no chão … e que possam dar uns pulinhos, e que não seja soh para cima possam ser pulinhos para os lados, vejam os rascunho que fiz no ônibus.
A idéia é simples, uma classe para gerenciar a gravidade contendo as lista de plataformas onde será necessário verificar a colisão, para segurar o personagem, uma lista de GNodes que vao ter uma lista de forças que vao incidir sobre e o Node propriamente dito.
cada item que vai representar um Força vai possuir x, y percentual de gravidade que sera aplicado, uma funcao applyGravity(g) e outra para ver se pode remover o objeto da lista, isDisposable()
a logica do update do gravity manager eh :
bem este é o plano, vamos ver se funciona
Opa a veja deu um espaço interessante para a divulgação dos jogos no Brasil !
http://veja.abril.com.br/040309/p_100.shtml
Fala taikodon o RPG espacial, e do lançamento do Zeebo o novo console brasileiro.
Pelo que estou estudando, entendi que ateh consigo ler arquivos externos ao programa usando a classe StreamReader, mas quando o build do projeto for feito vai ter dependência que o arquivo exista fora do executável e não tenho a menor idéia de como referenciar o sistema de arquivo do Xbox por exemplo.
Assim sendo tem a tal da pasta content no projeto do jogo, dando add em itens nesta pasta, eles podem ser carregados pelo ContentManager !! mor legal.
No meu caso preciso carregar arquivos de mapa que gerei que contém um monte de zeros e uns, algo do tipo :
111111111111111111111111111111
100000000001100000000000000001
101111111111101111111111111101
101111111111101111111111111101
100000000001100001100001100001
101111111101111101101101101111
101111111101111101101101101111
100000001101100001101101100001
entaum seguindo alguns tutoriais, entendi que precisava escrever um contentimporter, para que o xna soubesse como transformar meu belo arquivo em objetos usaveis.
segui o tutorial e criei um Content PipeLine Extension Library dentro da mesma solution do jogo

e criei a classe MapFilesImporter
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
using Microsoft.Xna.Framework.Content;
using System.IO;
namespace MapFilesContentPipeLine
{
[ContentImporter(".maze", DisplayName = "Map Files - ChikenMaze")]
public class MapFilesImporter : ContentImporter
{
public override Boolean[,] Import(string filename,
ContentImporterContext context)
{
FileStream stream = File.OpenRead(filename);
StreamReader reader = new StreamReader(stream);
List> buffer = new List
>();
string line = reader.ReadLine();
while (line != null)
{
List
foreach (char one in line)
{
bufferLine.Add(one == ’1′ ? true : false);
}
buffer.Add(bufferLine);
line = reader.ReadLine();
}
Boolean[,] result = new Boolean[buffer.Count, buffer[0].Count];
int row = 0;
int col = 0;
// copy data to the matrix
foreach (List
{
foreach (Boolean spot in onelist)
{
result[row, col] = spot;
col++;
}
row++;
col = 0;
}
return result;
}
}
}
Lembrando é claro de fazer referência ao projeto to content pipeline tanto no projeto do jogo, quanto nas referências da pasta content, isto me rendeu um tempão para perceber … hehehehe

dai tive a surpresa legal …
Error 1 Building content threw RankException: Cannot serialize multidimensional arrays.
po custava saber serializar arrays multidimensionais ?? =)
bem vou ter que mudar um pouco a estratégia de carregar os mapas, jah vi que vou precisar de um tipo de dados mais simplim que um array multidimensional de booleanos, pow mais simples que isso !! hehehehe, vou criar algo do tipo MapDefinition com as strings das linhas e um método para transformar na famigerada matriz de booleanos.
então refiz a classe de importar o formato de arquivo que tenho, novinha em folha !
comparando com a anterior, nesta mudei o display name para poder verificar se estava usando a nova implementação (danado desconfiado huahuahuuah) e passei a retornar List
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
using Microsoft.Xna.Framework.Content;
using System.IO;
namespace MapFilesContentPipeLine
{
[ContentImporter(".maze", DisplayName = "Map Files - ChikenMaze (List
public class MapFilesImporter : ContentImporter>
{
public override List
ContentImporterContext context)
{
FileStream stream = File.OpenRead(filename);
StreamReader reader = new StreamReader(stream);
List
string line = reader.ReadLine();
while (line != null)
{
buffer.Add(line);
line = reader.ReadLine();
}
return buffer;
}
}
}
com esta novidade precisei alterar um pouco a classe que gerencia os mapas para poder converter de List
public Map getMap(int level)
{
// ok ok in one line would be cooler =)
// Boolean[,] map = ListString2MatrixBoolean(scene.game.Content.Load>(getMapName(level)));
//
// but this way we can read easily at 4:30AM
//
string filename = getMapName(level);
List>(filename);
Boolean[,] map = ListString2MatrixBoolean(buffer);
return new Map(this.scene, map);
}
public Boolean[,] ListString2MatrixBoolean(List
{
Boolean[,] result = new Boolean[list.Count, list[0].Length];
int row = 0;
int col = 0;
foreach (string line in list) {
foreach (char one in line)
{
result[row, col] = (one == ’1′ ? true : false);
col++;
}
row++;
col = 0;
}
return result;
}
ultimo comentario para os desesperados que leram até o final, uma pena que minha mãe não programa em c# !! ela estaria lendo tudinho !! huahuahuhua
quando fizer referência ao arquivo a ser carregado nao inclua a extensão, eu estava buscando por maze_10_10.maze e recebendo o erro de “file not found”, resolvi fazer do mesmo jeito que estava fazendo com os arquivos de imagens e todos ficaram felizes, basta uasr o nome sem extensão.
olha que bela imagem da galinha andando num mapa carregado do arquivo !!

link para os tutoriais
Custom Content Importer with XNA 2.0
http://www.ziggyware.com/readarticle.php?article_id=166
Using a Custom Importer or Content Processor
http://msdn.microsoft.com/en-us/library/bb447743(XNAGameStudio.20).aspx
http://www.athanazio.com/2009/01/11/c-criando-um-labirinto-v1/
Fazer as conexões entre os elementos do labirinto foi uma coisa interessante, porque eu preciso de uma representação onde cada posição, seja parede ou celula vazia tenha a mesma largura para representar isto para cada item do labirinto criei uma matriz 3×3 onde a posição do centro representa a celula em si e as posições ao redor são as possiveis conexões com as proximas células, veja o desenho abaixo

o centro do quadro eh a celula atual sendo processada, e as caixa ao redor sao os possiveis vizinhos, note que para cara vizinho existe uma combinacao de modificadores para x e y, por exemplo o vizinho ao norte é 0 e -1 o do leste 1 e 0 e assim por diante.
alem disto para conectar ao vizinho armazenei tambem que coordenada na matriz de conexões é necessário atualizar para realizar a conexão, por exemplo para o norte a conexão é feita em 1,2 da matriz de conexões.
a matriz com estas definições está na classe de geração do maze
int[,] SearchNeighBorNumbers = { { 0, -1, 1, 2 }, { 1, 0, 0, 1 }, { 0, 1, 1, 0 }, { -1, 0, 2, 1 } };
desta forma preciso sortear somente que linha desta matriz vou usar para manipular o vizinho, e assim realizo as conexões. Antes que eu me esqueça ! além de mudar a matriz de conexões do vizinho é necessário fazer o mesmo na célula corrente e para encontrar a célula correta basta usar o mesmo deslocamento usado para encontrar o vizinho, dentro da matriz de conexões da celula atual, a partir da posicao 1,1 conforme este treco abaixo
current.conections[
1 + SearchNeighBorNumbers[neighborRow, NEIGHBOR_X],
1 + SearchNeighBorNumbers[neighborRow, NEIGHBOR_Y]] = OPEN;
algumas referencias para labirintos
tutorial sobre a geração do labirinto
http://www.mazeworks.com/mazegen/mazetut/index.htm
mais comentarios sobre o algoritmo
http://en.wikipedia.org/wiki/Maze#Generating_mazes
uma visao mais detalhada do Deep-first
http://en.wikipedia.org/wiki/Depth-first_search
Para o jogo que estou criando preciso de uns labirintos e como não achei algo que pudesse gerar para um arquivo texto, estou implementando o meu mesmo, com algumas leituras vi que o algoritmo mais fácil para este caso seria a através de uma pilha assim eu garanto que percorri todos os elementos do labirinto.
o algortimo consiste em escolher uma posicao aleatoria, e buscar os vizinhos nao visitados ao redor, encontrando um abre caminho ateh ele e empilha o mesmo, e vai fazendo isto até que não existam vizinhos não visitados, assim vai desempilhando para voltar no caminho e tentando achar vizinhos não visitados no caminho de volta.
Assim percorremos todo o mapa mas ainda ficam uns becos sem saída, como pode ver marcado neste desenho que fiz quando estava testando manualmente o algoritmo

o teste foi de 5×5 e marquei com bolinhas abobora os becos sem saída, na próxima versão do algoritmo vou guardar os becos sem saída e conectar com algum vizinho =) eh melhor fazzer andar em círculos do que encontrar um beco em saída, hehehe
Seguem os códigos fonte, do gerador e do programa que esta gravando num arquivo texto o maze gerado, que por sinal esta com algum problema ainda …
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace MazeGenerator
{
class Program
{
static void Main(string[] args)
{
MazeGen mazeGen = new MazeGen();
MazeGen.MazeCell[,] maze = mazeGen.generateMaze(5,5);
string fileName = “c:/work/maze” + DateTime.Now.ToBinary() + “.txt”;
FileStream stream = File.Open(fileName, FileMode.OpenOrCreate);
StreamWriter writer = new StreamWriter(stream);
for (int n = 0; n < maze.GetLength(0); n++)
{
for (int conn = 0; conn < 3; conn++)
{
for (int m = 0; m < maze.GetLength(1); m++)
{
writer.Write(maze[n, m].conections[conn, 0] == MazeGen.OPEN ? "_" : "*");
writer.Write(maze[n, m].conections[conn, 1] == MazeGen.OPEN ? "_" : "*");
writer.Write(maze[n, m].conections[conn, 2] == MazeGen.OPEN ? "_" : "*");
}
writer.WriteLine();
}
}
writer.Close();
}
}
}
MazeGen,cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace MazeGenerator
{
public class MazeGen
{
public const Boolean DEBUG = true;
public const int CLOSED = 0;
public const int OPEN = 1;
public const int NEIGHBOR_X = 0;
public const int NEIGHBOR_Y = 1;
public const int NEIGHBOR_CONNECTION_2OPEN_X = 2;
public const int NEIGHBOR_CONNECTION_2OPEN_Y = 3;
public const int POSSIBLE_NEIGHBORS = 4;
public static Random random = new Random();
public class MazeCell
{
public MazeCell(int X, int Y)
{
this.X = X;
this.Y = Y;
this.Visited = false;
}
public Boolean Visited = false;
public int X;
public int Y;
// north, east, south, west
public byte[,] conections = { { CLOSED, CLOSED, CLOSED },
{ CLOSED, OPEN, CLOSED },
{ CLOSED, CLOSED, CLOSED } };
public override string ToString()
{
string buffer = "";
foreach( byte one in conections){
buffer += one.ToString();
}
return X + "," + Y + " [" + buffer +"] ";
}
}
// used to random the neighbor cell
// { increment of X, increment of Y, connection to open in the neighbor }
int[,] SearchNeighBorNumbers = { { 0, -1, 1, 2 }, { 1, 0, 0, 1 }, { 0, 1, 1, 0 }, { -1, 0, 2, 1 } };
public MazeCell[,] generateMaze(int rows, int cols)
{
// fill the matrix
MazeCell[,] result = new MazeCell[rows, cols];
for (int n = 0; n < result.GetLength(0); n++)
{
for (int m = 0; m < result.GetLength(1); m++)
{
result[n, m] = new MazeCell(n, m);
}
}
int visited = 1;
int total = rows * cols;
Stack path = new Stack();
int x = random.Next(1, rows - 1);
int y = random.Next(1, cols - 1);
result[x, y].Visited = true;
path.Push(result[x, y]);
MazeCell current = result[x, y];
while (visited < total)
{
// try the unvisited neighbors
int neighborRow = random.Next(0, 3);
MazeCell neighbor = nextRandomUnvisited(result, current, neighborRow);
int tries = 1;
while (tries < POSSIBLE_NEIGHBORS && neighbor == null)
{
neighborRow ++;
if( neighborRow >= POSSIBLE_NEIGHBORS ){
neighborRow = 0;
}
neighbor = nextRandomUnvisited(result, current, neighborRow);
tries ++;
}
if (neighbor == null)
{
current = (MazeCell)path.Pop();
}
else
{
current.conections[
1 + SearchNeighBorNumbers[neighborRow, NEIGHBOR_X],
1 + SearchNeighBorNumbers[neighborRow, NEIGHBOR_Y]] = OPEN;
neighbor.Visited = true;
neighbor.conections[
SearchNeighBorNumbers[neighborRow, NEIGHBOR_CONNECTION_2OPEN_X],
SearchNeighBorNumbers[neighborRow, NEIGHBOR_CONNECTION_2OPEN_Y]] = OPEN;
if (DEBUG){Console.WriteLine(“from {0} to {1}”, current, neighbor);}
path.Push(neighbor);
current = neighbor;
visited++;
}
}
return result;
}
public MazeCell nextRandomUnvisited(MazeCell[,] result, MazeCell current, int neighborRow)
{
int testX = current.X + SearchNeighBorNumbers[neighborRow, NEIGHBOR_X];
int testY = current.Y + SearchNeighBorNumbers[neighborRow, NEIGHBOR_Y];
// check if neighbor exists
// if not will try again
if (testX >= 0 && testX < result.GetLength(0) && testY >= 0 && testY < result.GetLength(1))
{
if (!result[testX, testY].Visited)
{
return result[testX, testY];
}
}
return null;
}
}
}
Uai ! em ateh map scroll hehehe, bem parte das minhas reflexões sobre a forma de implementar o jogo estão se materalizando, criei o conceito de cena, contendo o gerenciador de eventos e deslocamento da tela para permitir o scroll da tela de fundo.
o primeiro detalhe eh a classe Scene em si
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace ChickenMaze
{
public abstract class Scene
{
public Game game;
public EventManager eventManager;
public int X;
public int Y;
public Scene(Game game, EventManager eventManager)
{
this.game= game;
this.eventManager= eventManager;
this.X = 0;
this.Y = 0;
}
public abstract void Initialize();
public abstract void LoadContent();
public abstract void Draw(GameTime gameTime, SpriteBatch spriteBatch);
}
}
que em alguns aspectos tentei manter o design semelhante a classe Game, com excessão do método Draw que adicionei o objeto spriteBatch sendo passado como parâmetro para evitar esta complexidade na cena atual, assim na classe Game principal do jogo um objeto scene é criado, e ao executar initialize, loadcontent e draw os respectivos metodos da Scene corrente serão chamados.
Esta abaixo eh a SceneMainMaze que determina os detalhes especificos do jogo, como sprite que vai ser usado como personagem, mapa e tratadores de eventos, note que a implementação dos eventos fica separada deste código.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace ChickenMaze
{
class SceneMainMaze : Scene
{
Sprite chicken;
Map map;
public SceneMainMaze(Game game, EventManager eventManager)
: base(game, eventManager)
{
}
public override void Initialize(){
map = new Map(this);
chicken = new Sprite(“chicken”, this);
chicken.Position = map.GetFirstFreeSpot();
chicken.SetMap(map);
eventManager.addHandler(new EventCantMoveHere(this));
eventManager.addHandler(new EventKeyboardLeft(this, chicken, map));
eventManager.addHandler(new EventKeyboardRight(this, chicken, map));
eventManager.addHandler(new EventKeyboardUp(this, chicken, map));
eventManager.addHandler(new EventKeyboardDown(this, chicken, map));
eventManager.addHandler(new EventNeedScrollMap(this, chicken, map ));
}
public override void LoadContent()
{
chicken.LoadContent(game.Content);
map.LoadContent(game.Content);
}
public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
Color color = new Color(0, 136, 0);
game.GraphicsDevice.Clear(color);
map.Draw(spriteBatch);
chicken.Draw(spriteBatch);
}
}
}
Então o trecho abaixo mostra como os eventos são registrados para que ao serem disparados, o tipo de evento é testado e em seguida o métido run() dos eventos é executado
eventManager.addHandler(new EventKeyboardDown(this, chicken, map));
eventManager.addHandler(new EventNeedScrollMap(this, chicken, map ));
O código abaixo é o tratador de eventos para quando o scroll de mapa eh necessário
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace ChickenMaze
{
class EventNeedScrollMap: EventHandler
{
protected Sprite sprite;
protected Map map;
protected int HorizontalBorderLimit;
protected int VerticalBorderLimit;
const byte BORDER_DIVISION_INDEX = 4;
public EventNeedScrollMap(Scene scene, Sprite sprite, Map map)
: base(EventHandler.EventType.SPRITE_MOVED, scene)
{
this.sprite = sprite;
this.map = map;
HorizontalBorderLimit = scene.game.GraphicsDevice.Viewport.Width / BORDER_DIVISION_INDEX;
VerticalBorderLimit = scene.game.GraphicsDevice.Viewport.Height / BORDER_DIVISION_INDEX;
}
///
/// for each direction of the movement change the viewport
/// of the GraphicsDevice
///
/// public override void run(Object source) {
int SceneShift = sprite.speed;
int x = (int)sprite.Position.X – scene.X;
int y = (int)sprite.Position.Y – scene.Y;
// into left limit
if (x < scene.game.GraphicsDevice.Viewport.X + HorizontalBorderLimit)
{
if (scene.X - SceneShift > 0)
{
scene.X -= SceneShift;
}
}
// into right limit
if (x > scene.game.GraphicsDevice.Viewport.X
+ scene.game.GraphicsDevice.Viewport.Width – HorizontalBorderLimit)
{
scene.X += SceneShift;
}
// into top limit
if (y < scene.game.GraphicsDevice.Viewport.Y + VerticalBorderLimit)
{
if (scene.Y - SceneShift > 0)
{
scene.Y -= SceneShift;
}
}
// into bottom limit
if (y > scene.game.GraphicsDevice.Viewport.Y
+ scene.game.GraphicsDevice.Viewport.Height – VerticalBorderLimit)
{
scene.Y += SceneShift;
}
}
}
}
Desta maneira tratadores de evento podem ser facilmente reaproveitados entre jogos, e inclusive escrever código para realizar testes destes tratadores.
os próximos passos são :
http://www.nickontech.com/archive/#alienaggressors
e tem um pdf em especial que eh um passo a passo de um jogo completo em xna
http://www.nickontech.com/archive/AlienAggressors/AlienAggressors.pdf
muito show este site de efetios sonoros, tem uns experimentais muito legais
http://www.freesound.org
Este devo usar no jogo da galinha
http://www.freesound.org/samplesViewSingle.php?id=49805