Jugando con colisiones elásticas
() translation by (you can also view the original English article)
En este tutorial crearemos un juego donde el objetivo es evitar que
otros objetos colisionen con el cursor. No usaremos los métodos
hitTestObject()
integrados de Flash; en cambio, escribiremos nuestras
propias rutinas de detección de colisiones.
Cada pocas semanas, volvemos a visitar algunas de las publicaciones favoritas de nuestros lectores a lo largo de la historia del sitio. Este tutorial se publicó por primera vez en febrero de 2011.
Vista previa del resultado final
Echemos un vistazo al resultado final para el que trabajaremos:
Paso 1: Comienza
Crear un nuevo archivo Flash (ActionScript 3.0)

Establezca las dimensiones del escenario a 500x500 px y FPS a 32.



Paso 2: la clase Ball
Esta clase contendrá todos los datos
relacionados con una pelota. Una pelota tiene _mass
, _radius
, _xSpeed
y _ySpeed
. Así que haremos una propiedad para cada uno. En el
constructor pasamos la masa, el ángulo y la velocidad de la pelota. Debido
a que la clase se vinculará a un objeto de visualización, podemos
recuperar el radio de nuestra bola dividiendo el ancho del objeto de
visualización por 2. La _xSpeed
y _ySpeed
pueden calcularse
utilizando funciones simples de seno y coseno.
1 |
|
2 |
package
|
3 |
{
|
4 |
import flash.display.Stage |
5 |
import flash.display.Sprite |
6 |
import flash.events.Event |
7 |
|
8 |
public class Ball extends Sprite |
9 |
{
|
10 |
private var _radius:Number = 0 |
11 |
private var _mass:Number = 0 |
12 |
private var _xSpeed:Number = 0 |
13 |
private var _ySpeed:Number = 0 |
14 |
|
15 |
public function Ball(mass:Number = 10.0, angle:Number = Math.PI, speed:Number = 10.0):void |
16 |
{
|
17 |
this.mass = mass |
18 |
this._radius = this.width/2 |
19 |
this.xSpeed = speed*Math.sin(angle) |
20 |
this.ySpeed = speed*Math.cos(angle) |
21 |
}
|
22 |
}
|
23 |
}
|
Para obtener más información sobre estas funciones trigonométricas Math.sin() y Math.cos(), consulte esta sugerencia rápida.
Paso 3: Proporcionando Getters y Setters
En nuestra clase Ball proporcionamos getters y setters para nuestras propiedades.
1 |
|
2 |
public function get radius():Number |
3 |
{
|
4 |
return this._radius |
5 |
}
|
6 |
|
7 |
public function set mass(mass:Number):void |
8 |
{
|
9 |
this._mass = mass |
10 |
}
|
11 |
|
12 |
public function get mass():Number |
13 |
{
|
14 |
return this._mass |
15 |
}
|
16 |
|
17 |
public function set xSpeed(xSpeed:Number):void |
18 |
{
|
19 |
this._xSpeed = xSpeed |
20 |
}
|
21 |
|
22 |
public function get xSpeed():Number |
23 |
{
|
24 |
return this._xSpeed |
25 |
}
|
26 |
|
27 |
public function set ySpeed(ySpeed:Number):void |
28 |
{
|
29 |
this._ySpeed = ySpeed |
30 |
}
|
31 |
|
32 |
public function get ySpeed():Number |
33 |
{
|
34 |
return this._ySpeed |
35 |
}
|
Paso 4: Función de actualización
Esta función actualiza las propiedades xey de nuestra bola de acuerdo
con _xSpeed
y _ySpeed
. Implementaremos esta función en nuestra clase
Ball
.
1 |
|
2 |
public function update():void |
3 |
{
|
4 |
this.x += _xSpeed |
5 |
this.y += _ySpeed |
6 |
}
|
Paso 5: la clase completada
Terminaremos nuestra clase Ball
en este paso.
1 |
|
2 |
package
|
3 |
{
|
4 |
import flash.display.Stage |
5 |
import flash.display.Sprite |
6 |
import flash.events.Event |
7 |
|
8 |
public class Ball extends Sprite |
9 |
{
|
10 |
private var _radius:Number = 0 |
11 |
private var _mass:Number = 0 |
12 |
private var _xSpeed:Number = 0 |
13 |
private var _ySpeed:Number = 0 |
14 |
|
15 |
public function Ball(mass:Number = 10.0, angle:Number = Math.PI, speed:Number = 10.0):void |
16 |
{
|
17 |
this.mass = mass |
18 |
this._radius = this.width/2 |
19 |
this.xSpeed = speed*Math.sin(angle) |
20 |
this.ySpeed = speed*Math.cos(angle) |
21 |
}
|
22 |
|
23 |
public function get radius():Number |
24 |
{
|
25 |
return this._radius |
26 |
}
|
27 |
|
28 |
public function set mass(mass:Number):void |
29 |
{
|
30 |
this._mass = mass |
31 |
}
|
32 |
|
33 |
public function get mass():Number |
34 |
{
|
35 |
return this._mass |
36 |
}
|
37 |
|
38 |
public function set xSpeed(xSpeed:Number):void |
39 |
{
|
40 |
this._xSpeed = xSpeed |
41 |
}
|
42 |
|
43 |
public function get xSpeed():Number |
44 |
{
|
45 |
return this._xSpeed |
46 |
}
|
47 |
|
48 |
public function set ySpeed(ySpeed:Number):void |
49 |
{
|
50 |
this._ySpeed = ySpeed |
51 |
}
|
52 |
|
53 |
public function get ySpeed():Number |
54 |
{
|
55 |
return this._ySpeed |
56 |
}
|
57 |
|
58 |
public function update():void |
59 |
{
|
60 |
this.x += _xSpeed |
61 |
this.y += _ySpeed |
62 |
}
|
63 |
}
|
64 |
}
|
Paso 6: mostrar objetos para nuestra clase Ball
En los archivos fuente incluí un inicio FLA que contiene todos los elementos de la biblioteca que necesita. Puedes dibujarlos tú mismo si quieres, por supuesto. Asegúrese de que su FLA tenga los siguientes objetos de visualización:

(Nota del editor: es un error tipográfico: "ennemyball" debería decir "enemyball").
Paso 7: Vinculación de nuestros objetos de biblioteca
La clase Ball
que acabamos de crear tiene que estar
vinculada al Sprite enemyball
en la biblioteca.



El Sprite
playerball
debe tener Ball
como clase base y PlayerBall
como clase.



El clip de
película score
debe tener una clase Score
.



Paso 8: la clase de la aplicación (clase de documento)
La clase Application
contendrá toda
la lógica del juego. Importamos todas las clases que necesitamos. Como
puede ver, usaremos TweenMax.
A continuación definimos nuestras variables
de campo. La primera variable de campo es ballPlayer
.
Debido a que la
clase base de nuestro Playerball
Sprite es Ball
, podemos almacenar esta
Clase en la variable ballPlayer
. Esto hace que sea más fácil verificar
las colisiones entre el ballPlayer
y las pelotas enemigas.
La
segunda variable de campo es una matriz que contendrá todas nuestras
bolas enemigas. La tercera variable es el temporizador que se usará para
realizar el ciclo principal del juego. El
cuarto y último campo es una instancia de nuestro objeto de la
biblioteca Score
que se usará para mostrar el tiempo transcurrido del
juego. En el constructor llamamos a la función init()
que explicaré en
el siguiente paso.
1 |
|
2 |
package
|
3 |
{
|
4 |
import flash.display.Sprite |
5 |
import flash.display.Graphics |
6 |
import flash.events.Event |
7 |
import flash.events.TimerEvent |
8 |
import flash.events.MouseEvent |
9 |
import flash.geom.Matrix |
10 |
import flash.utils.Timer |
11 |
import flash.ui.Mouse |
12 |
import com.greensock.TweenMax |
13 |
import com.greensock.easing.* |
14 |
|
15 |
public class Application extends Sprite |
16 |
{
|
17 |
|
18 |
private var ballPlayer:Ball |
19 |
private var eballs:Array |
20 |
private var tmr:Timer |
21 |
private var score:Score |
22 |
|
23 |
public function Application():void |
24 |
{
|
25 |
init() |
26 |
}
|
27 |
}
|
28 |
}
|
¡No olvides vincular la clase document!.

Paso 9: La función init()
Eche un vistazo a este código:
1 |
|
2 |
private function init():void |
3 |
{
|
4 |
ballPlayer = new PlayerBall() |
5 |
eballs = new Array() |
6 |
tmr = new Timer(10) |
7 |
score = new Score() |
8 |
|
9 |
stage.align = "TL" |
10 |
stage.scaleMode = "noScale" |
11 |
Mouse.hide() |
12 |
|
13 |
setBackground() |
14 |
|
15 |
score.x = stage.stageWidth/2 |
16 |
score.y = stage.stageHeight/2 |
17 |
stage.addChild(score) |
18 |
|
19 |
stage.addEventListener(MouseEvent.MOUSE_MOVE, updatePlayerBall) |
20 |
stage.addChild(ballPlayer) |
21 |
|
22 |
tmr.addEventListener(TimerEvent.TIMER, updateTime) |
23 |
|
24 |
stage.addEventListener(MouseEvent.CLICK, startGame) |
25 |
}
|
En las primeras cuatro líneas, inicializamos nuestras variables de campo.
A continuación, nos aseguramos de que nuestra etapa esté alineada con la esquina superior izquierda y no se escale.
Ocultamos el cursor del mouse
Nuestro cursor será reemplazado por el Sprite playerball
. Luego llamamos a
la función setBackground
(explicada en el siguiente paso).
Centramos
nuestro score
en la pantalla y lo agregamos a la lista de
visualización. Para actualizar la posición de ballPlayer
, adjuntamos un
evento MouseEvent.MOUSE_MOVE al escenario.
La función updatePlayerBall
(explicada en el paso 11) manejará este evento MouseEvent. A
continuación, agregamos ballPlayer
a la lista de visualización.
El
temporizador se usará para mostrar el tiempo del juego. Adjuntamos
un oyente TimerEvent.TIMER a nuestro temporizador, que activará la
función updateTime()
(explicada en el Paso 12) cada 10
milisegundos.
Finalmente, agregamos un MouseEvent.CLICK a nuestro
escenario. La función startGame
(explicada en el paso 13) comenzará
nuestro juego.
Paso 10: función setBackground()
Esta función agrega un fondo degradado radial a la lista de visualización. Para dibujar un degradado en un Sprite debe definir el tipo de degradado, los colores que desea usar, los valores alfa de los colores, las relaciones (esto define la distribución de los colores) y el método de dispersión.
Para obtener más información, consulte esta sugerencia rápida sobre degradados.
1 |
|
2 |
private function setBackground():void |
3 |
{
|
4 |
var type:String = "radial" |
5 |
var colors:Array = [0xffffff,0xcccccc] |
6 |
var alphas:Array = [ 1, 1 ] |
7 |
var ratios:Array = [ 0, 255 ] |
8 |
var matr:Matrix = new Matrix() |
9 |
matr.createGradientBox(stage.stageWidth, stage.stageHeight, Math.PI / 2, 0, 0 ) |
10 |
//SpreadMethod will define how the gradient is spread. Note!!! Flash uses CONSTANTS to represent String literals
|
11 |
var sprMethod:String = "pad" |
12 |
//Start the Gradietn and pass our variables to it
|
13 |
var sprite:Sprite = new Sprite() |
14 |
//Save typing + increase performance through local reference to a Graphics object
|
15 |
var g:Graphics = sprite.graphics |
16 |
g.beginGradientFill( type, colors, alphas, ratios, matr, sprMethod ) |
17 |
g.drawRect(0,0,stage.stageWidth,stage.stageHeight) |
18 |
|
19 |
stage.addChild(sprite) |
20 |
}
|
Paso 11: Función updatePlayerBall()
Esta función actualiza la posición de ballPlayer
según la posición del
mouse.
1 |
|
2 |
private function updatePlayerBall(e:MouseEvent):void |
3 |
{
|
4 |
ballPlayer.x = mouseX |
5 |
ballPlayer.y = mouseY |
6 |
}
|
Paso 12: función updateTime()
Calculamos el tiempo en segundos y lo colocamos dentro del
cuadro de texto de nuestro Sprite score
. Cada 5000 ms (cinco segundos)
agregamos una nueva pelota al juego.
1 |
|
2 |
private function updateTime(e:TimerEvent):void |
3 |
{
|
4 |
score.txtScore.text = String(((tmr.currentCount*tmr.delay)/1000).toFixed(2)); |
5 |
if((tmr.currentCount*tmr.delay) % 5000 == 0) |
6 |
{
|
7 |
addBall(); |
8 |
}
|
9 |
}
|
Paso 13: función startGame()
El juego se inicia haciendo clic en el escenario. Primero eliminamos el
oyente para el clic de escenario, de modo que no podemos comenzar el
juego varias veces. Agregamos tres bolas al juego llamando a la función
addBall()
(explicada en el siguiente paso) tres veces. Comenzamos
nuestro temporizador que actualizará nuestro tiempo de juego.
Finalmente,
agregamos un evento ENTER_FRAME a nuestra etapa. La función gameLoop()
(explicada en el Paso 15) actualizará la posición de nuestras bolas
enemigas.
1 |
|
2 |
private function startGame(e:MouseEvent):void |
3 |
{
|
4 |
stage.removeEventListener(MouseEvent.CLICK, startGame) |
5 |
addBall() |
6 |
addBall() |
7 |
addBall() |
8 |
tmr.start() |
9 |
stage.addEventListener(Event.ENTER_FRAME, gameLoop) |
10 |
}
|
Paso 14: Función addBall()
Primero hacemos una nueva instancia de nuestra clase Ball
. Posicionamos
la ball
aleatoriamente en el escenario con un alfa de 0 y lo agregamos a
la lista de visualización.
A
continuación, volvemos a conectar el alfa a 1. (Utilizo TweenMax, está
incluido en los archivos de origen. También puede usar el motor de
interpolación Flash incorporado). La segunda interpolación no es
realmente una interpolación. Simplemente espera un segundo y la función
onComplete
empuja la ball
hacia nuestra matriz de eballs
. De esta forma,
la función gameLoop()
(explicada en el siguiente paso) puede manejar
el resto.
1 |
|
2 |
private function addBall():void |
3 |
{
|
4 |
var ball:Ball = new Ball(10, Math.random()*Math.PI*2, 5) |
5 |
ball.x = Math.random()*stage.stageWidth |
6 |
ball.y = Math.random()*stage.stageHeight |
7 |
ball.alpha = 0 |
8 |
stage.addChild(ball) |
9 |
TweenMax.to(ball, 0.5, {alpha:1}) |
10 |
TweenMax.to(ball, 0, {delay: 1, onComplete:function():void{eballs.push(ball)}}) |
11 |
}
|
Paso 15: Función gameLoop()
Cada cuadro pasará por esta función.
1 |
|
2 |
private function gameLoop(e:Event):void |
3 |
{
|
4 |
for (var i:uint = 0; i < eballs.length; i++) |
5 |
{
|
6 |
for (var j:uint = i + 1; j < eballs.length; j++) |
7 |
{
|
8 |
if (collision(eballs[i], eballs[j])) |
9 |
{
|
10 |
doCollision(eballs[i], eballs[j]) |
11 |
}
|
12 |
}
|
13 |
|
14 |
if(collision(eballs[i], ballPlayer)) |
15 |
{
|
16 |
endOfGame() |
17 |
break
|
18 |
}
|
19 |
|
20 |
eballs[i].update() |
21 |
checkBounds(eballs[i]) |
22 |
}
|
23 |
}
|
Comenzamos iterando a través de todas nuestras bolas enemigas.
El segundo for-loop comprueba las colisiones entre las bolas enemigas. El ciclo comienza en 'i + 1'. De esta forma no verificamos las colisiones.
A continuación, verificamos si ballPlayer
golpea la
bola enemiga. Si es así, el juego está terminado. Luego actualizamos la
posición de nuestra bola enemiga.
Nos
aseguramos de que las bolas permanezcan en la pantalla del juego
llamando a la función checkBounds()
(explicada más adelante).
Paso 16: función collision()
Esta función verifica si un par determinado de bolas está colisionando.
Primero calculamos la distancia xy la distancia y entre las dos bolas. Usando el Teorema de Pitágoras (ver el siguiente diagrama) calculamos la distancia absoluta entre ellos. Si la distancia es menor o igual a la suma de los radios de las bolas, tenemos una colisión.

1 |
|
2 |
private function collision(ball1:Ball, ball2:Ball):Boolean |
3 |
{
|
4 |
var xDist:Number = ball1.x - ball2.x |
5 |
var yDist:Number = ball1.y - ball2.y |
6 |
var Dist:Number = Math.sqrt(xDist * xDist + yDist * yDist) |
7 |
|
8 |
return Dist <= ball1.radius + ball2.radius |
9 |
}
|
Paso 17: Función doCollision()
Esta función calculará las nuevas velocidades x e y de las bolas según la velocidad y el ángulo de la colisión. Advertencia: matemática;)
Primero calculamos la distancia horizontal entre las dos bolas y luego la distancia vertical entre las bolas. Con estas distancias (y un poco más de trigonometría) podemos calcular el ángulo entre las bolas (ver diagrama).

Luego calculamos lo que llamo la magnitud de cada bola. (Tenemos un vector xspeed y un vector yspeed, la magnitud es la suma de esos). Luego calculamos el ángulo de cada bola (similar al cálculo del ángulo anterior).
Luego rotamos las nuevas velocidades x e y de cada bola. Lo que realmente estamos haciendo es rotar el sistema de coordenadas. Al rotar nuestros ejes tenemos una colisión 1D. (Ver el siguiente diagrama).

Newton dice que la cantidad total de energía cinética en un sistema cerrado es constante. Ahora usamos estas fórmulas:
v1 = (u1*(m1-m2) + 2*m2*u2)/(m1+m2)
v2 = (u2*(m2-m1) + 2*m1*u1)/(m1+m2)
dónde:
v1 = final xSpeedBall 1
v2 = final xSpeedBall 2
m1 = masa de bola 1
m2 = masa de bola 2
u1 = velocidad inicial de bola 1
u2 = velocidad inicial de bola 2
Las velocidades y no cambian ya que es una colisión 1D.
Con estas fórmulas podemos calcular el
xSpeed
y el ySpeed
de cada bola.
Ahora tenemos las nuevas
velocidades x e y en nuestro sistema de coordenadas giradas. El último
paso es convertir todo a un sistema de coordenadas normal. Usamos
Math.PI/2
porque el ángulo entre xSpeed
y ySpeed
debe ser siempre
de 90 grados (pi/2 radianes).
1 |
|
2 |
private function doCollision(ball1:Ball, ball2:Ball):void |
3 |
{
|
4 |
var xDist:Number = ball1.x - ball2.x |
5 |
var yDist:Number = ball1.y - ball2.y |
6 |
var collisionAngle:Number = Math.atan2(yDist, xDist) |
7 |
|
8 |
var magBall1:Number = Math.sqrt(ball1.xSpeed*ball1.xSpeed+ball1.ySpeed*ball1.ySpeed) |
9 |
var magBall2:Number = Math.sqrt(ball2.xSpeed*ball2.xSpeed+ball2.ySpeed*ball2.ySpeed) |
10 |
|
11 |
var angleBall1:Number = Math.atan2(ball1.ySpeed, ball1.xSpeed) |
12 |
var angleBall2:Number = Math.atan2(ball2.ySpeed, ball2.xSpeed) |
13 |
|
14 |
var xSpeedBall1:Number = magBall1 * Math.cos(angleBall1-collisionAngle) |
15 |
var ySpeedBall1:Number = magBall1 * Math.sin(angleBall1-collisionAngle) |
16 |
var xSpeedBall2:Number = magBall2 * Math.cos(angleBall2-collisionAngle) |
17 |
var ySpeedBall2:Number = magBall2 * Math.sin(angleBall2-collisionAngle) |
18 |
|
19 |
var finalxSpeedBall1:Number = ((ball1.mass-ball2.mass)*xSpeedBall1+(ball2.mass+ball2.mass)*xSpeedBall2)/(ball1.mass+ball2.mass) |
20 |
var finalxSpeedBall2:Number = ((ball1.mass+ball1.mass)*xSpeedBall1+(ball2.mass-ball1.mass)*xSpeedBall2)/(ball1.mass+ball2.mass) |
21 |
var finalySpeedBall1:Number = ySpeedBall1 |
22 |
var finalySpeedBall2:Number = ySpeedBall2 |
23 |
|
24 |
ball1.xSpeed = Math.cos(collisionAngle)*finalxSpeedBall1+Math.cos(collisionAngle+Math.PI/2)*finalySpeedBall1 |
25 |
ball1.ySpeed = Math.sin(collisionAngle)*finalxSpeedBall1+Math.sin(collisionAngle+Math.PI/2)*finalySpeedBall1 |
26 |
ball2.xSpeed = Math.cos(collisionAngle)*finalxSpeedBall2+Math.cos(collisionAngle+Math.PI/2)*finalySpeedBall2 |
27 |
ball2.ySpeed = Math.sin(collisionAngle)*finalxSpeedBall2+Math.sin(collisionAngle+Math.PI/2)*finalySpeedBall2 |
28 |
}
|
Para encontrar más información sobre colisiones elásticas, visite hoomanr.com.
Paso 18: Función endOfGame()
Esto se ejecuta cuando el juego termina.
1 |
|
2 |
private function endOfGame():void |
3 |
{
|
4 |
tmr.stop() |
5 |
Mouse.show() |
6 |
stage.removeEventListener(MouseEvent.MOUSE_MOVE, updatePlayerBall) |
7 |
stage.removeEventListener(Event.ENTER_FRAME, gameLoop) |
8 |
|
9 |
while(eballs.length > 0) |
10 |
{
|
11 |
TweenMax.to(eballs[0], 0.5, {scaleX:0, scaleY:0, ease:Bounce.easeOut}) |
12 |
eballs.splice(0,1) |
13 |
}
|
14 |
|
15 |
TweenMax.to(ballPlayer, 0.5, {scaleX:0, scaleY:0, ease:Bounce.easeOut}) |
16 |
}
|
Primero, detengamos el temporizador. Mostramos el mouse nuevamente A continuación, eliminamos los detectores de eventos MOUSE_MOVE y ENTER_FRAME. Finalmente hacemos que todas las bolas en el escenario sean invisibles.
Paso 19: función checkBounds()
Esta función asegura que las bolas
permanezcan dentro de la pantalla del juego. Entonces, si la pelota
golpea el lado superior o inferior, invertimos la velocidad y ySpeed
. si la
pelota golpea el lado izquierdo o el derecho de la pantalla invertimos
la xSpeed
. Utiliza
una lógica similar a la función de detección de colisión de bolas para
comprobar si los bordes de la pelota golpean un borde de la pantalla.
1 |
|
2 |
private function checkBounds(ball:Ball):void |
3 |
{
|
4 |
if((ball.x + ball.radius) > stage.stageWidth) |
5 |
{
|
6 |
ball.x = stage.stageWidth - ball.radius |
7 |
ball.xSpeed *= -1 |
8 |
}
|
9 |
if((ball.x - ball.radius) < 0) |
10 |
{
|
11 |
ball.x = 0 + ball.radius |
12 |
ball.xSpeed *= -1 |
13 |
}
|
14 |
if((ball.y + ball.radius) > stage.stageHeight) |
15 |
{
|
16 |
ball.y = stage.stageHeight - ball.radius |
17 |
ball.ySpeed *= - 1 |
18 |
}
|
19 |
if((ball.y - ball.radius) < 0) |
20 |
{
|
21 |
ball.y = 0 + ball.radius |
22 |
ball.ySpeed *= - 1 |
23 |
}
|
24 |
}
|
Paso 20: la completa clase de la aplicación
Hemos completado nuestra clase de Aplicación. ¡Ahora tenemos un juego funcionando!
1 |
|
2 |
package
|
3 |
{
|
4 |
import flash.display.Sprite; |
5 |
import flash.display.Graphics; |
6 |
import flash.events.Event; |
7 |
import flash.events.TimerEvent; |
8 |
import flash.events.MouseEvent; |
9 |
import flash.geom.Matrix; |
10 |
import flash.utils.Timer; |
11 |
import flash.ui.Mouse; |
12 |
import com.greensock.TweenMax; |
13 |
import com.greensock.easing.*; |
14 |
public class Application extends Sprite |
15 |
{
|
16 |
|
17 |
private var ballPlayer:Ball; |
18 |
private var eballs:Array; |
19 |
private var tmr:Timer; |
20 |
private var score:Score; |
21 |
public function Application():void |
22 |
{
|
23 |
init(); |
24 |
}
|
25 |
|
26 |
private function init():void |
27 |
{
|
28 |
ballPlayer = new PlayerBall(); |
29 |
eballs = new Array(); |
30 |
tmr = new Timer(10); |
31 |
score = new Score(); |
32 |
|
33 |
stage.align = "TL"; |
34 |
stage.scaleMode = "noScale"; |
35 |
Mouse.hide(); |
36 |
|
37 |
setBackground(); |
38 |
|
39 |
score.x = stage.stageWidth / 2; |
40 |
score.y = stage.stageHeight / 2; |
41 |
stage.addChild(score); |
42 |
|
43 |
stage.addEventListener(MouseEvent.MOUSE_MOVE, updatePlayerBall); |
44 |
stage.addChild(ballPlayer); |
45 |
|
46 |
tmr.addEventListener(TimerEvent.TIMER, updateTime); |
47 |
|
48 |
stage.addEventListener(MouseEvent.CLICK, startGame); |
49 |
}
|
50 |
|
51 |
private function setBackground():void |
52 |
{
|
53 |
var type:String = "radial"; |
54 |
var colors:Array = [0xffffff,0xcccccc]; |
55 |
var alphas:Array = [1,1]; |
56 |
var ratios:Array = [0,255]; |
57 |
var matr:Matrix = new Matrix(); |
58 |
matr.createGradientBox(stage.stageWidth, stage.stageHeight, Math.PI / 2, 0, 0 ); |
59 |
//SpreadMethod will define how the gradient is spread. Note!!! Flash uses CONSTANTS to represent String literals
|
60 |
var sprMethod:String = "pad"; |
61 |
//Start the Gradietn and pass our variables to it
|
62 |
var sprite:Sprite = new Sprite(); |
63 |
//Save typing + increase performance through local reference to a Graphics object
|
64 |
var g:Graphics = sprite.graphics; |
65 |
g.beginGradientFill( type, colors, alphas, ratios, matr, sprMethod ); |
66 |
g.drawRect(0,0,stage.stageWidth,stage.stageHeight); |
67 |
stage.addChild(sprite); |
68 |
}
|
69 |
|
70 |
private function updatePlayerBall(e:MouseEvent):void |
71 |
{
|
72 |
ballPlayer.x = mouseX; |
73 |
ballPlayer.y = mouseY; |
74 |
}
|
75 |
|
76 |
private function updateTime(e:TimerEvent):void |
77 |
{
|
78 |
score.txtScore.text = String(((tmr.currentCount*tmr.delay)/1000).toFixed(2)); |
79 |
if ((tmr.currentCount*tmr.delay) % 5000 == 0) |
80 |
{
|
81 |
addBall(); |
82 |
}
|
83 |
}
|
84 |
|
85 |
private function startGame(e:MouseEvent):void |
86 |
{
|
87 |
stage.removeEventListener(MouseEvent.CLICK, startGame); |
88 |
addBall(); |
89 |
addBall(); |
90 |
addBall(); |
91 |
tmr.start(); |
92 |
stage.addEventListener(Event.ENTER_FRAME, gameLoop); |
93 |
}
|
94 |
|
95 |
private function addBall():void |
96 |
{
|
97 |
var ball:Ball = new Ball(10,Math.random() * Math.PI * 2,5); |
98 |
ball.x = Math.random() * stage.stageWidth; |
99 |
ball.y = Math.random() * stage.stageHeight; |
100 |
ball.alpha = 0; |
101 |
stage.addChild(ball); |
102 |
TweenMax.to(ball, 0.5, {alpha:1}); |
103 |
TweenMax.to(ball, 0, {delay: 1, onComplete:function():void{eballs.push(ball)}}); |
104 |
}
|
105 |
|
106 |
private function gameLoop(e:Event):void |
107 |
{
|
108 |
for (var i:uint = 0; i < eballs.length; i++) |
109 |
{
|
110 |
for (var j:uint = i + 1; j < eballs.length; j++) |
111 |
{
|
112 |
if (collision(eballs[i],eballs[j])) |
113 |
{
|
114 |
doCollision(eballs[i], eballs[j]); |
115 |
}
|
116 |
}
|
117 |
|
118 |
if (collision(eballs[i],ballPlayer)) |
119 |
{
|
120 |
endOfGame(); |
121 |
break; |
122 |
}
|
123 |
|
124 |
eballs[i].update(); |
125 |
checkBounds(eballs[i]); |
126 |
}
|
127 |
}
|
128 |
|
129 |
private function collision(ball1:Ball, ball2:Ball):Boolean |
130 |
{
|
131 |
var xDist:Number = ball1.x - ball2.x; |
132 |
var yDist:Number = ball1.y - ball2.y; |
133 |
var Dist:Number = Math.sqrt(xDist * xDist + yDist * yDist); |
134 |
|
135 |
if (Dist <= ball1.radius + ball2.radius) |
136 |
{
|
137 |
if (ball1.x < ball2.x) |
138 |
{
|
139 |
ball1.x -= 2; |
140 |
ball2.x += 2; |
141 |
}
|
142 |
else
|
143 |
{
|
144 |
ball1.x += 2; |
145 |
ball2.x -= 2; |
146 |
}
|
147 |
|
148 |
if (ball1.y < ball2.y) |
149 |
{
|
150 |
ball1.y -= 2; |
151 |
ball2.y += 2; |
152 |
}
|
153 |
else
|
154 |
{
|
155 |
ball1.y += 2; |
156 |
ball2.y -= 2; |
157 |
}
|
158 |
}
|
159 |
|
160 |
|
161 |
return Dist <= ball1.radius + ball2.radius; |
162 |
}
|
163 |
|
164 |
private function doCollision(ball1:Ball, ball2:Ball):void |
165 |
{
|
166 |
var xDist:Number = ball1.x - ball2.x; |
167 |
var yDist:Number = ball1.y - ball2.y; |
168 |
var collisionAngle:Number = Math.atan2(yDist,xDist); |
169 |
var magBall1:Number = Math.sqrt(ball1.xSpeed * ball1.xSpeed + ball1.ySpeed * ball1.ySpeed); |
170 |
var magBall2:Number = Math.sqrt(ball2.xSpeed * ball2.xSpeed + ball2.ySpeed * ball2.ySpeed); |
171 |
var angleBall1:Number = Math.atan2(ball1.ySpeed,ball1.xSpeed); |
172 |
var angleBall2:Number = Math.atan2(ball2.ySpeed,ball2.xSpeed); |
173 |
var xSpeedBall1:Number = magBall1 * Math.cos(angleBall1 - collisionAngle); |
174 |
var ySpeedBall1:Number = magBall1 * Math.sin(angleBall1 - collisionAngle); |
175 |
var xSpeedBall2:Number = magBall2 * Math.cos(angleBall2 - collisionAngle); |
176 |
var ySpeedBall2:Number = magBall2 * Math.sin(angleBall2 - collisionAngle); |
177 |
var finalxSpeedBall1:Number = ((ball1.mass-ball2.mass)*xSpeedBall1+(ball2.mass+ball2.mass)*xSpeedBall2)/(ball1.mass+ball2.mass); |
178 |
var finalxSpeedBall2:Number = ((ball1.mass+ball1.mass)*xSpeedBall1+(ball2.mass-ball1.mass)*xSpeedBall2)/(ball1.mass+ball2.mass); |
179 |
var finalySpeedBall1:Number = ySpeedBall1; |
180 |
var finalySpeedBall2:Number = ySpeedBall2; |
181 |
ball1.xSpeed = Math.cos(collisionAngle) * finalxSpeedBall1 + Math.cos(collisionAngle + Math.PI / 2) * finalySpeedBall1; |
182 |
ball1.ySpeed = Math.sin(collisionAngle) * finalxSpeedBall1 + Math.sin(collisionAngle + Math.PI / 2) * finalySpeedBall1; |
183 |
ball2.xSpeed = Math.cos(collisionAngle) * finalxSpeedBall2 + Math.cos(collisionAngle + Math.PI / 2) * finalySpeedBall2; |
184 |
ball2.ySpeed = Math.sin(collisionAngle) * finalxSpeedBall2 + Math.sin(collisionAngle + Math.PI / 2) * finalySpeedBall2; |
185 |
}
|
186 |
|
187 |
private function endOfGame():void |
188 |
{
|
189 |
tmr.stop(); |
190 |
Mouse.show(); |
191 |
stage.removeEventListener(MouseEvent.MOUSE_MOVE, updatePlayerBall); |
192 |
stage.removeEventListener(Event.ENTER_FRAME, gameLoop); |
193 |
|
194 |
while (eballs.length > 0) |
195 |
{
|
196 |
TweenMax.to(eballs[0], 0.5, {scaleX:0, scaleY:0, ease:Bounce.easeOut}); |
197 |
eballs.splice(0,1); |
198 |
}
|
199 |
|
200 |
TweenMax.to(ballPlayer, 0.5, {scaleX:0, scaleY:0, ease:Bounce.easeOut}); |
201 |
}
|
202 |
|
203 |
private function checkBounds(ball:Ball):void |
204 |
{
|
205 |
if ((ball.x + ball.radius) > stage.stageWidth) |
206 |
{
|
207 |
ball.x = stage.stageWidth - ball.radius; |
208 |
ball.xSpeed *= -1; |
209 |
}
|
210 |
if ((ball.x - ball.radius) < 0) |
211 |
{
|
212 |
ball.x = 0 + ball.radius; |
213 |
ball.xSpeed *= -1; |
214 |
}
|
215 |
if ((ball.y + ball.radius) > stage.stageHeight) |
216 |
{
|
217 |
ball.y = stage.stageHeight - ball.radius; |
218 |
ball.ySpeed *= -1; |
219 |
}
|
220 |
if ((ball.y - ball.radius) < 0) |
221 |
{
|
222 |
ball.y = 0 + ball.radius; |
223 |
ball.ySpeed *= -1; |
224 |
}
|
225 |
}
|
226 |
}
|
227 |
}
|
Conclusión
Eso es todo por este tutorial. Por supuesto, podrías agregar la posibilidad de reiniciar el juego, pero eso no debería ser demasiado difícil. Este ejemplo básico de colisiones elásticas se puede utilizar para juegos más grandes como un juego de billar o similar.
Espero que les haya gustado este tutorial, ¡gracias por leer!