Me suscribi para seguir el Twitter de JavaFX y el primer post que encontré me pareció entretenido. Se llama Pastello. Es un juego de "puzzle" en que debes dibujar la solución para que una bolita llegue hasta donde está la estrella.
Lo interesante del proyecto es la FISICA de los elementos. Así, por ejemplo, puedes dejar caer bolitas sobre la bola principal, de manera que esta gire subiendo por una rampa que dibujaste con un triangulo. También puedes agregar contrapesos, etc. Un tanto adictivo! ;D
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".
// 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 ] } ] } }
// 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 ; } };