| Volver al Inicio | Microsoft Student Tech Club: Universidad Libre |
Tutorial de XNA: Los enemigos

Se añade a la biblioteca de imágenes del proyecto y como van a haber varios enemigos en pantalla, luego requerimos un arreglo unidimensional de objetos.

Y se cargan las imágenes de ese enemigo en el arreglo unidimensional de objetos.

Cada enemigo tiene una vida limitada: nace en la parte derecha de la ventana, viaja por la ventana y desaparece cuando llega al borde izquierdo de esta. Luego es necesario saber cuando el enemigo se mantiene activo, es decir, cuando se muestra por pantalla. Para eso hacemos uso del atributo de la clase ObjetosJuego de tipo booleano que cuando tiene el valor de "true" significa que el enemigo esta activo y debe mostrarse, caso contrario, manténgalo oculto.
No solamente eso, también hay algo importante con los enemigos: se desplazan por la pantalla, el desplazamiento tiene una dirección y una velocidad, por lo tanto hacemos uso del atributo de velocidad (magnitud y dirección) de la clase ObjetosJuego.
El cambio (solo en los comentarios, no en el código) se observa en la imagen

Este es el código
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Media; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace PrimerJuego
{
class ObjetosJuego
{
public Texture2D sprite; //La textura (o imagen) que tendrá el sprite
public Vector2 posicion; //En que punto se encuentra ubicado el sprite
public Vector2 centro; //El centro por donde gira el lanzador
public float rotacion; //Angulo de rotación del lanzador y del cohete public bool esta_activo; //Sabe si esta activo o no el cohete y del enemigo
public Vector2 velocidad; //Dirección y velocidad del cohete y del enemigo //Constructor
public ObjetosJuego(Texture2D textura)
{
sprite = textura; //Recibe la textura por parámetro
posicion = Vector2.Zero; // Posición [0,0] por defecto
rotacion = (float) 0.0; //Rotación 0.0 en radianes por defecto
centro = new Vector2(sprite.Width / 2, sprite.Height / 2); //El centro del sprite esta_activo = false; //Por defecto no se muestra el cohete ni el enemigo
velocidad = Vector2.Zero; //Por defecto el cohete no se mueve, tampoco el enemigo
}
}
} |
Ahora viene la programación del enemigo como tal y estas son las cosas que hay que tener en cuenta:
1. El enemigo aparece a la derecha de la ventana en una posición Y aleatoria.
2. El enemigo viaja a una velocidad inicial aleatoria.
3. El enemigo se desplaza de derecha a izquierda, desaparece cuando llegue al borde izquierdo de la ventana.
Para lo aleatorio necesitamos la clase Random();
| Random aleatorio = new Random(); |
Como el enemigo sale en una posición Y aleatoria, debemos saber entre que rango es el aleatorio (una interpolación lineal entre un Ymin y un Ymax), lo mismo pasa con la velocidad (entre un Vmin y Vmax). Usamos estas variables.
| const float alturaMax = 0.1f; //No pegado del techo const float alturaMin = 0.5f; //La mitad de la pantalla const float velocidadMax = 5.0f; //Máxima velocidad const float velocidadMin = 1.0f; //Mínima velocidad |
El uso de estas variables se da en estas instrucciones
| //Interpolación lineal enemigo.posicion = new Vector2(rectFondo.Right, MathHelper.Lerp((float)rectFondo.Height * alturaMin, (float)rectFondo.Height * alturaMax, (float)aleatorio.NextDouble())); //Velocidad del enemigo enemigo.velocidad = new Vector2(MathHelper.Lerp(-velocidadMin, -velocidadMax, (float)aleatorio.NextDouble()), 0); |
MathHelper.Lerp es una interpolación lineal entre dos valores. Mas información aqui: http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.mathhelper.lerp.aspx
El código que actualiza los enemigos es similar al de los cohetes: posición del enemigo mientras este se mantenga activo, este es el método:
public void ActualizaEnemigos()
{
foreach (ObjetosJuego enemigo in enemigos)
{
if (enemigo.esta_activo == true)
{
//El enemigo avanza a determinada velocidad
enemigo.posicion += enemigo.velocidad; //Si el enemigo se sale de la pantalla
if (rectFondo.Contains(new Point((int)enemigo.posicion.X, (int)enemigo.posicion.Y)) == false)
enemigo.esta_activo = false;
}
else
{
//Activa el enemigo
enemigo.esta_activo = true; //Interpolación. Posición Y aleatoria.
enemigo.posicion = new Vector2(rectFondo.Right, MathHelper.Lerp((float)rectFondo.Height * alturaMin, (float)rectFondo.Height * alturaMax, (float)aleatorio.NextDouble())); //Velocidad del enemigo. Aleatoria.
enemigo.velocidad = new Vector2(MathHelper.Lerp(-velocidadMin, -velocidadMax, (float)aleatorio.NextDouble()), 0);
}
}
} |
No olvidar dibujar los enemigos en Draw()
//Dibuja cada enemigo
foreach (ObjetosJuego enemigo in enemigos)
if (enemigo.esta_activo == true)
spriteBatch.Draw(enemigo.sprite, enemigo.posicion, null, Color.White, enemigo.rotacion, enemigo.centro, 0.2f, SpriteEffects.None, 0); |
Y llamar la rutina de actualización en Update()
//Actualiza los enemigos
ActualizaEnemigos(); |
El código completo de la clase Game1.cs es el siguiente:
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Media; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace PrimerJuego
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch; //Contenedor de los sprite
Texture2D Fondo; //Variable de tipo textura que tendrá el fondo
Rectangle rectFondo; //Un rectángulo donde estará la textura del fondo ObjetosJuego lanzador; //El lanzador
ObjetosJuego baselanzador; //Base del lanzador //Almacena el estado anterior del ratón
MouseState anteriorRaton; //Almacena el estado anterior del teclado
KeyboardState anteriorTeclado; //Cohetes
ObjetosJuego[] cohetes;
const int MAXCOHETES = 7; //Enemigos
ObjetosJuego[] enemigos;
const int MAXENEMIGOS = 6; const float alturaMax = 0.1f; //No pegado del techo
const float alturaMin = 0.5f; //La mitad de la pantalla
const float velocidadMax = 5.0f; //Máxima velocidad
const float velocidadMin = 1.0f; //Mínima velocidadRandom aleatorio = new Random(); public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
} /// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here base.Initialize();
} /// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);// TODO: use this.Content to load your game content here //Utiliza genéricos <> por eso el uso de <> En C++ se llaman plantillas. Content carga el contenido binario.
Fondo = Content.Load<Texture2D>("Imagenes\\FondoJuego"); //El rectángulo en el que estará contenido el fondo
rectFondo = new Rectangle(0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); //Cargar e inicializar el Lanzador
lanzador = new ObjetosJuego(Content.Load<Texture2D>("Imagenes\\Lanzador")); //No es necesaria la extension .tga
lanzador.posicion = new Vector2(160, 530); //Posicion del lanzador //Cargar e inicializar la base
baselanzador = new ObjetosJuego(Content.Load<Texture2D>("Imagenes\\Base")); //No es necesaria la extension .tga
baselanzador.posicion = new Vector2(70, 560); //Posicion de la base del lanzador //Cargar e inicializar los cohetes
cohetes = new ObjetosJuego[MAXCOHETES];
for (int Cont = 0; Cont < MAXCOHETES; Cont++)
cohetes[Cont] = new ObjetosJuego(Content.Load<Texture2D>("Imagenes\\Cohete")); //Cargar e inicializar los enemigos
enemigos = new ObjetosJuego[MAXENEMIGOS];
for (int Cont = 0; Cont < MAXENEMIGOS; Cont++)
enemigos[Cont] = new ObjetosJuego(Content.Load<Texture2D>("Imagenes\\Chatarra"));} /// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
} /// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit(); // Código para leer el estado del Gamepad de una XBOX. Se considera que hay un jugador al menos.
GamePadState estadoControl = GamePad.GetState(PlayerIndex.One); //Como hay que tener en cuenta los controles del XBOX llamados thumbstick, se
//observa que botón del XBOX oprimió, eso genera una constante algo grande por lo que se multiplica por 0.1
lanzador.rotacion += estadoControl.ThumbSticks.Left.X * 0.1f;//Esta es una compilación condicional. El teclado y el mouse solo son leídos si la aplicación ejecuta en un PC
#if !XBOX
//Código para leer el teclado
KeyboardState estadoTeclado = Keyboard.GetState();
if (estadoTeclado.IsKeyDown(Keys.Left)) lanzador.rotacion -= 0.1f; //Gira el lanzador hacia la izquierda
if (estadoTeclado.IsKeyDown(Keys.Right)) lanzador.rotacion += 0.1f; //Gira el lanzador hacia la derecha //Dispara un solo cohete.
//Chequea si el usuario mantiene presionada la tecla de disparo por lo que hace caso omiso a eso,
//el usuario debe presionar y soltar la tecla de disparo para lanzar mas misiles.
if (estadoTeclado.IsKeyDown(Keys.Space) && anteriorTeclado.IsKeyUp(Keys.Space))
DispararCohete(); //Se actualiza el estado del teclado
anteriorTeclado = estadoTeclado; //Código para leer el ratón
MouseState Raton = Mouse.GetState(); //Si movió el ratón entonces el lanzador cambia de ángulo
if (Raton != anteriorRaton)
{
int DiferX = Raton.X - anteriorRaton.X; //La diferencia entre la anterior posición del ratón y la nueva.
lanzador.rotacion += (float)DiferX / 100; //El movimiento en X del ratón cambia el ángulo del lanzador
} //Se actualiza el estado del ratón
anteriorRaton = Raton;
#endif //Esta instrucción limita entre 0 y 90 grados el giro del lanzador. Recordar que se hace uso de radianes.
lanzador.rotacion = MathHelper.Clamp(lanzador.rotacion, -MathHelper.PiOver2, 0); //Actualiza los cohetes
ActualizaCohetes(); //Actualiza los enemigos
ActualizaEnemigos(); base.Update(gameTime);
} /// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);// TODO: Add your drawing code here //Se inicia el spritebatch (Entre Begin y End se dibuja en cada unidad de tiempo los sprites)
spriteBatch.Begin(SpriteBlendMode.AlphaBlend); //Dibujar el fondo combinando el color blanco con el fondo
spriteBatch.Draw(Fondo, rectFondo, Color.White); //Dibuja la base del lanzador combinando con blanco
spriteBatch.Draw(baselanzador.sprite, baselanzador.posicion, Color.White); /* Dibuja el lanzador como tal, estos son los parámetros
* Parámetro 1: El sprite del lanzador
* Parámetro 2: La posición del lanzador
* Parámetro 3: null porque es solo una imagen (no una sucesión de estas
* Parámetro 4: Color.White, combinación de color
* Parámetro 5: Angulo de rotación por defecto
* Parámetro 6: Centro, el centro del sprite para girarlo después
* Parámetro 7: Escala a dibujar
* Parametro 8: Dibujar tal como es el sprite (no reflejo vertical ni horizontal)
* Parámetro 9: Ponerlo en la capa superior */
spriteBatch.Draw(lanzador.sprite, lanzador.posicion, null, Color.White, lanzador.rotacion, lanzador.centro, 0.6f, SpriteEffects.None, 0); //Dibuja cada cohete
foreach (ObjetosJuego cohete in cohetes)
if (cohete.esta_activo == true)
spriteBatch.Draw(cohete.sprite, cohete.posicion, null, Color.White, cohete.rotacion, cohete.centro, 0.2f, SpriteEffects.None, 0); //Dibuja cada enemigo
foreach (ObjetosJuego enemigo in enemigos)
if (enemigo.esta_activo == true)
spriteBatch.Draw(enemigo.sprite, enemigo.posicion, null, Color.White, enemigo.rotacion, enemigo.centro, 0.2f, SpriteEffects.None, 0);
//Finaliza el "ciclo" de los sprite
spriteBatch.End(); base.Draw(gameTime);
} //Metodo llamado desde Update
public void DispararCohete()
{
//Busca un cohete inactivo
foreach (ObjetosJuego cohete in cohetes)
{
//Si encuentra un cohete inactivo
if (cohete.esta_activo == false)
{
//Activa el cohete (para que sea dibujado)
cohete.esta_activo = true; //El cohete inicia en la posición del lanzador
cohete.posicion = lanzador.posicion; //En que dirección y a que velocidad sale el cohete. Hay que tener en cuenta la rotación del lanzador
cohete.velocidad = new Vector2((float)Math.Cos(lanzador.rotacion), (float)Math.Sin(lanzador.rotacion)) * 4.0f; //Rote el sprite del cohete para que coincida con el del lanzador
cohete.rotacion = lanzador.rotacion;
return;
}
}
} //Método llamado desde Update
public void ActualizaCohetes()
{
//Va de cohete en bala
foreach (ObjetosJuego cohete in cohetes)
if (cohete.esta_activo == true)
{
//Solo es actualizar la velocidad porque el vector se actualiza gracias a esa magnitud
cohete.posicion += cohete.velocidad; //Si el cohete se sale de la pantalla entonces se desactiva
if (rectFondo.Contains(new Point((int)cohete.posicion.X, (int)cohete.posicion.Y)) == false)
cohete.esta_activo = false;
}
} //Método llamado desde Update
public void ActualizaEnemigos()
{
foreach (ObjetosJuego enemigo in enemigos)
{
if (enemigo.esta_activo == true)
{
//El enemigo avanza a determinada velocidad
enemigo.posicion += enemigo.velocidad; //Si el enemigo se sale de la pantalla
if (rectFondo.Contains(new Point((int)enemigo.posicion.X, (int)enemigo.posicion.Y)) == false)
enemigo.esta_activo = false;
}
else
{
//Activa el enemigo
enemigo.esta_activo = true; //Interpolación. Posición Y aleatoria.
enemigo.posicion = new Vector2(rectFondo.Right, MathHelper.Lerp((float)rectFondo.Height * alturaMin, (float)rectFondo.Height * alturaMax, (float)aleatorio.NextDouble())); //Velocidad del enemigo. Aleatoria.
enemigo.velocidad = new Vector2(MathHelper.Lerp(-velocidadMin, -velocidadMax, (float)aleatorio.NextDouble()), 0);
}
}
}
}
} |
Este es el resultado:

| Volver al Inicio | Célula Microsoft. Universidad Libre |