Comunidad de diseño web y desarrollo en internet online

tiro con parabola AS3

Citar            
MensajeEscrito el 06 May 2013 10:19 am
Muchas veces busque como hacer un sencillo juego donde disparaba un proyectil hacia algo, y son temas donde los ejemplos escasean, bueno le voy a compartir como hacer un tiro con parábola, donde ademas actúan elementos como la gravedad y la fricción, la fuerza y la trayectoria, variantes que controlaremos con un sencillo control en forma de flecha con el cual le podremos dar dirección y potencia al disparo; ademas de poder agregar un score o marcador, un objeto que se mueva de forma independiente y al azar que sea blanco de los disparos. Bueno empezemos.





1.- CARACTERISTICAS DEL ESCENARIO

- Tamaño: 550px de ancho por 400 px de alto (pueden usar cualquier tamaño)
- Velocidad: 30 fps (pueden usar cualquier velocidad, pero si influirá en el resultado final)
- 3 capas: en la primera el AS3, la segunda para un texto dinámico con nombre de instancia
puntos, la ultima para el fondo, en mi caso solo use un borde color negro de 3 pts. de ancho


- 3 movieclips: despues de crearlos, los exportamos y vinculamos para poder usarlos desde la biblioteca, y los borramos del escenario.

el primero seria un objeto (yo use un circulo, pero pueden usar cualquier otra forma) de 50 px con una flechita en el borde superior, alineado al centro del escenario, exportamos como MiObjeto


el segundo seria un movieclip (yo use un circulo) de 20 px alineado al centro del escenario (pueden usar cualquier otra forma o imagen png, pero su tamaño afecta su gravedad ya que usaremos su tamaño como referencia de su peso) , exportamos como MiClip


el ultimo una flecha de 52 de ancho por 30 de largo aprox. alineada al centro a la izquierda como en la imagen, exportamos como FlechaPower


2.- OK, una vez realizado estos pasos, vamos a con el AS3, ya tengo preparado un código explicado y se los voy a pegar tal cual, posteriormente les comparto el archivo fla para que lo puedan evaluar y mejorar.
Si alguien tiene alguna duda pregunten, igual no lo se todo y pienso que con la retroalimentacion nos podemos ayudar entre todos y a mejorar lo que hacemos, un saludo afectuoso!!

Código ActionScript :


//importamos un tween y easing que utilizaremos mas adelante
import fl.transitions.Tween;
import fl.transitions.TweenEvent;
import fl.transitions.easing.*;

/*   Creamos 3 movieclips para usar desde la biblioteca asignandole una clase y nombre de instancia:   */
//circulo de 50px convertido a movie clip, punto registro al centro, con una flechita en borde superior
var miobjeto:MiObjeto = new MiObjeto();
//circulo de 20px convertido a movie clip, punto registro al centro
var miclip:MiClip = new MiClip();
//flecha de 53px ancho por 30px alto convertido a movie clip, punto registro al centro a la izquierda
var powerDir:FlechaPower = new FlechaPower();

//variable tipo array con la que se creara un nuevo objeto
var objetos:Array = new Array(); 
//y otra variable "i" con la que le daremos un valor inicial de 0 al array 
var i:int=0;

//variables con las que almacenaremos el ancho y alto de nuestro escenario
var stageW:Number = stage.stageWidth;
var stageH:Number = stage.stageHeight;

//variables solo declaradas (mas adelante le daremos un valor)
var peso:Number;//peso del clip a lanzar
var fuerza:Number;//fuerza con que se lanza al clip
var angulo:Number;//angulo de disparo
var angulo_rad:Number;//convertir angulos a radianes
var vx:Number;//velocidad x del clip
var vy:Number;//velocidad y del clip
var rumboX:Number;//rumbo x de la flechaPower
var rumboY:Number;//rumbo y de la flechaPower
var randomObjetoX:Number;//posicion x objeto
var randomObjetoY:Number;//posicion y objeto

//variable que vale la mitad de ancho y alto en pixeles del clip
var radioW:Number = miclip.width/2;
var radioH:Number = miclip.height/2;
//variable para el valor de la friccion constante que sufre la aceleracion
var friccion:Number = .99;
//valor de la gravedad terrestre (redondeado)
var gravedad:Number=10;

var randomNum:Number;//numero random para rotacion de objeto 
var randomVel:Number;//velocidad de desplazamiento random del objeto
var randomTime:Number;//tiempo random en milseg que dura el objeto en escena
var anguloObj:Number;//angulo de desplazamiento del objeto
var anguloObj_rad:Number;//conversion a radianes del angulo de desplazamiento del objeto
var vxObj:Number;//velocidad exponencial x del objeto
var vyObj:Number;//velocidad exponencial y del objeto
var tiempo:Timer;//clase timer para la duracion de desplazamiento del objeto
var objetoTw:Tween;//clase tween para ease in u out del objeto

var miClip:MovieClip = MovieClip(miclip);//clase movieclip para colision compleja de clip
var miObjeto:MovieClip = MovieClip(miobjeto);//clase movieclip para colision compleja de objeto

var miclipRect:Rectangle;//clase rectangle para colision de clip
var miclipOffset:Matrix;//clase matrix para colision de clip
var miclipBmpData:BitmapData;//clase bitmapdata para colision de clip
var objetoRect:Rectangle;//clase rectangle para colision de objeto
var objetoOffset:Matrix;//clase matrix para colision de objeto
var miobjetoBmpData:BitmapData;//clase bitmapdata para colision de objeto
var objetoLoc:Point;//punto de colision de objeto
var miclipLoc:Point;//punto de colision de clip

//marcador inicial en 0 dentro del texto dinamico puntos
var score:int = 0;//variable score que inicialmente empieza con 0 de valor y sirve para los puntos
puntos.text = ""+score;//sumamos la variable score al campo de texto dinamico puntos

addChild(powerDir);//agregamos al escenario flechaPower
addChild(miclip);//agregamos al escenario clip
miclip.x = stageW / 2;//acomodamos miclip en medio del escenario
miclip.y = stageH - miclip.height / 2;//acomodamos miclip en el borde de la parte baja
powerDir.scaleX = powerDir.scaleY = .7;//bajamos la escala de la flechaPower

//funcion que mueve angulo de la flecha segun la posicion del raton
function flechaPos(e:Event){
   powerDir.x = miclip.x;//centramos la flecha en el centro del clip
   powerDir.y = miclip.y;//centramos la flecha en el centro del clip
   powerDir.visible = true;//la flecha es visible
   rumboX = mouseX - powerDir.x;//valor variable rumboX segun mouseX respecto a posicion flecha x
   rumboY = mouseY - powerDir.y;//valor variable rumboY segun mouseY respecto a posicion flecha y
   //traducimos el rumbo xy de radianes a grados para rotar flecha, redondeamos la ecuación
   powerDir.rotation =  int(Math.atan2(rumboY,rumboX) * 180 / Math.PI);
   angulo = -powerDir.rotation;//usamos rotacion de flecha para dar valor a angulo de trayecto de clip
   angulo_rad = angulo*Math.PI/180;//convertimos angulos a radianes para dar rumbo xy a trayecto de clip
}

//funcion se dispara al presionar boton de raton izq, la flecha da angulo y rumbo segun su ancho
function chekPower(e:Event){
   peso = ((miclip.width + miclip.height) / 2) / 100;//usamos las dimensiones de miClip para dar el peso
   //incrementamos exponencialmente la escala de la flechaPower mientras el boton izq del raton este DOWN
   powerDir.scaleX += .05;
   //usamos la escala de la flechaPower para darle un valor a la fuerza (igual pueden usar otro valor)
   fuerza = (powerDir.scaleX * powerDir.scaleX) * 15;
   //usamos formula newton para calcular velocidad y rumbo xy a la q viajara clip segun peso y fuerza
   vx = (fuerza/peso)*Math.cos(angulo_rad);// velocidad = fuerza/peso
   vy = (-fuerza/peso)*Math.sin(angulo_rad);//invertimos Y ya que plano cartesiano no corresponde al escenario 
   //si la escala de la flecha excede cierto tamaño, la regresamos al tamaño que estamos usando
   if(powerDir.scaleX >= 2){
      powerDir.scaleX = .7;
   }
}

//funcion que se ejecuta continuamente cuando soltamos el boton izquierdo del mouse
//con esta funcion haremos el efecto de parabola en miclip
function moverMc(e:Event){
   //vamos a redondear todas las operaciones a 2 decimales: Math.round(()*100)/100
   //lo haremos asi por que usaremos estas cifras para detener algunos eventos incluyendo este
   powerDir.visible = false;//la flecha es NO visible
   //esta operacion nos da la gravedad, obviamente la gravedad solo afecta la posicion Y de miClip
   vy = Math.round((vy + gravedad)*100)/100;
   //como en la funcion flechaPos ya dijimos q vx y vy equivalen al seno y coseno del angulo de rotacion de
   //flechaPower, entonces solo redondeamos el valor ya establecido
   vy = Math.round((vy)*100)/100;
   vx = Math.round((vx)*100)/100;
   //ahora usaremos la friccion para darle un efecto mas realista tanto a la parabola como al rebote de miClip
   //tambien podemos multiplicar el valor por algun decimal no mayor a 1 para afectar el rebote/friccion de miClip
   vx = Math.round((vx*friccion*.8)*100)/100;
   vy = Math.round((vy*friccion*.8)*100)/100;
   //la posicion x Y de miClip la establecemos asi, para evitar que el movimiento se vea interrumpido por
   //la repeticion del evento, aparte si no le damos un valor de posicion x y a miClip este no se movera
   miclip.x = miclip.x + vx;
   miclip.y = miclip.y + vy;
   //detenemos este evento, el valor de 4.4 puede variar si cambiamos cualquier valor de gravedad o friccion 
   if(vy == 4.4){
      miclip.removeEventListener(Event.ENTER_FRAME,moverMc);//miClip deja de moverse
      stage.addEventListener(MouseEvent.MOUSE_UP,upBtn);//add evento Mouse UP
      stage.addEventListener(MouseEvent.MOUSE_DOWN,downBtn);//add evento Mouse DOWN
      addEventListener(Event.ENTER_FRAME,flechaPos);//add evento flechaPos
      powerDir.scaleX = .7;//regresamos la flecha a la escala inicial
   }
   
   //con este trace podemos saber los valores de Y que se repiten cuando deja de botar el miClip
   //asi usamos este valor en un if == para poder detener el evento
   trace("velocidadY:"+vy);
   
   //con esta serie de if elseif registramos los bordes del escenario donde rebotara miClip
   if(miclip.y+radioH > stageH){
      miclip.y=stageH-radioH;
      vy=vy*-1;
   }
   else if(miclip.y-radioH<0){
      miclip.y=radioH;
      vy=vy*-1;
   }
   
   if(miclip.x+radioW>stageW){
      miclip.x=stageW-radioW;
      vx=vx*-1;
   }
   else if(miclip.x-radioW<0){
      miclip.x=radioW;
      vx=vx*-1;
   }
   cheka();//funcion que evalua constantemente si miClip y el objeto chocan mientras miClip se mueve
}

//funciones que usaremos para dar un valor mínimo y máximo para conseguir varios valores aleatorios
//posición X aleatoria del objeto
function minmaxObjetoX( min:Number, max:Number ):Number {
   return int(min + (max - min) * Math.random()); 
}
//posición Y aleatoria del objeto
function minmaxObjetoY( min:Number, max:Number ):Number {
   return int(min + (max - min) * Math.random()); 
}
//rotación aleatoria del objeto
function minmaxNum( min:Number, max:Number ):Number {
   return int(min + (max - min) * Math.random()); 
}
//velocidad aleatoria del objeto
function minmaxVel( min:Number, max:Number ):Number {
   return int(min + (max - min) * Math.random()); 
}
//tiempo aleatorio que dura el objeto en escena entre uno y otro objeto
function minmaxTime( min:Number, max:Number ):Number {
   return int(min + (max - min) * Math.random()); 
}

//con esta función haremos que el objeto aparezca en un lugar XY aleatorio con una velocidad aleatoria //y con una trayectoria, por supuesto también aleatoria cada determinado //tiempo también aleatorio, //igual podrían determinar los valores como fijos, yo preferí hacerlo así
clonar();
function clonar():void{
   
   randomNum = minmaxNum(-179,179);//el valor de rotación rad de un objeto es de -179 a 179 no de 0 a 360
   randomVel = minmaxNum(10,20);//el valor de velocidad tiene que ver con los pixeles, de 10 a 20 px en mi caso
   randomTime = minmaxNum(2000,5000);// la velocidad es en milisegundos, 1000 mlseg equivalen a 1 segundo 
   
   anguloObj = -randomNum;//invertimos valor de angulo de rotación para que corresponda a la del objeto
   anguloObj_rad = anguloObj*Math.PI/180;//convertimos ángulos a radianes para trayecto de objeto
   //usamos los radianes para darle rumbo y velocidad x y al objeto
   vxObj = randomVel * Math.cos(anguloObj_rad);
   vyObj = randomVel * Math.sin(anguloObj_rad);
   
   
   addChild(miobjeto);//agregamos el objeto al escenario
   objetos.push(miobjeto);//disparamos el array para que haga un objeto nuevo
   
   //damos valor a las variables de posicion x y del objeto, de tal manera que el objeto no quede fuera
   //de los bordes del escenario
   randomObjetoX = minmaxObjetoX(miobjeto.width,stageW - miobjeto.width);
   randomObjetoY = minmaxObjetoY(miobjeto.height,stageH - miobjeto.height);
   
   //usamos la variable de tipo timer para crear un tiempo con el que cambiaremos de manera aleatoria
   //la posicion y trayectoria x y del objeto cada cierto tiempo, tambien aleatorio
   tiempo = new Timer(randomTime, 1);//el timer tiene tiempo aleatorio, solo se usa una vez
   tiempo.start();//empezamos el timer
   tiempo.addEventListener(TimerEvent.TIMER_COMPLETE,cambiar);//cuando timer termina cambiar
   //con este for creamos una cantidad infinita de objetos ya q i siempre es menor a longitud de los objetos
   for(i=0; i<objetos.length; ++i){
      objetos[i].x = randomObjetoX;//la posicion x del objeto es aleatoria
      objetos[i].y = randomObjetoY;//la posicion y del objeto es aleatoria
      //tween IN de alpha cada que se cree un objeto nuevo
      objetoTw = new Tween(objetos[i], "alpha", Strong.easeInOut, 0, 1, 1, true);
      
      addEventListener(Event.ENTER_FRAME,siempre);
   }
}

//funcion que continuamente cheka que el objeto no pase de los bordes del escenario, y cuando lo hace
//rebota en trayectoria proporcionalmente inversa
function siempre (e:Event):void{
   //la trayectoria x y del objeto corresponde a su angulo de rotacion
   miobjeto.x = miobjeto.x + vxObj;
   miobjeto.y = miobjeto.y + vyObj;
   //el objeto tambien rota con respecto a su trayectoria
   miobjeto.rotation =  int(Math.atan2(vxObj,-vyObj) * 180 / Math.PI);
   
   //con esta serie de if elseif registramos los bordes del escenario donde rebotara el objeto
   if(miobjeto.y+(miobjeto.height/2) > stageH){
      miobjeto.y=stageH-(miobjeto.height/2);
      vyObj=vyObj*-1;
   }
   else if(miobjeto.y-(miobjeto.height/2)<0){
      miobjeto.y=miobjeto.height/2;
      vyObj=vyObj*-1;
   }
   if(miobjeto.x+(miobjeto.width/2)>stageW){
      miobjeto.x=stageW-(miobjeto.width/2);
      vxObj=vxObj*-1;
   }
   else if(miobjeto.x-(miobjeto.width/2)<0){
      miobjeto.x=miobjeto.width/2;
      vxObj=vxObj*-1;
   }
}

// funcion de tipo timer que se ejecuta cuando se termina el timer, dispara un easeOUT del objeto
function cambiar (e:TimerEvent):void{
   objetoTw = new Tween(miobjeto, "alpha", Strong.easeInOut, 1, 0, 1, true);
   //cuando se termine el easeOUT se ejecuta vadenuez
   objetoTw.addEventListener(TweenEvent.MOTION_FINISH,vadenuez);
}

//funcion de tipo tween con la que detenemos el listener del timer y clonamos un nuevo objeto
function vadenuez(e:TweenEvent):void{
   tiempo.reset();//damos reset al timer
   tiempo.removeEventListener(TimerEvent.TIMER_COMPLETE,cambiar);//borramos listener de timer
   removeEventListener(Event.ENTER_FRAME,siempre);//borramos evento siempre
   clonar();//funcion clonar
}

//funcion con la que registraremos las colisiones entre miClip y el objeto   
function cheka ():void{
   puntos.text = ""+score;//el marcador siempre se actualizara con el valor del score

   miclipRect = miClip.getBounds(this);//obtener limites del rectangulo delimitado de miClip
   miclipOffset = miclip.transform.matrix;//transformar la matriz de miClip
   miclipOffset.tx = miclip.x - miclipRect.x;//offset de transfX de miClip menos su rect delimitador
   miclipOffset.ty = miclip.y - miclipRect.y;   //offset de transfY de miClip menos su rect delimitador
   //la colision compleja requiere parametros de bitmapdata, con esto pasamos del cuadro delimitador
   //a los bordes de miClip
   miclipBmpData = new BitmapData(miclipRect.width, miclipRect.height, true, 0);
   miclipBmpData.draw(miclip, miclipOffset);      
   
   //hacemos igual para el objeto
   objetoRect = miobjeto.getBounds(this);
   miobjetoBmpData = new BitmapData(objetoRect.width, objetoRect.height, true, 0);
   objetoOffset = miobjeto.transform.matrix;
   objetoOffset.tx = miobjeto.x - objetoRect.x;
   objetoOffset.ty = miobjeto.y - objetoRect.y;   
   miobjetoBmpData.draw(miobjeto, objetoOffset);   
   
   //con estas variables registramos la colision entre dos puntos
   objetoLoc = new Point(objetoRect.x, objetoRect.y);
   miclipLoc = new Point(miclipRect.x, miclipRect.y);   
   
   //usamos if para chekar las condiciones de la colision de la siguiente manera
   if(miobjetoBmpData.hitTest(objetoLoc,255,miclipBmpData,miclipLoc,255)){
      //use un filtro glow en ambos clips, si dejamos vacios los parametros nos da valores default
      miclip.filters = [new GlowFilter()];
      miobjeto.filters = [new GlowFilter()];
      miobjeto.scaleX = miobjeto.scaleY = 1.2;//ademas escalamos el objeto para apreciar mejor la colision
      score = fuerza+score;
   }
   //mientras no se produzca colision alguna, vaciamos los clips de los filtros usados y devolvemos al objeto
   //su escala original
   else
   {
      miclip.filters = [];
      miobjeto.filters = [];
      miobjeto.scaleX = miobjeto.scaleY = 1;
   }
   miclipBmpData.dispose();//deshechamos bitmap para que no ocupe memoria de mas
   miobjetoBmpData.dispose();//deshechamos bitmap para que no ocupe memoria de mas
}

//agregamos funciones de boton al escenario o stage
//funcion del boton izquierdo del mouse cuando esta DOWN
function downBtn(e:Event){
        //mientras el botón izq. del mouse este abajo, se va a ejecutar el evento chekPower
   powerDir.addEventListener(Event.ENTER_FRAME,chekPower);
}
//funcion del boton izquierdo del mouse cuando esta UP
function upBtn(e:Event){
/* al soltar el botón izq. del mouse eliminaremos el evento que cheka la posición de la flecha así como el evento que se ejecuto al estar el botón izq. del mouse abajo que cheka la fuerza, ademas eliminamos los eventos de los botones mouse UP o DOWN para que no interfieran durante la trayectoria de miClip, solo quedara entonces agregar el listener que permita el movimiento de miClip
*/
   removeEventListener(Event.ENTER_FRAME,flechaPos);
   powerDir.removeEventListener(Event.ENTER_FRAME,chekPower);
   stage.removeEventListener(MouseEvent.MOUSE_DOWN,downBtn);
   stage.removeEventListener(MouseEvent.MOUSE_UP,upBtn);
        miclip.addEventListener(Event.ENTER_FRAME,moverMc);
}

//por ultimo los listeners que se van a ejecutar al inicar la pelicula
addEventListener(Event.ENTER_FRAME,flechaPos);
stage.addEventListener(MouseEvent.MOUSE_UP,upBtn);
stage.addEventListener(MouseEvent.MOUSE_DOWN,downBtn);


Archivo Flash (CS4)
Archivo FLA

Ya al final pueden cambiar los objetos por imágenes png y verán el resultado de la colisión compleja, lo único que falto agregar seria que la piedra también rotara según donde rebote y su fuerza.

Por mario sanchez

3 de clabLevel



 



Ultima edición por mario sanchez el 28 May 2013 04:49 am, editado 6 veces

chrome
Citar            
MensajeEscrito el 15 May 2013 03:01 pm
Eh notado el detalle que entre mas rápido sea el disparo o mas rápido viaje el objeto, las colisiones se tornan mas difíciles de detectar, creo que se debe a la velocidad de fotogramas por segundo del escenario, igual existan otras posibilidades, se los dejo a consideración, si alguien tiene alguna sugerencia, bienvenida sea, la otra interrogante es que si existe alguna mejor forma de detener el evento, ya que el if en raras ocasiones no resulta muy efectivo, sobre todo por que la cantidad de rebotes también es afectado por la fuerza y la fricción, pero bueno para mi fue una salida rápida del problema, también me gustaría que alguien me sugiriera alguna otra alternativa, saludos!

Por mario sanchez

3 de clabLevel



 



Ultima edición por mario sanchez el 28 May 2013 01:01 am, editado 2 veces

chrome
Citar            
MensajeEscrito el 15 May 2013 03:06 pm
por ultimo podemos agregar esta linea:
miclip.rotation = int(Math.atan2(-vx,vy) * 180 / Math.PI);
dentro de la funcion moverMc, y asi le damos efecto a la piedra, para que rote conforme su trayecto

Código ActionScript :

function moverMc(e:Event){
   powerDir.visible = false;
   vy = Math.round((vy + gravedad)*100)/100;
   vy = Math.round((vy)*100)/100;
   vx = Math.round((vx)*100)/100;
   vx = Math.round((vx*friccion*.8)*100)/100;
   vy = Math.round((vy*friccion*.8)*100)/100;
   miclip.x = miclip.x + vx;
   miclip.y = miclip.y + vy;
        //aqui es donde agregariamos la linea
   miclip.rotation =  int(Math.atan2(vx,vy) * 180 / Math.PI);


Y se vería de esta manera:

Por mario sanchez

3 de clabLevel



 



Ultima edición por mario sanchez el 28 May 2013 05:11 am, editado 4 veces

chrome
Citar            
MensajeEscrito el 20 May 2013 05:12 pm

mario sanchez escribió:

tiro con parabola a objeto en movimiento random con score

sin mas pego el scrip as3 para que lo analicen, si a alguien le interesa el tema se los voy explicando


hola. Para salir publicado en portada hay que explicar en formato tutorial con pasos, ejemlos y códigos explicados y si pasa la calidad va a portada.

Muevo a aportes.

Por Mariux

BOFH

7756 de clabLevel

28 tutoriales
15 articulos

Genero:Femenino   Héroes Editores

Diseñadora & ilustradora

chrome
Citar            
MensajeEscrito el 28 May 2013 01:15 am
ok, ya dejo el script explicado y mas detallado para lo q te sirva, saludos!

Por mario sanchez

3 de clabLevel



 

chrome
Citar            
MensajeEscrito el 28 May 2013 11:10 pm
por cierto mis ejemplos swf estan en google sites, si los van a poder visualizar los demas o solo yo?

Por mario sanchez

3 de clabLevel



 

chrome
Citar            
MensajeEscrito el 10 Ago 2015 08:39 pm
Podrías subir las imágenes de nuevo por favor?

Por pascuale

Claber

100 de clabLevel



Genero:Masculino  

maracay -venezuela

mozilla

 

Cristalab BabyBlue v4 + V4 © 2011 Cristalab
Powered by ClabEngines v4, HTML5, love and ponies.