Gráficos
Resumen: Clase Curve (curva de
Bézier), clase Rectangle, clase MorphShape.
Clase
Curve.
La clase Curve
ubicada en el paquede org.newdawn.slick.geom
permite crear una curva de Bézier.
Una curva de Bézier está formada por
un punto inicial, por varios puntos de control y por un punto final.
De acuerdo a la cantidad de
puntos que contiene la curva recibe un nombre, por ejemplo, una curva
con 2 puntos se la llama curva de Bézier lineal, una curva con 3
puntos se llama curve de Bézier cuadrática, y
así.
La curva debe
pasar por el punto inicial y por el punto final. Los
puntos de control siven
para guiar a la curva pero
no necesariamente la curva pasa por ellos.
Existen
animaciones sobre cómo se va dibujando una
curva de Bézier:
Slick2D brinda la posibidad de crear
una curva de Bézier cúbica, es decir, con dos puntos de control.
La clase Curve
provee dos constructores:
- new Curve(Vector2f comienzo, Vector2f primerControl, Vector2f segControl, Vector2f fin).
- new Curve(Vector2f comienzo, Vector2f primerControl, Vector2f segControl, Vector2f fin, int numSeg).
Ambos contructores permiten crear
curvas de Bézier a partir del punto inicial, punto final y los dos
puntos de control.
El segundo constructor además permite
especificar el número de segmentos por el cual va a estar compuesto
la curva, al igual que clase Circle.
Para dibujar una curva de Bézier use
el método draw()
de la clase Graphics.
La clase Curve
contiene solamente 3 métodos públicos:
- boolean closed(): Retorna siempre false debido a que Slick2D considera que una curva nunca es cerrada.
- Vector2f pointAt(float t): Devuelve un Vector con las coordenadas del punto que corresponde al parámetro t. Es importante saber que a la curva de Bézier se la puede representar matemáticamente como una ecuación paramétrica y es por ello que este método tiene sentido.
- Shape transform(Transform transform): Este método permite transformar la curva mediante un objeto Transform. Como ya dije anteriormente, más adelante volveremos sobre la clase Transform.
Clase
Rectangle.
La clase Rectangle
como su nombre lo indica permite crear un rectángulo. Provee
solamente un constructor:
- new Rectangle(float x, float y, float ancho, float alto).
El
constructor recibe como parámetro la esquina superior izquierda (x,
y) y también el ancho y alto del rectángulo.
Algunos
métodos:
- boolean contains(float x, float y): Verifica si el punto está o no dentro del rectángulo.
- float getHeight().
- float getWidth().
- void setWidth(float width).
- void setHeight(float height).
- void grow(float crecHor, float crecVert): Este método hace crecer al rectángulo tanto como indican los parámetros. Por ejemplo, si el parámetro crecHor (crecimiento horizontal) es igual a 40 entonces el rectángulo crecerá 40 para el lado izquierdo y 40 para el lado derecho, aumentando en 80 el ancho original. Si se usan parámetros negativos entonces el rectángulo se hará más angosto. Aunque no siempre será el caso. Por ejemplo, vea el siguiente código:
rect = new
Rectangle(300, 300, 80, 40);
rect.grow(-200,
0);
Observe que el ancho del rectángulo
es 80
y le estoy diciendo que crezca -200 de cada lado (es
decir, decrecer 200). El
rectángulo va a decrecer tanto que luego empezará a crecer
terminando con una ancho de 400
- 80 = 320. Eso
es porque el lado derecho original se desplazo 200 para la izquierda
y el lado izquierdo se desplazo 200 para la derecha, convirtiéndose
así el lado derecho en el lado izquiero y viceversa, generando
de esta manera un crecimiento en el rectángulo.
- void scaleGrow(float escalaHor, float escalaVert): Similar al anterior pero se basa en crecimiento relativo al actual, definiendo parámetros de escalas. El regimen de la escala no me parece muy intuitivo así que no vale la pena explicarlo.
- boolean
intersects(Shape
shape): Devuelve true
si el rectángulo interseca con la figura pasada
como parámetro.
Clase
MorphShape.
La clase MorphShape
ubicada en el paquete org.newdawn.slick.geom
provee servicios para animar las figuras. Es realmente muy útil y
recomiendo que le prestes mucha atención pues esta clase te
permitirá crear animaciones de manera muy sencilla. Aquí es dónde
Slick2D empieza a ser muy útil.
Antes de seguir vamos a ver las
"partes" de una animación. En primer lugar, tenemos que
fijar la parte base o lo inicial de la animación. Luego tenemos que
fijar puntos de control. En algún momento, la animación se corresponderá con cada uno de esos puntos de control. Por último tenemos que unir todos
esos puntos de control, darle una velocidad de movimiento y listo. Lo dicho antes se resume en la
siguiente lista:
1- Crear figura base.
2- Crear figuras de control, pueden
ser tantas como se necesite.
3- Unir todos esos puntos de control y
ejecutar la animación.
Puede parecer un tanto complicado pero Slick2D nos ofrece la
posibilidad de hacerlo de manera muy sencilla.
La clase MorphShape
nos provee un sólo constructor:
- new MorphShape(Shape base).
El constructor recibe la figura de
base, es decir, lo inicial de la animación. Luego, tenemos que ir
agregando los puntos de control, usando el método addShape(Shape
figura). Acá hay algo que notar, al momento de agregar una
figura se debe tener en cuenta que sea la misma figura que la figura
base. De otra manera Slick2D lanzará un RuntimeException.
Cuando digo que sea la misma figura me refiero a que tengan la misma
cantidad de vértices, pues esto es lo que le interesa a Slick2D. No
importa la posición ni el tamaño de la figura, importa como ya
dije, la cantidad de vértices. Para obtener la cantidad de vértices
de una figura puede usar el método getPointCount()
de la clase Shape.
Recuerde que la clase Shape
es la clase padre de todas las figuras que vamos viendo.
A continuación se muestra un código
de cómo crear un MophShape.
@Override
public
void init(GameContainer
container) throws SlickException
{
// Círculo
de base.
Circle
circulo1 = new Circle(100,
100, 40);
// Primer
punto de control.
Circle
circulo2 = new Circle(100,
100, 20);
// Segundo
punto de control.
Circle
circulo3 = new Circle(100,
100, 10);
//
Creación del MorphShape (debe ser un atributo).
morphShape1 = new
MorphShape(circulo1);
// Agrego
los puntos de control.
morphShape1.addShape(circulo2);
morphShape1.addShape(circulo3);
}
Observe que creamos 3 círculos, de
los cuales, uno es el base y los otros 2 son puntos de control.
Fíjese que los círculos están ubicados en la misma posición
(100, 100) y
que sólo cambian los radios. Por lo tanto, la animación mostrará a
un círculo cambiando de tamaño pero siempre en la misma posición.
Note además que si ahora desea agregar como punto de control a un
Rectángulo no podrá sin embargo sí podrá agregar una Elipse. ¿Por
qué? Por los números de vértices. Un rectángulo posee 4 vértices
mientras que un círculo o una elipse poseen muchos vértices
(exactamente 53 vértices).
Un último dato sobre esto, le pido
que recuerde el concepto de números de segmentos para dibujar un
círculo. Bien, usted puede pensar que con 3 segmentos entonces
corresponde 3 vértices pues esto no es así. Slick2D realiza algunos
cálculos y terminan siendo 4 vértices. Como conclusión podemos
decir que no se confíe con determinar la cantidad de segmentos para
calcular el número de vértices en un círculo o elipse, ya que
esto varía según Slick2D considere necesario para dibujar.
Según lo anterior, es correcto
agregar un círculo de 3 segmentos y un rectángulo al mismo
morphShape.
Hasta ahora hemos creado el MorphShape
con la figura base y le hemos agregado puntos de control, nos falta
conectar todos esos puntos y darle movimiento. En esta etapa Slick2D
nos ayuda mucho. La clase MorphShape
provee dos métodos:
void
setMorphTime(float
tiempo)
void
updateMorphTime(float
delta)
Antes de explicar estos métodos es
preciso saber que la figura base representa el tiempo cero. El primer
punto de control representa el tiempo 1. El segundo punto de control
representa el tiempo 2, y así. En nuestro ejemplo, sería:
circulo1
cuya posición es (100, 100)
y radio = 40 representa el tiempo 0.
circulo2
cuya posición es (100, 100)
y radio = 20 representa el tiempo 1.
circulo3
cuya posición es (100, 100)
y radio = 10 representa el tiempo 2.
Cuando la animación se inicia, el
tiempo es cero. Por eso, lo que el morphShape
muestra es la figura base (circulo1)
quedándose ahí sin ningún movimiento.
Si invocamos al método setMorphTime()
así:
morphShape1.setMorphTime(1)
// Note el
parámetro igual a tiempo=1.
implicaría que el morphShape
muestre el primer punto de control pero sin ninguna continuidad, es
decir, cambia directamente del circulo1
al circulo2. Lo que
deseamos hacer es que este cambio sea gradual.
Para ello podemos usar el método
updateMorphTime().
Este método recibe como parámetro el valor diferencial del tiempo,
es decir, el tiempo transcurrido desde la última vez que se lo
llamó. Entonces podemos usar el parámetro delta
del método update()
para ir llamando a ese método. Recordar que el parámetro delta
tiene como valor el tiempo desde el cual se llamó la última vez al
método update()
expresado en milisegundos. Lo que debemos hacer ahora es crear una
escala para calcular la velocidad de movimiento de nuestra animación.
Observe la siguiente figura:
La escala que propuse fue que la animación debe tardar 500 ms para
llegar al primer punto de control (t = 1). Para llegar al segundo
punto de control (t = 2) debe tardar otros 500 ms, en total 1000 ms.
Entonces por regla de 3 simple tenemos
que si t = 1 equivale a 500 ms entonces el valor de delta
equivaldrá a t = delta /
500. Lo podés visualizar mejor en:
500 ms ----
t = 1
delta
ms ---- t = delta * 1
/ 500
Bien, entonces lo que nos queda es
llamar al método updateMorphTime()
haciendo el cambio de escala.
@Override
public
void update(GameContainer
container, int delta) throws
SlickException {
morphShape1.updateMorphTime(normalizar(delta));
}
public
float normalizar(float
v) {
return
v / 500;
}
Por último pintamos el morphShape
en el método render().
@Override
public
void render(GameContainer
container, Graphics g) throws
SlickException {
g.fill(morphShape1);
}
Traslado de una figura
con MorphShape.
Ahora haremos que nuestra figura se
traslade. Para ello es necesario cambiar la posición. El siguiente
código agrega dos líneas al ejemplo anterior para crear un nuevo morphShape que traslade una figura:
@Override
public
void init(GameContainer
container) throws SlickException
{
Circle
circulo1 = new Circle(100,
100, 40);
Circle
circulo2 = new Circle(100,
100, 20);
Circle
circulo3 = new Circle(100,
100, 10);
morphShape1 = new
MorphShape(circulo1);
morphShape1.addShape(circulo2);
morphShape1.addShape(circulo3);
//
morphShape2 debe ser un atributo.
morphShape2
= new MorphShape(new
Circle(240,
100, 30));
morphShape2.addShape(new
Circle(240,
300, 30));
}
Fíjese que hemos cambiado la posición
en y, dejando la
posición en x y el
radio igual. Por lo que el círculo se trasladará en forma vertical
(de arriba a abajo y de abajo hacia arriba).
A continuación se muestra el código
completo:
import
org.newdawn.slick.AppGameContainer;
import
org.newdawn.slick.BasicGame;
import
org.newdawn.slick.GameContainer;
import
org.newdawn.slick.Graphics;
import
org.newdawn.slick.SlickException;
import
org.newdawn.slick.geom.Circle;
import
org.newdawn.slick.geom.MorphShape;
public
class MiJuego extends
BasicGame {
private
AppGameContainer contenedor;
private
MorphShape morphShape1, morphShape2;
public
MiJuego() throws SlickException
{
super("Gráficos");
contenedor = new
AppGameContainer(this);
contenedor.setShowFPS(false);
contenedor.setDisplayMode(600,
500, false);
contenedor.start();
}
@Override
public
void init(GameContainer
container) throws SlickException
{
Circle
circulo1 = new Circle(100,
100, 40);
Circle
circulo2 = new Circle(100,
100, 20);
Circle
circulo3 = new Circle(100,
100, 10);
morphShape1 = new
MorphShape(circulo1);
morphShape1.addShape(circulo2);
morphShape1.addShape(circulo3);
morphShape2 = new
MorphShape(new
Circle(240, 100, 30));
morphShape2.addShape(new
Circle(240, 300, 30));
}
@Override
public
void update(GameContainer
container, int delta) throws
SlickException {
morphShape1.updateMorphTime(normalizar(delta));
morphShape2.updateMorphTime(normalizar(delta));
}
public
float normalizar(float
v) {
return
v / 500f;
}
@Override
public
void render(GameContainer
container, Graphics g) throws
SlickException {
g.fill(morphShape1);
g.fill(morphShape2);
}
}
No hay comentarios:
Publicar un comentario