Mostrando entradas con la etiqueta animation. Mostrar todas las entradas
Mostrando entradas con la etiqueta animation. Mostrar todas las entradas

jueves, 23 de abril de 2009

Cache:true ... otro detalle para mejorar el rendimiento gráfico


Si, por ejemplo, utilizas una figura con una gradiente -o sombra- como fondo de un área, es mejor agregar a esa figura el parámetro cache:true, de manera de evitar que el objeto sea re-dibujado con cada cambio que se realiza por encima de él (en el caso de una gradiente, debe volver a recalcular y redibujar cada franja de color que conforma la transición entre los colores... lo que resulta sumamente "caro" en términos de procesamiento).



El código al pie del artículo

Al agregar cache:true se le indica al sistema que debe generar la imagen una vez y guardarla en cache, de esta menera, cuando se realiza un cambio sobre ella, sólo recupera la imagen desde una copia almacenada en memoria (como bytes) y pinta los cambios encima, lo que resulta sumamente rápido y menos "costoso" en ciclos de procesamiento.

En todo caso, no debes utilizar cache:true cuando la imagen va a cambiar con frecuencia, porque esto sólo producirá que sea re-generada con cada cambio, pero además almacenada, lo que da por resultado un proceso mucho más "costoso". El cache:true debe ser utilizado en imágenes que NO cambian frecuentemente.




import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.animation.Timeline;
import javafx.animation.KeyFrame;
import javafx.scene.effect.DropShadow;

var px:Number;
var py:Number;
Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse:true
keyFrames : [
KeyFrame {
time : 0s
values: [px=>100, py=>100]
}
KeyFrame {
time : 3s
canSkip : true
values: [px=>1180, py=>680]
}
]
}.play();

var angle:Number;
Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse:true
keyFrames : [
KeyFrame {
time : 0s
values: [angle=>0]
}
KeyFrame {
time : 1.5s
canSkip : true
values: [angle=>360]
}
]
}.play();

Stage {
title: "Application title"
width: 1024
height: 768
scene: Scene {
content: [
Rectangle {
cache:true
x: 0, y: 0
width: 1280, height: 768
fill: LinearGradient {
startX : 0.0
startY : 0.0
endX : 1.0
endY : 0.0
stops: [
Stop {
color : Color.BLACK
offset: 0.0
},
Stop {
color : Color.BLUE
offset: 1.0
},

]
}
effect: DropShadow { offsetX:10, offsetY:10 }
}
Rectangle {
x: bind px, y: bind py
width: 200, height: 200
fill: Color.RED
rotate: bind angle
}

]
}
}

domingo, 29 de marzo de 2009

Por qué incluir canSkip:true en los timelines


Este video muestra la diferencia en el rendimiento de las animaciones gráficas con y sin canSkip:true. Es recomendable incluir este parametro en los KeyFrame que alteran valores (values:) asociados a animaciones y efectos (cambio de posiciones, variación de sombras, difuminado, etc... cualquier efecto o movimiento gradual).

Una persona del grupo JavafxProgramming creo una aplicación Flex que luego replicó en JavaFX. Su problema era el rendimiento de JavaFX, que hacia su aplicación inutilizable. Pidió consejos al foro, pero nada funcionó... hasta que incluyó el canSkip:true en los timelines... recién ahí la aplicación funcionó como debía.

Quizás sería bueno que Sun incluyera el canSkip con true como valor por defecto. Esto puede terminar desprestigiando la plataforma.

En todo caso, sería bueno que todos comparta este detalle o lo tengan muy presente al momento de usar los timelines o de tener problemas con el rendimiento.

miércoles, 4 de febrero de 2009

JavaFX + Phys2D : JavaFX aprende sobre física (ojo, no solo ciencia)


Phys2D es una excelente API de física, que no solo sirve para simular descabellados experimentos de cientificos encerrados en sus laboratorios o estudiantes queriendo experimentar lo aprendido en clases, esta API es excelente para desarrollar JUEGOS. Si, porque con ella puedes hacer que los elementos del juego se comporten como elementos reales, que se deslicen por pendientes, reboten o se comporten de manera elástica, aceleren, frenen o colisionen.



Lo mejor es que es código abierto... para más información, visita el sitio web de Phys2D

Este ejemplo comprende tres archivos:

1) la biblioteca de Phys2D, que debe estar incluida en los libraries del proyecto (y que incluyo en el directorio lib).

2) una clase que describe EL MODELO de elementos en la simulación... en este caso, incluye una BASE (o anchor, ancla en inglés... que es el elemento fijo en la parte superior), un RESORTE, una masa y un trozo rigido que lo une a la segunda masa. En otras palabras, es un péndulo doble que cuelga por un resorte. OJO: Porque el modelo es una CLASE JAVA... así que de pasada pueden ver lo simple que es integrar Java + JavaFX Script.

3) la animación en JavaFX, que incluye un Timeline para generar los cálculos y el código que pinta los elementos dependiendo de las coordenadas recibidas desde el modelo físico. Bastante simple, como verán... pero no menos sofisticado.

Descarga el projecto Netbeans 6.5. DOWNLOAD the Netbeans project, here!

sábado, 31 de enero de 2009

JavaFX: ¿Cuantas bolitas? (test de rendimiento gráfico)


Este ejercicio contiene 3 controles de barra que permiten ajustar:

1) El número de bolas rebotando en la ventana;
2) La transparencia de las bolas;
3) El radio de las bolas.



Cada nueva bola que se agrega, lo hace con un efecto (cambia el radio un par de veces) y luego continua rebotando entre los márgenes de la ventana. Si se aumenta el tamaño de la ventana, el area de desplazamiento aumenta, si se reduce, las bolas que se encuentran más alla del nuevo margen, regresan al centro de la ventana.

Un area de texto muestra el número de bolas en la ventana, el porcentaje de transparencia y el radio de los círculos.

Una instrucción importante es "canSkip:true", en el Timeline que actualiza la posicion de las bolas (y que por lo tanto, las redibuja). Esta instrucción evita que la CPU se sobrecargue de trabajo y solo realiza la actualización de las bolas si hay capacidad de procesamiento disponible. Intenten con unas 200 bolas y quiten el canSkip y veran como el programa se "pega".

Archivo Maix.fx
package howmany;

import javafx.scene.*;
import javafx.stage.Stage;
import javafx.ext.swing.SwingSlider;
import javafx.scene.layout.VBox;
import javafx.scene.text.*;

// si cambia tamaño de la ventana
// reposiciona los circulos mas alla del margen
var maxX=250.0 on replace { circles.resetPos() };
var maxY=250.0 on replace { circles.resetPos() };

// seleccion numero de bolas
var selector = SwingSlider {
minimum: 0
maximum: 100
value: 0
vertical: false
};

// transparencia de las bolas
var transparency = SwingSlider {
minimum: 0
maximum: 100
value: 50
vertical: false
};

// radio de las bolas
var radius = SwingSlider {
minimum: 0
maximum: 40
value: 20
vertical: false
};

// texto para desplegar valores
var total = Text {
font: Font { size: 18 }
textOrigin:TextOrigin.TOP;
};

// componente que contiene los circulos
var circles = element {
balls:bind selector.value;
radius:bind radius.value;
level:bind transparency.value / 100.0;
display: total
maxX:bind maxX
maxY:bind maxY
};

Stage {
// si cambia tamaño de la ventana
// actualiza variables con dimensiones
width: bind maxX with inverse
height: bind maxY with inverse

scene: Scene { content: [
circles,
VBox { content: [
selector,
transparency,
radius,
total ]
} ]
}
}


Archivo element.fx

package howmany;

import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.animation.*;
import java.lang.Math;


// clase que extiende circulos
class ball extends Circle {

// "sobreescribe" los parametros del circulo normal,
// de manera de tener los valores en la nueva clase
override var stroke = null;
override var fill = Color.RED;
override var centerX = 100;
override var centerY = 100;
override var radius = 10;
override var scaleX;
override var scaleY;

public var incx;
public var incy;

// mueve el circulo en los incrementos de la instancia
function move():Void {
centerX += incx;
centerY += incy;
if (centerX<0 or centerX>maxX) then
incx = -incx;
if (centerY<0 or centerY>maxY) then
incy = -incy;
};

// efecto para cuando crea el circulo
// cambia un par de veces de tamaño (como rebote)
var dropIn = Timeline {
repeatCount:5
autoReverse:true
keyFrames: [
at (0s) { scaleX => 2.0; scaleY => 2.0 }
at (0.3s) { scaleX => 1.0; scaleY => 1.0 }
]
};

// al crear el circulo, dispara el efecto de rebote
postinit {
dropIn.play();
};

};


// clase que contiene a todos los circulos
public class element extends CustomNode {

public var balls = 50.0;
public var display = Text { };
public var maxX;
public var maxY;
// si cambia la transparencia
public var level = 1.0 on replace {
// actualiza la opacidad de todos los circulos
for (obj in objs) obj.opacity = level; };
// si cambia el radio
public var radius = 10.0 on replace {
// actualiza el radio de todos los circulos
for (obj in objs) obj.radius = radius; };

// arreglo que contiene todos los circulos
var objs:ball[];
// colores que asigna a los circulos
var colors:Color[]=[
Color.RED, Color.BLUE, Color.YELLOW,
Color.PURPLE, Color.CYAN];

var i=0;

// agrega nuevo circulo al componente
function addBall():Void {
var ix=Math.random()*15 - 7;
var iy=Math.random()*15 - 7;
var circ = ball {
centerX:maxX/2
centerY:maxY/2
incx: ix
incy:iy
radius:radius
fill: colors[i mod sizeof colors]
opacity:level
};
insert circ into objs;
};


// crea el componente con los circulos
override function create():Node {

// chequea si debe agregar o quitar circulos
Timeline {
repeatCount:Timeline.INDEFINITE
keyFrames: [
KeyFrame {
time: 0.2s
action: function():Void {
display.content=
"{sizeof objs} / {(level*100) as Integer}% / {radius}";
if (sizeof objs < balls) then {
addBall();
i++;
}
if (sizeof objs > balls) then
delete objs[0];
}
}
]
}.play();

// mueve los circulos
Timeline {
repeatCount:Timeline.INDEFINITE
keyFrames: [
KeyFrame {
time: 0.04s
// para evitar que se sobrecargue de trabajo
canSkip: true
action: function():Void {
for (obj in objs)
obj.move();
}
}
]
}.play();

// retorna el componente con los circulos
Group {
content: bind objs
};

};

// reposicion los circulos si excende el margen
public function resetPos():Void {
for (obj in objs) {
var pos = obj.boundsInScene;
if (pos.maxX > maxX) then
obj.centerX = maxX/2 ;
if (pos.maxY > maxY) then
obj.centerY = maxY/2 ;
}
};

}

miércoles, 28 de enero de 2009

Mi primer juego en JavaFX: Alunizaje


Al juego le falta un par de detalles, como que la nave aparezca inicialmente en cualquier area de la pantalla. Por ahora aparece al centro a una velocidad y dirección aleatoria. También me falta agregar que la "base" de alunizaje tenga una posición aleatoria y que detecte cuando aterriza fuera de ella. Son detalles menores que quizas hasta puedes terminar tú.



El juego usa 4 teclas. La barra de espacio, para encender el motor principal. Las teclas de flecha hacia izquierda y derecha, que encienden los motos auxiliares para inclinar el módulo, y la tecla con la flecha hacia arriba, que enciende el piloto automático, para frenar la rotación del módulo (automáticamente).

Está bastante realista, los retrocohetes se encienden cuando corresponde y dejan de operar cuando no hay mas combustible. También incluye alarmas visuales y sonoras para cuando se excede la velocidad de aterrizaje, la inclinación del módulo es incorrecta cuando esta cerca de alunizar o cuando el combustible baja de 1/3 del estanque.

Me resultó entretenido hacer este juego y también descubrir LO INCREIBLEMENTE SIMPLE que es desarrollar cosas bastante decentes en tan poco tiempo (el grueso estaba operando en un par de horas... ¡y eso que estoy aprendiendo JavaFX script!).

La facilidad de incluir gráficos desde Adobe Illustrator sin duda es una ventaja inmensa. Especialmente, porque puede ser reemplazada por el trabajo de un diseñador más experto, sin necesidad de tocar el código de JavaFX script ya desarrollado. De hecho, con poco trabajo adicional podría agregarse multiples "skins" o apariencias al juego... escenarios de distintos planetas y naves por ejemplo (pudiendo relocalizar las alarmas, botones, formas de la nave, motores, entorno, etc).

El proyecto Netbeans 6.5 completo está disponible aquí (código fuente, gráfica, sonidos, etc).

El código requiere que esté incluida la libreria JavaFX FXD 1.0 en el proyecto.

sábado, 17 de enero de 2009

JavaFX: Clases de clases... y dos Scene alternadas


Este ejemplo crea una clase que hace rotar un círculo. Luego, crea una nueva clase que utiliza varias instancias de la primera clase para hacer rotar varios círculos dentro del nuevo objeto. Finalmente, crea varias instancias de este último tipo de objeto y los pone en dos Scene que luego alterna... es decir, es un Stage con dos Scene.




package experimentroam;

import javafx.stage.Stage;
import javafx.animation.*;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.shape.*;
import javafx.scene.paint.*;

// clase basica que dibuja circulo moviendose formando un cuadrado
class myObject extends CustomNode {

var x:Number;
var y:Number;

public var ix=0;
public var iy=0;
public var dx=100;
public var dy=100;
public var radius=40;
public var color=Color.RED;

var roamingtimeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
at(0s) { x => ix+radius; y => iy+radius }
at(0.3s) { x => dx-radius; y => iy+radius }
at(0.6s) { x => dx-radius; y => dy-radius }
at(0.9s) { x => ix+radius; y => dy-radius }
at(1.2s) { x => ix+radius; y => iy+radius }
]
};

// mensaje desplegado (compuesto por texto y rectangulo a su alrededor)
override protected function create():Node {
roamingtimeline.play();
Group {
content: [
Circle {
centerX: bind x
centerY: bind y
radius: bind radius
fill: bind color
}
]
}
};

};


// clase compuesta por objetos de la clase precedente
class myBigObject extends CustomNode {

var fig:myObject=myObject{
ix: 0
iy: 0
radius:55
dx:250
dy:250
color:Color.PURPLE
}
var fig2:myObject=myObject{
ix: 40
iy: 40
radius:40
dx:210
dy:210
color:Color.RED
}
var fig3:myObject=myObject{
ix: 80
iy: 80
radius:30
dx:170
dy:170
color:Color.GREEN
}
var fig4:myObject=myObject{
ix: 120
iy: 120
radius:20
dx:130
dy:130
color:Color.BLUE
}
var fig5:myObject=myObject{
ix: 160
iy: 160
radius:10
dx:90
dy:90
color:Color.YELLOW
}
var fig6:myObject=myObject{
ix: 190
iy: 190
radius:5
dx:60
dy:60
color:Color.ORANGE
}

override protected function create():Node {
Group {
content: [
fig,
fig2,
fig3,
fig4,
fig5,
fig6
]
}
};

}


// instancia de clase compuesta
var bigFig:myBigObject=myBigObject{};

// instancia de clase compuesta desplazada en la pantalla
var bigFig2:myBigObject=myBigObject{
translateX:250
translateY:0
};
// instancia de clase compuesta desplazada en la pantalla
var bigFig3:myBigObject=myBigObject{
translateX:250
translateY:250
};
// instancia de clase compuesta desplazada en la pantalla
var bigFig4:myBigObject=myBigObject{
translateX:0
translateY:250
};

var myScene:Scene=Scene {
fill:Color.BLACK
content: [
bigFig,
bigFig2,
bigFig3,
bigFig4
]
};

var myScene2:Scene=Scene {
fill:Color.BLACK
content: [
bigFig,
bigFig3,
]
};

var scenes=[myScene, myScene2];
var activeScene:Integer;

Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
at(0s) { activeScene => 0 }
at(1s) { activeScene => 1 }
]
}.play();

Stage {
title: "Application title"
width: 500+16
height: 500+36
scene: bind scenes[activeScene]
}