Comunidad de diseño web y desarrollo en internet online

Trigonometría en Flash y clases

Para empezar, antes de realizar este tutorial es conveniente dominar algo de trigonometría básica y Programación Orientada a Objetos a un nivel aceptable.

Radianes y grados:

Las funciones matemáticas en Flash exigen radianes, sin embargo las rotaciones que damos a los clips de película son en grados. Dividir una circunferencia en 360 partes iguales no tiene mucho misterio, sin embargo el sistema en radianes es más usado (de hecho la unidad para medir grados en el Sistema Internacional son precisamente los radianes). Este sistema consiste en dividir una circunferencia en segmentos iguales a su radio. Como la longitud de una circunferencia es 2*π*radio, radio=2* π, de lo cual inferimos que la circunferencia quedará dividida en 2π partes.

Como en Flash trabajaremos en grados y radianes sería conveniente averiguar a cuántos grados equivale un radián:

360 º = 2π rad → 1 º = 2π/360 rad = π/180 rad

De modo que podemos crear una clase, Trig.as en la que pondremos una variable privada y un getter:

//importamos la clase Point
import flash.geom.Point;
class Trig {
        //constante estática para hallar radianes en 1º
        private static var g_r:Number = Math.PI/180;
        //la hacemos accesible
        public static function get degR():Number {
                return g_r;
        }
}

Trigonometría en Flash:

Todos los juegos en los que haya personajes cuyo movimiento se basa en rotaciones o en el que los personajes obtienen una rotación a partir de coordenadas utilizan las razones trigonométricas. En Flash se puede acceder a ellas mediante el objeto Math.

Básicamente nos servirán estos métodos:

Math.sin y Math.cos:

Nos servirán para hallar coordenadas a partir de una rotación. Supongamos que la circunferencia de arriba es unitaria (su radio es 1), y que el punto A es el punto (0,0) de nuestro sistema de coordenadas.

  • El seno (Math.sin(α)) nos dice cuánto tendremos que desplazarnos en el eje horizontal para llegar de A a B (longitud de BC)
  • El coseno (Math.cos(α)) nos da esa misma información sobre la coordenada Y (longitud de AB). Pero tenemos que tener en cuenta que el plano cartesiano de flash está invertido verticalmente. Por tanto desplazarse hacia arriba es restar a la ordenada y desplazarse hacia abajo, sumar. Por ello siempre que usemos cosenos en flash, tendremos que calcular su opuesto (-cos),

Si el radio del círculo no es uno, solamente tendremos que multiplicar esos valores por su radio.

De modo que nuestra función devolverá un punto, para lo que podemos usar la clase Point (que en su momento presentó Elecash), y pedirá como parámetros un ángulo en grados, una distancia, a la que estará el nuevo punto y un punto de inicio. Los dos últimos parámetros pueden ser por defecto 1 y (0,0) respectivamente, de manera que no haya que declararlos.Ya podemos importar Point y crear una nueva función estática, de modo que la clase quedaría así (Atención a los comentarios, donde está la explicación):

//importamos la clase Point
import flash.geom.Point;
class Trig {
        //definición de clase
        //constante estática para hallar radianes en 1º
        private static var g_r:Number = Math.PI/180;
        //la hacemos accesible
        public static function get degR():Number {
                return g_r;
        }
        public static function hallarPunto(angle:Number, distance:Number, initPos:Point):Point {
                //Esta función nos devolverá un punto dependiendo de la rotación
                //gestión de parámetros opcionales
                if (distance == undefined) {
                        distance = 1;
                }
                if (initPos == undefined) {
                        var initX = 0;
                        var initY = 0;
                } else {
                        var initX = initPos.x;
                        var initY = initPos.y;
                }
                var xPos:Number = initX+distance*Math.sin(angle*g_r);
                //Hallamos la coordenada x del punto
                var yPos:Number = initY-distance*Math.cos(angle*g_r);
                //Lo mismo con la y, teniendo en cuenta que está invertida con respecto a un plano cartesiano
                var targetPoint:Point = new Point(xPos, yPos);
                //Concatenamos la informacióm en un punto
                return targetPoint;
                //lo devolvemos
        }
}

Nota: Se puede hacer lo mismo con Point.polar(), pero es más lento, compruébenlo si quieren ^^.

Math.atan2:

Supongamos ahora que lo que queremos es que un clip de película con posición (A) apunte al punto B. Dicho de otro modo, ahora tenemos los demás datos del dibujo del triángulo y queremos hallar el ángulo α.

  • Math.atan2 nos dará ese ángulo, pero tendremos que hacer que las coordenadas iniciales (x,y) sean las de referencia (el punto (0,0)). Para ello hallamos la diferencia entre las coordenadas de A y B.

Tendremos pues que crear otra función que tendrá como parámetros el punto al que queremos que apunte y las coordenadas iniciales. De paso añadimos un parámetro para poder darle ángulos en radianes.:

//importamos la clase Point
import flash.geom.Point;
class Trig {
        //definición de clase
        //constante estática para hallar radianes en 1º
        private static var g_r:Number = Math.PI/180;
        //la hacemos accesible
        public static function get degR():Number {
                return g_r;
        }
        public static function hallarPunto(angle:Number, distance:Number, initPos:Point):Point {
                //Esta función nos devolverá un punto dependiendo de la rotación
                //gestión de parámetros opcionales
                if (distance == undefined) {
                        distance = 1;
                }
                if (initPos == undefined) {
                        var initX = 0;
                        var initY = 0;
                } else {
                        var initX = initPos.x;
                        var initY = initPos.y;
                }
                var xPos:Number = initX+distance*Math.sin(angle*g_r);
                //Hallamos la coordenada x del punto
                var yPos:Number = initY-distance*Math.cos(angle*g_r);
                //Lo mismo con la y, teniendo en cuenta que está invertida con respecto a un plano cartesiano
                var targetPoint:Point = new Point(xPos, yPos);
                //Concatenamos la informacióm en un punto
                return targetPoint;
                //lo devolvemos
        }
        public static function hallarAngulo(targetPos:Point, cords:Point, rads:Boolean):Number {
                //extraemos coordenadas de los puntos
                if (cords == undefined) {
                        var cr_x:Number = 0;
                        var cr_y:Number = 0;
                } else {
                        var cr_x:Number = cords.x;
                        var cr_y:Number = cords.y;
                }
                var initX = targetPos.x;
                var initY = targetPos.y;
                //las restamos
                var xRef:Number = cr_x-initX;
                var yRef:Number = cr_y-initY;
                //Obtenemos el ángulo y devolvemos el resulado.
                var angle:Number = -Math.atan2(xRef, yRef);
                //Si nos indican que el ángulo es en radianes no hacemos nada,
                //de lo contrario lo convertimos a grados.
                if (rads != true) {
                        angle /= g_r;
                }
                return angle;
        }
}

Obteniendo una coordenada a través de la otra y la rotación

Es posible que también sea útil saber en qué posición de uno de los ejes de coordenadas estará un objeto con una trayectoria cuando esté en una determinada posición del otro eje. Un uso prático sería crear un enemigo que juegue al ping-pong. Su posición _x ideal es la que tendrá la bola cuando, con su rotación pase por la coordenada _y del enemigo. Crearemos pues dos funciones más:

//importamos la clase Point
import flash.geom.Point;
//definición de clase
class Trig {
        //constante estática para hallar radianes en 1º
        private static var g_r:Number = Math.PI/180;
        //la hacemos accesible
        public static function get degR():Number {
                return g_r;
        }
        //Esta función nos devolverá un punto dependiendo de la rotación
        public static function hallarPunto(angle:Number, distance:Number, initPos:Point):Point {
                //gestión de parámetros opcionales
                if (distance == undefined) {
                        distance = 1;
                }
                if (initPos == undefined) {
                        var initX = 0;
                        var initY = 0;
                } else {
                        var initX = initPos.x;
                        var initY = initPos.y;
                }
                var xPos:Number = initX+distance*Math.sin(angle*g_r);
                //Hallamos la coordenada x del punto
                var yPos:Number = initY-distance*Math.cos(angle*g_r);
                //Lo mismo con la y, teniendo en cuenta que está invertida.
                var targetPoint:Point = new Point(xPos, yPos);
                //Concatenamos la informacióm en un punto
                return targetPoint;
                //lo devolvemos
        }
        //Esta función devuelve una rotación a partir de un punto de origen y otro dados.
        public static function hallarAngulo(targetPos:Point, cords:Point, rads:Boolean):Number {
                //extraemos coordenadas de los puntos
                if (cords == undefined) {
                        var cr_x:Number = 0;
                        var cr_y:Number = 0;
                } else {
                        var cr_x:Number = cords.x;
                        var cr_y:Number = cords.y;
                }
                var initX = targetPos.x;
                var initY = targetPos.y;
                //las restamos
                var xRef:Number = cr_x-initX;
                var yRef:Number = cr_y-initY;
                //Obtenemos el ángulo y devolvemos el resulado.
                var angle:Number = -Math.atan2(xRef, yRef);
                //Si nos indican que el ángulo es en radianes no hacemos nada,
                //de lo contrario lo convertimos a grados.
                if (rads != true) {
                        angle /= g_r;
                }
                return angle;
        }
        //Estas funciones devolverán una de las coordenadas sabiendo la otra y un ángulo, así
        //como las coordenadas iniciales.
        public static function hallarX(angle:Number, yPoint:Number, initCords:Point, rads:Boolean):Number {
                //Convertimos el ángulo en radianes si no nos lo dan.
                var ang:Number = angle;
                if (rads != true) {
                        ang *= g_r;
                }
                if (initCords == undefined) {
                        var initCords:Point = new Point(0, 0);
                }
                //calculamos lo que nos hemos desplazado en y. 
                var yDif:Number = initCords.y-yPoint;
                //Si yDif=radio*Math.cos(ang):
                var radio:Number = yDif/Math.cos(ang);
                //Si el radio resulta ser menor que cero, la ecuación no tiene sentido y no se cruzan.
                if (radio<0) {
                        return null;
                }
                //Hallado el radio ya solo falta hallar la x. 
                var xPos:Number = radio*Math.sin(ang)+initCords.x;
                return xPos;
        }
        public static function hallarY(angle:Number, xPoint:Number, initCords:Point, rads:Boolean):Number {
                var ang:Number = angle;
                if (rads != true) {
                        ang *= g_r;
                }
                if (initCords == undefined) {
                        var initCords:Point = new Point(0, 0);
                }
                var xDif:Number = initCords.x-xPoint;
                var radio:Number = xDif/Math.sin(ang);
                //Aquí es al revés. Siempre que hay colisión, el radio es negativo.
                if (radio>0) {
                        return null;
                }
                var yPos:Number = radio*Math.cos(ang)+initCords.y;
                return yPos;
        }
        //
        //
        //
        //
}

Manejando MCs :

Ahora que ya tenemos nuestra clase Trig, vamos a crear una clase que extienda a MovieClip con la que interactúe.

import Trig;
import flash.geom.Point;
class Character extends MovieClip {
}

No serán más que unas funciones para manejar las coordenadas de los clips como puntos y la rotación como radianes:

import Trig;
import flash.geom.Point;
class Character extends MovieClip {
        //Obtenemos las coordenadas del MC como un punto
        public function getCoords():Point {
                return new Point(this._x, this._y);
        }
        //Damos al MC las coordenadas del punto especificado
        public function setCoords(pnt:Point):Void {
                this._x = pnt.x;
                this._y = pnt.y;
        }
        //hallamos la rotación del clip, en radianes.
        public function getRadsRotation():Number {
                return this._rotation*Trig.degR;
        }
        //le damos al clip una rotación en radianes.
        public function setRadsRotation(rads:Number):Void {
                this._rotation = rads/Trig.degR;
        }
        //Devuelve la posición del ratón con respecto a las coordenadas del clip. (no es igual a devolver _xmouse ý _ymouse)
        public function getMouseCoords():Point {
                var mPoint:Point = new Point(this._parent._xmouse, this._parent._ymouse);
                var dif:Point = mPoint.subtract(this.getCoords());
                return dif;
        }
}

Conclusiones

Si queremos hacer cualquier juego en el que los personajes disparen o se muevan por el escenario en base a su rotación, es imprescindible tener conocimientos sobre trigonometría, y matemáticas en general. Una vez hecho eso, sólo hacen falta algunos conocimientos sobre programación y mucha imaginación.

Cuando se hacen proyectos en flash que requieren siempre funciones parecidas, es muy conveniente agrupar estas funciones en clases para que, por ejemplo, un clip pueda apuntar al ratón con una sóla línea de código, con el consiguiente ahorro de tiempo.

El ejemplo de este tutorial (En los archivos descargables) implementa de manera sencilla estos conceptos, y además podrán disfrutar destrozando a cierto usuario de nombre alfax XD.

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Descargar Archivo

Publica tu comentario

El autor de este artículo ha cerrado los comentarios. Si tienes preguntas o comentarios, puedes hacerlos en el foro

Entra al foro y participa en la discusión

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate