"El ser inmóvil mueve como objeto del amor, y lo que él mueve imprime el movimiento de todo lo demás."
Aristósteles (384 a.C. - 322 a.C.) - Filósofo griego.
Aviso: Usaremos como base el mismo código fuente desarrollado en la entrada anterior.
¡Saludos!
- Pensando en un objeto para el jugador.
- Diseñando una clase para el juego.
- Creación un objeto para el juego, representando al jugador.
- Caracterísitcas del Sistema de Coordenadas 2D en XNA.
- Movimiento del objeto creado a través del teclado.
![]() |
| Imagen 0. Manipularemos el movimiento de esta pequeña figura con el teclado. |
Pensando en un objeto
Los atributos (o campos) que tendrá nuestra clase Figura serán:
- textura [Texture2D]: hace referencia al Sprite que usaremos para vizualizar al objeto en la pantalla.
- posicion [Vector2]: un vector bidimensional que contendrá los datos sobre la ubicación del objeto en la pantalla, dados una coordenada horizonal 'x' y una vertical 'y'.
- velocidad [float]: guardaremos en esta variable el valor con la velocidad que deseamos que se mueva la figura.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework; // Espacio de nombre necesario para usar Vector2
using Microsoft.Xna.Framework.Graphics; // Espacio de nombre necesario para usar Texture2D
namespace Prueba01
{
class Figura
{
private Texture2D textura; // Textura que se usará para representar al jugador.
public Vector2 posicion; // Contendrá las coordenadas de su posición.
private float velocidadMov; // Representa al valor de la velocidad de su desplazamiento.
public void Inicializar(Texture2D textura, Vector2 posicion, float velocidadMov)
{
this.textura = textura;
this.posicion = posicion;
this.velocidadMov = velocidadMov;
}
public void Dibujar(SpriteBatch spriteBatch)
{
spriteBatch.Draw(
textura,
posicion,
null,
Color.White,
0f, //MathHelper.ToRadians(45)
Vector2.Zero,
0.5f,
SpriteEffects.None,
0f
);
}
public int GetAncho()
{
return textura.Width; // Se retorna el valor (en pixeles) del ancho de la textura.
}
public int GetAlto()
{
return textura.Height; // Se retorna el valor (en pixeles) del alto de la textura.
}
public float GetVelocidad()
{
return velocidadMov; // Se retorna el valor de la velocidad establecida.
}
}
}
Noten que al principio del código declaré 2 Namespaces (Microsoft.Xna.Framework y Microsoft.Xna.Framework.Graphics). El primero contiene una clase fundamental que usa un vector bidimensional, llamada Vector2, que nos servirá para establecer una posición en la pantalla. El segundo posee la clase Texture2D, necesaria para poder dibujar luego el Sprite del objeto en la pantalla.En la entrada referida a la Introducción a XNA 4.0, hablamos un poco sobre su framework y sobre los Espacios de Nombres (NameSpaces) que contiene esa biblioteca. Usaremos varios de ellos a partir de ahora, asi que recomiendo que vuelvan a hechar un vistazo.
Vean que también se agregó un método Dibujar() que necesita como parámetro una referencia de la clase SpriteBatch. Esta clase es proporcionada por el segundo namespace mencionado anteriormente y poseé un método sobrecargado llamado Draw() que nos permitirá dibujar nuestra figura en la pantalla. Podríamos expresar ese método de esta forma:
spriteBatch.Draw(textura, posición, rectángulo de origen, color, rotación, origen, escala, efecto de sprite, profundidad de capa)
En donde:
- textura [Texture2D]: es la textura del sprite que usaremos para representar a la figura.
- posición [Vector2]: es la posición en coordenadas (x,y) del punto en la pantalla donde se dibujará el sprite.
- recángulo de origen [Nullable<Rectangle>]: rectángulo que especifíca los téxeles de origen de una textura. Usamos null para dibujar toda la textura. De lo contrario, pasamos un rectángulo con las dimensiones deseadas. Se dibujará la parte de la textura que esté comprendida dentro de esas dimensiones.
- color [Color]: indica el color para tintar un sprite. Usamos Color.White para no tintar el sprite, es decir que no se modificarán sus colores originales.
- rotación [float]: indica el ángulo (dado en radianes) de rotación del sprite respecto a su centro.
- origen [Vector2]: es el punto de origen de la textura. La rotación de la textura, tomará como referencia este punto. Por defecto, usamos siempre Vector2.Zero o "(0,0)" que representa el punto de la esquina superior izquierda de la textura. Hay que prestar atención cuando se modifica este parámetro, ya que puede generar algunas confusiones luego.
- escala [float]: el valor de escala del sprite. Para dibujar el tamaño original, se usa como valor 1f. Pero en este caso usé 0.5f, por lo que se dibujará la figura con la mitad de su tamaño original.
- efecto de sprite [SpriteEffects]: indica si se efectuará algún efecto al sprite. Los efectos que se pueden efectuar son 2: voltear el sprite horinzontalmente o verticalmente.
- profundidad de capa [float]: representa la "profundidad" del sprite, en relación a otros sprites. Consideramos como una "capa": Imaginen que tienen una pila de hojas de papel (o capas) apoyadas en un escritorio y que las observan desde arriba. La hoja que está en frente de todas (la capa de tope) tendría el valor de 1f mientras que la que está al fondo de todas (la capa base) tendría un valor de 0f. Las hojas que están entre esas 2, tendrían un valor entre el intérvalo [0f - 1f]. Teniendo en cuenta esto, las capas que tengan el valor más cercano a 1, son las que estarán al frente y se verán primero, "tapando" a aquellas que tengan un valor menor (que tiendan a 0).
Creación de un objeto y Sistemas de Coordenadas 2D en XNA
![]() |
| Figura 1. Este será el "personaje" que el jugador controlará. |
Es un poco grande, pero el método Dibujar() que realizamos reduce la escala de cualquier textura a la midad. Es sólo por cuestión de comodidad.
Vamos a la clase principal Game1 y declaremos una instancia de la clase Figura. Declaremos también otra variable tipo float que contenga el valor de la velocidad con la que se desplazará el cuadrado.
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Figura jugador; // Instancia del objeto de clase Figura.
float velocJugador = 5f; // Valor que se le sumará o restará a la posición del objeto, por cada loop.
KeyboardState estadoTeclado; // Referencia que guardará el último estado del teclado, por cada loop.
SpriteFont font; // Se guardará el tipo de fuente que se usará en los textos.
Ahora continuemos con la creación del cuadrado que representará el jugador. Simplemente agregamos esto en el método Initialize():
protected override void Initialize()
{
jugador = new Figura();
base.Initialize();
}
Continuamos estableciendo el punto de origen y cargando la textura que usaremos para el cuadrado. Esto corresponde hacerlo en el método LoadContent():
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// Se establece la posición inicial del jugador (x,y)
Vector2 posicionJugador = new Vector2(
GraphicsDevice.Viewport.TitleSafeArea.X,
GraphicsDevice.Viewport.TitleSafeArea.Y
);
jugador.Inicializar(Content.Load<Texture2D>("Texturas/Azul"), posicionJugador, velocJugador);
font = Content.Load<SpriteFont>("SpriteFont1");
}
protected override void Draw(GameTime gameTime)
{
string anchoPantalla = Convert.ToString(GraphicsDevice.Viewport.Width);
string altoPantalla = Convert.ToString(GraphicsDevice.Viewport.Height);
GraphicsDevice.Clear(Color.CornflowerBlue);
// ============ Comienza dibujado ===================
spriteBatch.Begin();
// Se dibuja el texto donde informa el valor de Ancho de la pantalla
spriteBatch.DrawString(font,
"Ancho: " + anchoPantalla,
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X,
GraphicsDevice.Viewport.TitleSafeArea.Y),
Color.White,
0f,
new Vector2(0f,0f),
0.8f,
SpriteEffects.None,0);
// Se dibuja el texto donde informa el valor de Alto de la pantalla
spriteBatch.DrawString(font,
"Alto: " + altoPantalla,
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X+20,
GraphicsDevice.Viewport.TitleSafeArea.Y+50),
Color.DarkBlue,
MathHelper.ToRadians(90),
new Vector2(0,0),
0.8f,
SpriteEffects.None,
0f);
// Se dibuja la figura del jugador
jugador.Dibujar(spriteBatch);
spriteBatch.End();
// ============ Termina dibujado ===================
base.Draw(gameTime);
}
Todavía no realizamos un método que pueda mover al objeto que creamos. Lo haremos luego, pero primero hablemos un poco sobre el Sistema de Coordenadas 2D en XNA.
Sistemas de Coordenadas 2D en XNA
![]() |
| Figura 2. Sistema de Coordenadas 2D en XNA |
Recordemos que en la clase Figura definimos el método Inicializar() y que necesita (además de la textura y la velocidad) un parámetro de tipo Vector2, el cual representa su posición inicial. Ahora, volviendo a la clase principal Game1, recordemos también que dentro del método LoadContent() creamos una referencia del tipo Vector2 con 2 valores (x,y) que representan a un punto del sistema de coordenadas bidimensional. Dichos valores fueron (0,0), por lo que el cuadrado aparece dibujado en la esquina superior izquierda de la pantalla.
Podemos modificar esos valores por otros, en caso de que se desee crear el objeto en otra posición. Por ejemplo, podemos crear al objeto en el centro de la pantalla. Para ello, tendríamos que modificar los parámetros al crear la referencia del tipo Vector2. En este caso es útil la propiedad Viewport que mencionamos anteriormente, ya que gracias a ella podemos obtener, entre otras cosas, el punto del centro de la pantalla:
Vector2 posicionJugador = new Vector2(
GraphicsDevice.Viewport.TitleSafeArea.Center.X,
GraphicsDevice.Viewport.TitleSafeArea.Center.Y
);
jugador.Inicializar(Content.Load("Texturas/Azul"), posicionJugador, velocJugador);
Si hacemos esta modificación, al ejectuar el programa tendríamos que ver a nuestro amigo azul en el centro de la pantalla. De esta forma:
![]() |
| Figura 3. Ubicación del punto del centro de la pantalla y Origen de la textura. |
¿Dí muchas vueltas? Espero que se haya entendido, porque es algo que hay que tener muy en cuenta. Cuesta entender un poco hasta que uno se acostumbra. Experimenten con otros valores para que entiendan mejor el funcionamiento, no hay mejor forma.
Juguemos un poco con la rotación del objeto ahora. Hace poco hablamos del método Draw() que creamos en la clase Figura. Tengamos en cuenta 3 de todos sus parámetros: posición, rotación y origen. En estos momentos, tenemos a nuestra figura ubicada en el punto (400,240), que es el punto de su posición. La rotación tiene un valor de 0f, que es lo mismo que una rotación de 0º. Y por último, el punto origen de la textura es (0,0).
Cambiemos un par de cosas. En la entrada anterior vimos cómo rotar un texto con la ayuda de la clase MathHelper. Cambiemos entonces la rotación de nuestro amigo a unos 45º, de esta manera:
public void Dibujar(SpriteBatch spriteBatch)
{
spriteBatch.Draw(
textura,
posicion,
null,
Color.White,
MathHelper.ToRadians(45), //anteriormente 0f
Vector2.Zero,
0.5f,
SpriteEffects.None,
0f
);
}
Entonces lo que veremos al ejecutar el programa será lo siguiente:
![]() |
| Figura 4. Ejemplo de rotación de la textura en 45º, respecto al punto de su origen. |
¿Qué pasa ahora si cambiamos el origen de la textura? Quiero tener el origen ubicado en el centro de la textura. En este caso, la textura es de 200x200 pixeles. La clase Figura que diseñamos, tiene 2 métodos: GetAncho() y GetAlto(), los cuales nos devuelven el valor del ancho y del alto respectivamente de la textura que estamos usando. Esto nos resultará útil ahora:
public void Dibujar(SpriteBatch spriteBatch)
{
spriteBatch.Draw(
textura,
posicion,
null,
Color.White,
MathHelper.ToRadians(45), //esto estaba en 0f
new Vector2 (getAncho()/2,getAlto()/2),
0.5f,
SpriteEffects.None,
0f
);
}
Veamos qué sucede:
![]() |
| Figura 5. Ejemplo resultante de la modifiación de los valores de origen de la textura. |
Ahora sí podría decir que la figura está en el centro de la pantalla. Esto es lo que pasa cuando cambiamos el origen de la textura. Este es otro ejemplo con el origen de la textura modificado:
![]() |
| Figura 6. Otro ejemplo de la modificación del origen de la textura. |
En fin, resulta demasiado útil saber cómo lidiar con estos aspectos en el sistema de coordenadas de XNA. Pero también es importante tener cuidado cuando combinamos esto con la detección de colisiones, tema que se desarrollará en la siguiente entrada.
Movimiento del Objeto con Teclado
Para ello, necesitamos del NameSpace "Microsoft.Xna.Framework.Input" que contiene clases que reciben entradas desde el teclado, el mouse y el joystick de la consola Xbox360. Es decir, estas clases son las que permiten a nuestro juego interactuar con el/los jugadores. Para empezar de a poco, sólo realizaremos el movimiento de nuestro objeto a través del teclado. La clase que nos permitirá hacer esto, se llama "KeyboardState". ¡Comencemos a dar vida a nuestro proyecto!
Primero, necesitamos crear una referencia a la clase KeyboardState, al inicio de la clase principal Game1:
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Figura jugador; // Instancia del objeto de clase Figura.
float velocJugador = 5f; // Valor que se le sumará o restará a la posición del objeto, por cada loop.
KeyboardState estadoTeclado; // Referencia que guardará el último estado del teclado, por cada loop.
SpriteFont font; // Se guardará el tipo de fuente que se usará en los textos.
Una vez que guardamos dicho estado, deberíamos poder mover a nuestro amigo dependiendo de la tecla que hayamos presionado. Es decir, sería necesario tener un método que nos permita modificar la posición del objeto creado, luego de analizar el último estado del teclado. Aplicando todo lo dicho en nuestro código:
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
estadoTeclado = Keyboard.GetState();
MoverFigura(gameTime);
base.Update(gameTime);
}
private void MoverFigura(GameTime gameTime)
{
// Analizamos qué tecla fue presionada y luego modificamos su posición
if (estadoTeclado.IsKeyDown(Keys.Left))
{ jugador.posicion.X -= jugador.GetVelocidad(); }
if (estadoTeclado.IsKeyDown(Keys.Right))
{ jugador.posicion.X += jugador.GetVelocidad(); }
if (estadoTeclado.IsKeyDown(Keys.Up))
{ jugador.posicion.Y -= jugador.GetVelocidad(); }
if (estadoTeclado.IsKeyDown(Keys.Down))
{ jugador.posicion.Y += jugador.GetVelocidad(); }
jugador.posicion.X = MathHelper.Clamp(jugador.posicion.X, 0,
GraphicsDevice.Viewport.Width - jugador.GetAncho() /2);
jugador.posicion.Y = MathHelper.Clamp(jugador.posicion.Y, 0,
GraphicsDevice.Viewport.Height - jugador.GetAlto() /2);
}
Eso es todo, es fácil de interpretar el código. Para mover a la figura, sólo debemos presionar las teclas arriba, abajo, izquierda o derecha según hacia dónde se nos apetezca moverlo. Al determinar qué tecla fue presionada, se modificará la posición del objeto dependiendo de la velocidad que indicamos al principio. Lo único extraño es el método "Clamp()", el cual restringe un valor dentro de un intérvalo dado. En nuestro caso, le indicamos que el objeto no se puede pasar de los límites de la pantalla. Otro método muy útil de nuestro ya muy querido amigo MathHelper.
Y ahora sí, si ejecutamos el código vamos a poder mover a la figura cuadrada por toda la pantalla. ¡Al fin algo se mueve!
En la próxima entrada crearemos otro objeto para manipularlo esta vez con el mouse y veremos uno de los temas más importantes: Detección de Colisiones. Tengo ganas de hacer un juego muy muy simple luego de eso, como para simplificar todo lo que hicimos hasta ahora. Es una posibilidad, veremos qué sucede.
¡Hasta entonces!
¡Sigue adelante, siempre!




















