public Matrix GetMatrizTextura()
{
Matrix matFigura = Matrix.CreateTranslation(-puntoAncla.X, -puntoAncla.Y, 0) *
Matrix.CreateRotationZ(angulo) *
Matrix.CreateScale(escala) *
Matrix.CreateTranslation(posicion.X, posicion.Y, 0);
return matFigura;
}
Ya tenemos listos los métodos que retornan los elementos necesarios para la detección Pixel Perfect. Ahora queda el paso final que consiste en analizar y detectar colisión entre 2 objetos. Esto lo haremos desde la clase principal Game1.
Primero, definamos al principio de la clase 3 booleanos (uno para cada objeto) que nos pueda indicar si hubo, o no, colisión (con true o false, respectivamente). Luego añadimos el siguiente método de detección, realizado por Riemer, que pueda modificar el estado de esos booleanos:
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Figura jugador; // Instancia del objeto de clase Figura.
KeyboardState estadoTeclado; // Referencia que guardará el último estado del teclado, por cada loop.
Figura jugador2;
MouseState estadoMouse; // Referencia que guardará el último estado del Mouse, por cada loop.
Figura figNeutral; // Referencia del tercer objeto para prueba de colisiones. Es inmóvil.
SpriteFont font; // Se guardará el tipo de fuente que se usará en los textos.
// Tomarán el valor de 'true' en caso de que exista alguna colisión a nivel pixel.
bool huboColisionPix;
bool huboColisionNJ1Pix;
bool huboColisionNJ2Pix;
public bool ColisionPixelPerfectBool(Color[,] pixelesFig1, Matrix matFig1, Color[,] pixelesFig2, Matrix matFig2)
{
bool huboColision = false;
Matrix mat1a2 = matFig1 * Matrix.Invert(matFig2);
int ancho1 = pixelesFig1.GetLength(0);
int alto1 = pixelesFig1.GetLength(1);
int ancho2 = pixelesFig2.GetLength(0);
int alto2 = pixelesFig2.GetLength(1);
for (int x1 = 0; x1 < ancho1; x1++)
{
for (int y1 = 0; y1 < alto1; y1++)
{
Vector2 pos1 = new Vector2(x1, y1);
Vector2 pos2 = Vector2.Transform(pos1, mat1a2);
int x2 = (int)pos2.X;
int y2 = (int)pos2.Y;
if ((x2 >= 0) && (x2 < ancho2))
{
if ((y2 >= 0) && (y2 < alto2))
{
if (pixelesFig1[x1, y1].A > 0)
{
if (pixelesFig2[x2, y2].A > 0)
{
huboColision = true;
return huboColision;
}
}
}
}
}
}
return huboColision;
}
}
Finalmente, agregaremos un método que permita invocar al ColisionPixelPerfect() según la cantidad de figuras que usamos en el juego. En este caso invocaremos a dicho método 3 veces para analizar las colisiones entre los 3 objetos que hemos creado. Luego aplicamos todo esto dentro del método Update() y finalizamos modificando el método Draw(), en donde deberíamos cambiar el texto que indica si hubo colisión, según el resultado del análisis. Tendría que quedar entonces algo así:
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(); // Se verifica el estado del Teclado (inactivo / tecla pulsada)
estadoMouse = Mouse.GetState(); // Se verifica el estado del Mouse (inactivo / tecla pulsada)
MoverFigura(); // Se mueve una Figura dependiendo de los 2 estados anteriores.
RotarFigura(); // Se gira una Figura dependiendo de los 2 estados anteriores.
figNeutral.InreaseAngulo(0.005f); // Rotación de la Figura Neutral.
EscalarFigura(); // Se redimensiona el tamaño de una Figura.
ControlarColisiones(); // Checkea si hay algún tipo de colisión entre los objetos creados.
base.Update(gameTime);
}
public void ControlarColisiones()
{
huboColisionPix = ColisionPixelPerfectBool(jugador.GetMatrizPixeles(), jugador.GetMatrizTextura(),
jugador2.GetMatrizPixeles(), jugador2.GetMatrizTextura()
);
huboColisionNJ1Pix = ColisionPixelPerfectBool(jugador.GetMatrizPixeles(), jugador.GetMatrizTextura(),
figNeutral.GetMatrizPixeles(), figNeutral.GetMatrizTextura()
);
huboColisionNJ2Pix = ColisionPixelPerfectBool(jugador2.GetMatrizPixeles(), jugador2.GetMatrizTextura(),
figNeutral.GetMatrizPixeles(), figNeutral.GetMatrizTextura()
);
}
protected override void Draw(GameTime gameTime)
{
string anchoPantalla = Convert.ToString(GraphicsDevice.Viewport.Width);
string altoPantalla = Convert.ToString(GraphicsDevice.Viewport.Height);
string textoColision;
GraphicsDevice.Clear(Color.CornflowerBlue);
if (huboColisionPix || huboColisionNJ1Pix || huboColisionNJ2Pix)
{
GraphicsDevice.Clear(Color.DarkSlateBlue);
textoColision = "Si";
}
else
{
GraphicsDevice.Clear(Color.CornflowerBlue);
textoColision = "No";
}
// ============ Comienza dibujado ===================
spriteBatch.Begin();
// ...
// ... y aquí va el resto del código original.
// ...
}
Y con esto terminamos con el método de Detección de Colisiones a Nivel Pixel. Ejecutemos el código y veamos qué sucede:
|
Demostración de la Detección de Colisiones a Nivel Pixel en funcionamiento. |
Como podrán notar, el método funciona perfectamente y es muy preciso. Este método es la solución para aquellos objetos con texturas de formas complejas que pudieramos llegar a utilizar alguna vez.
Otra cosa importante es que, no sé si ustedes también, pero noté que inmediatamente luego de arrancar el programa, este funcionó lentamente por unos segundos y luego comenzó a correr normalmente (aunque en ciertas ocasiones se notaba una pequeña lentitud). Seguramente tiene que ver con el costo del que mencioné más arriba, sobre el consumo de CPU que requiere este método. Esa es la gran desventaja de la detección Pixel Perfect. El rendimiento empeora dependiendo de la cantidad de objetos a analizar y sus respectivos tamaños: mientras más cantidades de objetos y mientras más grandes sean, peor será el rendimiento.
Y bueno, no todo lo que brilla es oro. Pero, según lo que leí en algunos sitios, existen muchas maneras de "alivianar" el consumo de CPU, sin tener que dejar de emplear este excelente método. Uno de ellos es combinando la Detección Simple (que vimos en la entrada anterior) con Pixel Perfect. Elegí esta combinación porque me parecía bastante sencilla y además es de mucha ayuda. Lo veremos a continuación.
Detección de Colisiones combinada: Rectangle + Pixel Perfect
Aviso: Busqué la forma de aplicar Pixel Perfect, combinados con BoundingBox o BoundingSphere, pero no encontré nada. Si alguna vez llego a encontrar la forma de aplicarlo, estaré compartiendo eso en alguna futura Entrada. En su lugar, usaremos la clase Rectangle.
Esta combinación de métodos permite mejorar el rendimiento, conservando la precisión de la detección de colisión Pixel Perfect. Sólamente consiste en utilizar una clase muy parecida al BoundingBox, llamada Rectangle. Como se puede deducir de su nombre, un objeto de la clase Rectangle no es más que un rectángulo. La diferencia es que un BoundingBox está pensado para utilizarse en 3D (por sus coordenadas x,y,z), mientras que Rectangle nos resulta útil en 2D. Además, podemos transformarlo según la posición, rotación y escala de la textura de un objeto (al igual que las matrices). Es por esta ventaja que usaremos Rectangle para detectar primero una Colisión Simple, de bajo consumo de CPU, para que luego proceda a la detección Pixel Perfect sólo en caso de que ocurra la situación anterior. De esta manera, los cálculos no serán tan 'pesados'.
Pero antes, agreguemos un par de métodos que servirán para observar el comportamiento del origen de la textura de nuestro objeto. Sabemos que por defecto, el origen, al que llamaremos en mi caso como "puntoAncla", es una instancia de la clase Vector2 con las coordenadas X e Y ubicadas en la esquina superior izquierda (0,0) de la textura de una figura. La rotación tomará como referencia ese punto, tal como vimos en la entrada Level 01. Esta situación se dá con la estrella anaranjada, que contínuamente está rotando según su punto de Ancla (origen).
Agreguemos ahora un par de métodos en la clase Figura que permita cambiar las coordenadas del puntoAncla (a parte del set correspondiente) y ubicarla en el centro de la textura o en la ubicación por defecto (0,0) esquina superior izquierda.
Agregamos entonces los siguientes métodos en la clase Figura:
public void CentrarOrigen()
{
puntoAncla = new Vector2((textura.Width/2) , (textura.Height/2));
}
public void ReestablecerOrigen()
{
puntoAncla = Vector2.Zero;
}
Y llamamos a estos métodos en la clase principal Game1, por ejemplo dentro del método EscalarFigura(). No son relevantes estos métodos, son sólamente para analizar situaciones particulares. Hagamos que afecte únicamente a la figura cuadrada:
public void EscalarFigura()
{
if (estadoTeclado.IsKeyDown(Keys.W))
{
jugador.IncreaseEscala(0.01f);
}
if (estadoTeclado.IsKeyDown(Keys.S))
{
jugador.IncreaseEscala(-0.01f);
}
if (estadoTeclado.IsKeyDown(Keys.F))
{
jugador.CentrarOrigen();
}
if (estadoTeclado.IsKeyDown(Keys.G))
{
jugador.ReestablecerOrigen();
}
}
Así, si presionamos la tecla 'G' el puntoAncla tendrá el valor (0,0). En cambio, si presionamos la tecla 'F', el puntoAncla tendrá sus coordenadas en el centro de la textura del objeto (en este caso, el centro de la figura cuadrada de 200x200 pixeles, tiene las coordenadas [100,100]). Pueden comprobarlo ejecutando el código, el método Pixel Perfect también estará funcionando.
Ahora sí, regresemos con el tema sobre el uso de Rectangle para la detección de colisiones.
Necesitamos crear un Rectangle que se adapte según la posición, la rotación y la escala de nuestra figura. Para ello, agregamos el siguiente método en la clase Figura, que retornará un Rectangle transformado de acuerdo con los 3 elementos mencionados:
public Rectangle CrearRectanguloLim()
{
// Creamos un Rectangle teniendo en cuenta el ancho y alto de la textura.
Rectangle rectangulo = new Rectangle(0, 0, textura.Width, textura.Height);
// Necesitamos adaptar ese Rectangulo según la rotación, escala y posición del objeto.
Matrix transformada = GetMatrizTextura();
// Se toma el valor de cada esquina del Rectangulo creado.
Vector2 esquinaSupIzq = new Vector2(rectangulo.Left, rectangulo.Top);
Vector2 esquinaSupDer = new Vector2(rectangulo.Right, rectangulo.Top);
Vector2 esquinaInfIzq = new Vector2(rectangulo.Left, rectangulo.Bottom);
Vector2 esquinaInfDer = new Vector2(rectangulo.Right, rectangulo.Bottom);
// Se transforman esas 4 esquinas según la figura.
Vector2.Transform(ref esquinaSupIzq, ref transformada, out esquinaSupIzq);
Vector2.Transform(ref esquinaSupDer, ref transformada, out esquinaSupDer);
Vector2.Transform(ref esquinaInfIzq, ref transformada, out esquinaInfIzq);
Vector2.Transform(ref esquinaInfDer, ref transformada, out esquinaInfDer);
// Se busca el mínimo y máximo punto del rectangulo.
Vector2 min = Vector2.Min(Vector2.Min(esquinaSupIzq, esquinaSupDer),
Vector2.Min(esquinaInfIzq, esquinaInfDer));
Vector2 max = Vector2.Max(Vector2.Max(esquinaSupIzq, esquinaSupDer),
Vector2.Max(esquinaInfIzq, esquinaInfDer));
// Retorna un rectangulo transformado
return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y));
}
Así es como se obtiene un Rectangle que se pueda adaptar a las transformaciones de nuestra figura. Con esto, podremos mejorar el rendimiento de nuestra Detección de Colisiones. ¿Cómo?
La clase Rectangle tiene el mismo método que la clase BoundingBox (o BoundingSphere), que conocimos bajo el nombre de Intersects(). Ya sabemos cómo funciona, recuerden que en la entrada anterior lo expliqué. La idea es alivianar el uso de CPU, verificando primero si hubo alguna intersección entre 2 Rectangles y luego, en caso afirmativo, proseguir con la detección a nivel pixel. Tengan en cuenta que usando el método de detección con Rectangle se realiza un sólo control, mientras que con Pixel Perfect se realizan muchos más controles: piensen que se verifica cada uno de los pixeles de una textura con cada uno de los pixeles de otra figura... son muchísimos controles. Esta es la clave de por qué el uso combinado de Detección de Colisiones por Rectangle y Pixel Perfect mejora el rendimiento y mantiene precisión.
Volquemos en el proyecto lo dicho anteriormente. Para ello, necesitamos definir 3 booleanos más (de nuevo, uno para cada objeto) al principio de la clase Game1, que nos sirvan para indicar si hubo o no hubo alguna intersección entre los Rectangles de nuestras figuras. Recordemos también, de inicializar dichas variables en el método Initialize(). Luego, modificamos nuestro método ControlarColisiones(), de acuerdo a lo que habíamos planteado. Y por último, modificamos el método Draw() para que el texto de la pantalla indique si hubo o no colisión. Todas estas modificaciones deberían quedar de esta manera:
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Figura jugador; // Instancia del objeto de clase Figura.
KeyboardState estadoTeclado; // Referencia que guardará el último estado del teclado, por cada loop.
Figura jugador2;
MouseState estadoMouse; // Referencia que guardará el último estado del Mouse, por cada loop.
Figura figNeutral; // Referencia del tercer objeto para prueba de colisiones. Es inmóvil.
SpriteFont font; // Se guardará el tipo de fuente que se usará en los textos.
// Tomarán el valor de 'true' en caso de que exista alguna colisión a nivel pixel.
bool huboColisionPix;
bool huboColisionNJ1Pix;
bool huboColisionNJ2Pix;
// Tomarán el valor de 'true' en caso de que exista alguna intersección de Rectangle.
bool huboColisionRect;
bool huboColisionNJ1Rect;
bool huboColisionNJ2Rect;
protected override void Initialize()
{
jugador = new Figura();
jugador2 = new Figura();
figNeutral = new Figura();
huboColisionPix = false;
huboColisionNJ1Pix = false;
huboColisionNJ2Pix = false;
huboColisionRect = false;
huboColisionNJ1Rect = false;
huboColisionNJ2Rect = false;
base.Initialize();
}
public void ControlarColisiones()
{
if (jugador.CrearRectanguloLim().Intersects(jugador2.CrearRectanguloLim()))
{
huboColisionRect = true;
huboColisionPix = ColisionPixelPerfectBool(jugador.GetMatrizPixeles(), jugador.GetMatrizTextura(),
jugador2.GetMatrizPixeles(), jugador2.GetMatrizTextura()
);
}
else
huboColisionRect = false;
if (jugador.CrearRectanguloLim().Intersects(figNeutral.CrearRectanguloLim()))
{
huboColisionNJ1Rect = true;
huboColisionNJ1Pix = ColisionPixelPerfectBool(jugador.GetMatrizPixeles(), jugador.GetMatrizTextura(),
figNeutral.GetMatrizPixeles(), figNeutral.GetMatrizTextura()
);
}
else
huboColisionNJ1Rect = false;
if (jugador2.CrearRectanguloLim().Intersects(figNeutral.CrearRectanguloLim()))
{
huboColisionNJ2Rect = true;
huboColisionNJ2Pix = ColisionPixelPerfectBool(jugador2.GetMatrizPixeles(), jugador2.GetMatrizTextura(),
figNeutral.GetMatrizPixeles(), figNeutral.GetMatrizTextura()
);
}
else
huboColisionNJ2Rect = false;
}
protected override void Draw(GameTime gameTime)
{
string anchoPantalla = Convert.ToString(GraphicsDevice.Viewport.Width);
string altoPantalla = Convert.ToString(GraphicsDevice.Viewport.Height);
string textoColision;
GraphicsDevice.Clear(Color.CornflowerBlue);
if (huboColisionRect || huboColisionNJ1Rect || huboColisionNJ2Rect)
{
GraphicsDevice.Clear(Color.BlueViolet);
textoColision = "Casi";
if (huboColisionPix || huboColisionNJ1Pix || huboColisionNJ2Pix)
{
GraphicsDevice.Clear(Color.DarkSlateBlue);
textoColision = "Si";
}
}
else
{
GraphicsDevice.Clear(Color.CornflowerBlue);
textoColision = "No";
}
// ============ Comienza dibujado ===================
spriteBatch.Begin();
//...
//... y aquí va todo lo que teníamos anteriormente en el método.
//...
}
}
Ahora si ejecutamos el programa, podrán notar que funciona todo lo que estuvimos haciendo.
¡Excelente! Pero...¿cómo sabemos si realmente funciona?
Para terminar esta entrada, añadiremos algunos métodos que me ayudaron mucho mientras practicaba. Necesité que se pudiera visualizar algunos datos de la figura cuadrada, como su posición, su ángulo de rotación (en grados y radianes), la coordenada (x,y) de su punto de Ancla (u origen) y las coordenadas de cada esquina de su respectivo Rectangle. Lo único que hice fue añadir textos que puedan leerse en la pantalla y que puedan modificarse según las transformaciones de la figura. Todo esto lo hice en el método Draw(). Agreguemos eso:
protected override void Draw(GameTime gameTime)
{
string anchoPantalla = Convert.ToString(GraphicsDevice.Viewport.Width);
string altoPantalla = Convert.ToString(GraphicsDevice.Viewport.Height);
string textoColision;
GraphicsDevice.Clear(Color.CornflowerBlue);
if (huboColisionRect || huboColisionNJ1Rect || huboColisionNJ2Rect)
{
GraphicsDevice.Clear(Color.BlueViolet);
textoColision = "Casi";
if (huboColisionPix || huboColisionNJ1Pix || huboColisionNJ2Pix)
{
GraphicsDevice.Clear(Color.DarkSlateBlue);
textoColision = "Si";
}
}
else
{
GraphicsDevice.Clear(Color.CornflowerBlue);
textoColision = "No";
}
// ============ Comienza dibujado ===================
spriteBatch.Begin();
// Se dibuja a cada Jugador
jugador.Dibujar(spriteBatch);
jugador2.Dibujar(spriteBatch);
figNeutral.Dibujar(spriteBatch);
// 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.OrangeRed,
MathHelper.ToRadians(90),
new Vector2(0,0),
0.8f,
SpriteEffects.None,
0f);
// Se dibuja un texto que indica si hubo alguna colisión o no.
spriteBatch.DrawString(font,
"Colision: " + textoColision,
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.X,
GraphicsDevice.Viewport.TitleSafeArea.Bottom - 25),
Color.Orange,
0f,
new Vector2(0f, 0f),
0.8f,
SpriteEffects.None,
0f);
// Se dibuja un texto donde indica las coordenadas (x,y) del jugador1
spriteBatch.DrawString(font,
"Jugador 1: (" + jugador.GetPosicionX() + ", " + jugador.GetPosicionY() + ")",
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.Center.X + 100,
GraphicsDevice.Viewport.TitleSafeArea.Y),
Color.Aquamarine,
0f,
new Vector2(0f, 0f),
0.8f,
SpriteEffects.None,
0f);
// Se dibuja un texto donde indica las coordenadas (x,y) del jugador2
spriteBatch.DrawString(font,
"Jugador 2: (" + jugador2.GetPosicionX() + ", " + jugador2.GetPosicionY() + ")",
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.Center.X + 100,
GraphicsDevice.Viewport.TitleSafeArea.Y+25),
Color.Aquamarine,
0f,
new Vector2(0f, 0f),
0.8f,
SpriteEffects.None,
0f);
// Se dibuja un texto que indica el ángulo (en radiandes) del jugador1
spriteBatch.DrawString(font,
"Angulo J1 [rad]: (" + jugador.GetAngulo() + ")",
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.Center.X + 100,
GraphicsDevice.Viewport.TitleSafeArea.Y+ 50),
Color.Aquamarine,
0f,
new Vector2(0f, 0f),
0.8f,
SpriteEffects.None,
0f);
// Se dibuja un texto que indica el ángulo (en grados) del jugador1
spriteBatch.DrawString(font,
"Angulo J1 [gra]: (" + MathHelper.ToDegrees(jugador.GetAngulo()) + ")",
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.Center.X + 100,
GraphicsDevice.Viewport.TitleSafeArea.Y + 75),
Color.Aquamarine,
0f,
new Vector2(0f, 0f),
0.8f,
SpriteEffects.None,
0f);
// Se dibuja un texto donde indica las coordenadas del punto de ancla
// (u "Origen") (x,y) del jugador1
spriteBatch.DrawString(font,
"Punto de Ancla J1: (" + jugador.GetPuntoAncla().X + ", " + jugador.GetPuntoAncla().Y + ")",
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.Center.X + 100,
GraphicsDevice.Viewport.TitleSafeArea.Y + 150),
Color.Aquamarine,
0f,
new Vector2(0f, 0f),
0.8f,
SpriteEffects.None,
0f);
// Se dibuja un texto donde indica las coordenadas (x,y) del
// Rectángulo utilizado como BoundinBox (o Cuadro Delimitador) del jugador1
spriteBatch.DrawString(font,
"Coord (X,Y) RectangleJ1: (" + jugador.CrearRectanguloLim().X + ", " + jugador.CrearRectanguloLim().Y + ")",
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.Center.X + 100,
GraphicsDevice.Viewport.TitleSafeArea.Y + 175),
Color.Aquamarine,
0f,
new Vector2(0f, 0f),
0.8f,
SpriteEffects.None,
0f);
// Se dibuja un texto donde indica el valor (ancho,alto) del
// Rectángulo utilizado como BoundinBox (o Cuadro Delimitador) del jugador1
spriteBatch.DrawString(font,
"Ancho y Alto RectangleJ1: (" + jugador.CrearRectanguloLim().Width + ", " + jugador.CrearRectanguloLim().Height + ")",
new Vector2(GraphicsDevice.Viewport.TitleSafeArea.Center.X + 100,
GraphicsDevice.Viewport.TitleSafeArea.Y + 200),
Color.Aquamarine,
0f,
new Vector2(0f, 0f),
0.8f,
SpriteEffects.None,
0f);
spriteBatch.End();
// ============ Termina dibujado ===================
base.Draw(gameTime);
}
De esta manera, cuando ejecutemos el programa, vamos a poder visualizar algunos datos importantes sobre nuestra figura cuadrada. Se vería algo como esto:
|
Detección por Rectangle y Pixel Perfect, con algunos datos sobre los objetos. |
...mmm... sin embargo, a decir verdad, eso no parece ser suficiente. Estaría muy bueno que los Rectangles que creamos se
pudieran visualizar de alguna forma, ¿verdad?. ¡Buenas noticias!
Mientras investigaba, me encontré con que eso es posible. Así que para concluir esta entrada, haremos que los Rectangles se puedan ver para observar cómo funcionan. Para ello, necesitaremos añadir a nuestro proyecto una nueva textura de tan solo 1x1 pixel. ¿Extraño? Ya veremos para qué sirve.
Entonces, comencemos añadiendo un nuevo atributo a nuestra clase Figura. Será un Texture2D cuyo modificador será static, pues no pertenecerá únicamente a un objeto, sino a todos los objetos de la misma clase. Llamaremos a este atributo como bound ("límite" en inglés). En este campo guardaremos la textura de 1x1 pixel. Pueden crearla ustedes mismos en Paint y luego agregarla al proyecto. Me hubiera gustado mostrarla aquí abajo, pero es tan pequeña que no puedo observarla jajaja.
Luego crearemos un método sobrecargado a Dibujar(), el cual tendrá 2 parámetros más: uno que indique el color en que se quiera ver el Rectangle, y otro que indique el valor de transparencia del Rectangle.
Añadimos lo mencionado, dentro de la clase Figura:
class Figura
{
private Texture2D textura; // Textura que se usará para representar al jugador.
private Vector2 posicion; // Contendrá las coordenadas de su posición.
Vector2 puntoAncla; // Contendrá las coordenadas de su punto de anclaje(u Origen) como referencia para rotación y escala.
private float velocidadMov, // Representa al valor de la velocidad de su desplazamiento.
angulo, // Guardará el valor del ángulo de inclinación del objeto.
escala; // Guardará el valor de la escala de la Textura (o Sprite).
public static Texture2D bounds; // Utilizamos esta textura para dibujar los BoundingBox.
public void Dibujar(SpriteBatch spriteBatch, Color colorDelCuadro, byte transparencia)
{
// Se elije aplica Transparencia al color indicado.
colorDelCuadro.A = transparencia;
// Utilizando lo anterior, dibujo un Cuadro Delimitador.
spriteBatch.Draw(bounds, CrearRectanguloLim(), colorDelCuadro);
// Dibujo al objeto.
spriteBatch.Draw(
textura,
posicion,
null,
Color.White,
angulo, //MathHelper.ToRadians(45)
puntoAncla,
escala,
SpriteEffects.None,
0f
);
}
}
Con esto, logramos que se pueda visualizar nuestro Rectangle en la pantalla. Lo que hace el método spriteBatch.Draw() de la línea 18, es tomar una textura ('bounds' en nuestro caso) y rellenar con esa textura un Rectangle (que obviamente es el Rectangle que usamos para detectar colisiones) con el color y la transparencia que le pasamos como parámetro.
Lo que finalmente queda por hacer es invocar al método sobrecargado que creamos recién dentro del método Draw() de la clase principal Game1. Terminemos con esto reemplazando lo viejo por lo nuevo: