Ya se puede observar el progreso de nuestro juego ejemplo. Ya cuenta con elementos animados como la nave y los enemigos, un fondo que le da una profundidad y una sensación de mayor movimiento en el entorno. Por los momentos, la única forma de destruir los enemigos es estrellando la nave contra ellos. Es hora de equipar la nave con un arma para disparar proyectiles contra las minas explosivas.
Comienza agregando una nueva clase llamada Projectile.cs; y elimina las declaraciones using que trae por defecto, y agrega las siguientes:
using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics;
Ya que estaremos trabajando con clases y metodos del framework XNA. Ahora prosigue a agregar los siguientes atributos de la clase Projectile. En los comentarios observa la importancia de cada atributo.
// Imagen del proyectil
public Texture2D Texture;
// Posicion del proyectil
public Vector2 Position;
// Estado del proyectil
public bool Active;
// Cantidad de daño del proyectil
public int Damage;
// Limite visible del juego
Viewport viewport;
// Ancho del proyectil
public int Width
{
get { return Texture.Width; }
}
// Alto del proyectil
public int Height
{
get { return Texture.Height; }
}
// Velocidad de desplazamiento
float projectileMoveSpeed;
Ahora agreguemoe el metodo Initialize para inicializar las propiedades de la clase. Observa que le llegan tres parametros, el viewport que son las dimensiones de la escena del juego, la textura del proyectil que en nuestro caso es un rayo laser azul, y por ultimo la posicion inicial del proyectil.
public void Initialize(Viewport viewport,
Texture2D texture, Vector2 position)
{
Texture = texture;
Position = position;
this.viewport = viewport;
Active = true;
Damage = 2;
projectileMoveSpeed = 20f;
}
Como la idea de los proyectiles es que viajen de izquierda a derecha contra las minas enemigas, en el método Update evidentemente debemos incrementar la posición con respecto al eje X de acuerdo a la velocidad establecida en el método Initialize (proyectileMoveSpeed = 20f). Por último verificamos si el proyectil se ha salido de la escena para desactivar su despliegue, con el propósito de evitar desplegar imágenes que no podremos visualizar en la escena.
public void Update()
{
// Projectiles always move to the right
Position.X += projectileMoveSpeed;
// Deactivate the bullet if it goes out of screen
if (Position.X + Texture.Width / 2 > viewport.Width)
Active = false;
}
Finalmente para esta clase, agregamos el método Draw para visualizar un proyectil en la pantalla.
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, Position, null, Color.White, 0f,
new Vector2(Width / 2, Height / 2), 1f, SpriteEffects.None, 0f);
}
Ya con nuestra nueva clase Projectile lista, proseguimos a hacer unas modificaciones en la clase principal.
En la Clase Game1
Comenzamos declarando los siguientes atributos.
// Textura de un proyectil Texture2D projectileTexture; // Lista de proyectiles List <Projectile>projectiles; // Tasa de proyectiles a disparar TimeSpan fireTime; // Tiempo de disparo anterior TimeSpan previousFireTime;
Nota que agregamos una lista de proyectiles. Hacemos esto porque planeamos desplegar múltiples proyectiles disparados por la nave, por ende esta estructura de dato dinámica es ideal para llevar control de cada proyectil en escena en todo momento. fireTime nos dice en cada cuanto intervalo de tiempo el jugador disparará un proyectil en escena, ya verás cómo eso funciona más adelante. En el método Initialize, al final, agrega las siguientes instrucciones:
projectiles = new List<Proyectile>(); // Un laser se dispara cada cuarto de segundo fireTime = TimeSpan.FromSeconds(.15f);
Observas el valor de fireTime? Esto quiere decir que la nave disparará 4 láseres por segundo. Ahora, En el LoadContent, al final del método, proseguimos a cargar la textura del láser, con la siguiente instrucción.
projectileTexture = Content.Load<Texture2D>("laser");
Como cuando trabajamos con el despliegue de enemigos, debemos agregar un método para AddProjectile, para instanciar y agregar nuevos proyectiles en la lista, inicializándolos con el viewport, su textura y su posición inicial.
private void AddProjectile(Vector2 position)
{
Projectile projectile = new Projectile();
projectile.Initialize(GraphicsDevice.Viewport,
projectileTexture,position);
projectiles.Add(projectile);
}
Ahora debemos buscar donde invocar este método. Ya que el jugador es el único elemento que estará disparando rayos láser, tiene sentido ir al método UpdatePlayer, y agregar las siguientes instrucciones al final:
// Disparar bajo el valor de fireTime
if (gameTime.TotalGameTime - previousFireTime > fireTime)
{
// Reiniciamos nuestro tiempo actual
previousFireTime = gameTime.TotalGameTime;
// Agregamos el proyectil al frente de la nave
AddProjectile(player.Position + new Vector2(player.Width / 2, 0));
}
Aquí estamos agregando nuevos proyectiles en la cola, y disparando a la tasa del valor de fireTime. Ahora necesitamos actualizar las posiciones de cada proyectil en la escena. Como ya hicimos antes con los enemigos, debemos agregar un nuevo método llamado UpdateProjectiles y se verá de la siguiente manera:
private void UpdateProjectiles()
{
// Actualizar los Proyectiles
for (int i = projectiles.Count - 1; i >= 0; i--)
{
projectiles[i].Update();
if (projectiles[i].Active == false)
{
projectiles.RemoveAt(i);
}
}
}
Observa que se invoca el método Update por cada proyectil en la escena, donde estos se desplazarán de izquierda a derecha a una velocidad uniforme, y cuando Active se encuentre en false, son removidos de la lista de proyectiles. En cuanto al método Update, proseguimos a invocar UpdateProjectiles al final:
// Actualizar proyectiles UpdateProjectiles();
Hasta este punto los proyectiles se dibujan, se mueven, pero no colisionan con los enemigos. Dirígete al método UpdateCollision y agrega las siguientes intrucciones al final:
// Colision entre Proyectiles vs Enemigos
for (int i = 0; i < projectiles.Count; i++)
{
for (int j = 0; j < enemies.Count; j++)
{
// Creamos los rectangulos para validar si hay colision
rectangle1 = new Rectangle(
(int)projectiles[i].Position.X -
projectiles[i].Width / 2,
(int)projectiles[i].Position.Y -
projectiles[i].Height / 2,
projectiles[i].Width,
projectiles[i].Height);
rectangle2 = new Rectangle(
(int)enemies[j].Position.X -
enemies[j].Width / 2,
(int)enemies[j].Position.Y -
enemies[j].Height / 2,
enemies[j].Width,
enemies[j].Height);
// Determinamos si se intersectan
if (rectangle1.Intersects(rectangle2))
{
enemies[j].Health -= projectiles[i].Damage;
projectiles[i].Active = false;
}
}
}
Entiendes la lógica? Simplemente se está creando un rectángulo por cada proyectil, y se crea un rectángulo por cada enemigo visible en esa iteración (para capturar su posición en ese instante de tiempo). Luego se verifica si algún rectángulo enemigo se intersecta con el rectángulo del proyectil, y en caso afirmativo, se le resta vida al enemigo colisionado, y se desactiva el proyectil para que sea removido de la lista.
Finalmente, implementamos la logica para desplegar los proyectiles en escena. Vamos al metodo Draw, y debajo de Jugador.Draw(), inserta las siguientes instrucciones:
for (int i = 0; i < projectiles.Count; i++)
{
projectiles[i].Draw(spriteBatch);
}
Al ejecutar nuestro juego se vera algo así:
Observa la colisión de los proyectiles contra los enemigos, pero veras que solo desaparecen cuando son destruidos. En la siguiente parte de este juego ejemplo haremos que las minas muestren un efecto de explosión cuando son destruidas por el jugador. Si estas teniendo problemas armando la solución, puedes descargarte el proyecto haciendo clic en el siguiente enlace:

















