De ActionScript 2.0 y la herencia del MovieClip

26 de julio de 2007

Introducción

Una de las principales características del ActionScript 2.0 es que está realmente orientado a objetos. Esto aumenta su capacidad enormemente, y facilita el acercamiento de programadores experimentados en otros lenguajes,y familiarizados con los conceptos de la programación orientada a objetos. Aún así, al ser el Flash un entorno de diseño gráfico sigue poseyendo ciertas restricciones desde el código que intentaremos solventar en este artículo.

El IDE

Flash MX 2004 Pro posee un IDE enfocado principalmente al desarrollo gráfico, y que deja bastante que desear a cualquier programador,por no decir que utilizarlo es una verdadera tortura. Por suerte se pueden encontrar soluciones a este problema como Eclipse, que facilita la programación con la incorporación de un plugin. Se puede encontrar información detallada de como integrar Flash con Eclipse en el artículo de Carlos Rovira Towards Open Source Flash Development, en el cual además se aporta información valiosísima acerca de como instanciar MovieClips sin tenerlos previamente en la librería. Es la piedra angular de todo mi artículo.

La herencia

En el artículo anterior artículo se nos muestra como podemos programar una aplicación el Flash sin utilizar para nada el entorno de desarrollo de Macromedia. !!!GENIAL!!! Sí, para cualquier programador esto es ver sus sueños cumplidos, mandar a la porra un IDE horrible y únicamente necesario para compilar.

Quizás lo más importante de todo el artículo sea la posibilidad de crear MovieClips en tiempo de ejecución, no dependiendo de gráficos diseñados en Flash que deban ser instanciados(en el concepto de Flash) antes de poder ser utilizados. (Lease el capítulo 13 del libro ‘Essential ActionScript 2.0′ de Collin Mook publicado por O’reilly donde hay un perfecto ejemplo. Esto para un programador es “casi” una maravilla. Este “casi” se debe que normalmente un programador no trabaja solo, sino que lo hace con diseñadores que si utilizan la librería de objetos, y desean que esa clase tan maravillosa que ha creado el programador la puedan utilizar en su último diseño sin devanarse los sesos.

La mejor solución es crear una clase que herede del MovieClip, se sobrescriben un par de métodos y listo. El diseñador solo tendra que acordarse de asignar en las propiedades del MovieClip que éste pertenece a la clase ActionScript correspondiente. Todos felices, todos contentos…¿?

No,yo no. La situación actual es frustrante para el programador. Puede sobrescribir todos los métodos que quiera y hacer que el MovieClip tenga el comportamiento deseado en ese caso. Incluso puede crear métodos y propiedades nuevas. PERO (aquí está el problema), no puede utilizar estos me´todos y propiedades dentro de su código puesto que las instancias de su subclase no estarán “instanciadas” según el concepto de Flash, ya que solo se puede hacer esto en tiempo de diseño. La única manera de poder acceder a esos métodos y propiedades es llamarlos desde código que se encuentre en la propia linea de tiempo y lo cual nos obliga salirnos totalmente del esquema de programación ordenada, escribiendo funciones y código que siempre necesitaremos en la linea de tiempo, fuera de nuestro fichero de clase.

Ejemplo (ya es hora):

Supongamos que queremos crear un botón redondeado, dibujado enteramente en código. Voy a utilizar el Hack 14 del libro ‘Flash Hacks’ publicado por O’reilly.

Podemos dibujar el botón directamente en la linea de tiempo utilizando el siguiente código:

[code lang="actionscript"]
var clip:MovieClip = this.createEmptyMovieClip("circle_mc",this.getNextHighestDepth( ));
circle_mc._x = circle_mc._y = 150;
circle_mc.lineStyle(200, 0x0, 100);
circle_mc.moveTo(0, 0);
circle_mc.lineTo(0.2, 0);
[/code]

Siguiendo con el Hack 14 podemos agregar código al evento onRelease:

[code lang="actionscript"]
function createButton(dynaButton, dynaLabel, depth, x, y) { var clip = this.createEmptyMovieClip(dynaButton, depth);
clip.lineStyle(15, 0x0, 100);
clip.moveTo(0, 0);
clip.lineTo(0.2, 0);
clip._x = x;
clip._y = y;
var txt_fmt:TextFormat = new TextFormat( );
txt_fmt.font = "_sans";
txt_fmt.size = 12;
this.createTextField(dynaButton + "_txt", depth + 1,x + 10, y - 10, 100, 20);
var textLabel = this[dynaButton + "_txt"];
textLabel.text = dynaLabel;
textLabel.setTextFormat(txt_fmt);
}
createButton("home_btn", "home", 1, 100, 10);
createButton("products_btn", "products", 3, 100, 30);
createButton("about_btn", "about us", 5, 100, 50);
createButton("links_btn", "links we like", 7, 100, 70);
home_btn.onRelease = function( ) {
// Do stuff
};
products_btn.onRelease = function( ) {
// Do stuff
};
about_btn.onRelease = function( ) {
// Do stuff
};
links_btn.onRelease = function( ) {
// Do stuff
};
[/code]

Bien, esto funciona, pero se aleja de un diseño en condiciones. Así que mejor creamos una clase factoría de botones que nos cree los objetos según se los pedimos:

[code lang="actionscript"]
// This ActionScript 2.0 pre lang="actionscript"must go in an external CreateButton.as fileclass CreateButton {
// Variable target is the timeline that the
// CreateButton instance will create buttons on.
private var target:MovieClip;
// Constructor.
public function CreateButton(targetTimeline:MovieClip) {
target = targetTimeline;
}
// Define createBtn( ) method
// Arguments:
// buttonName - The instance name of the created button.
// dynaLabel - The label text of the created button.
// depth - The depth of the created button.
// x, y - The position of the created button.
// Returns:
// A button movie clip with instance name buttonName.
public function createBtn(buttonName:String, dynaLabel:String,
depth:Number, x:Number, y:Number):MovieClip {
// Initialize.
var clip:MovieClip;
var clipMask:MovieClip;
var txt_fmt:TextFormat;
var clipText:TextField;
// Create button clip.
clip = target.createEmptyMovieClip(buttonName, depth);
drawPip(clip);
clip._x = x;
clip._y = y;
// Create button hit area.
clipMask = clip.createEmptyMovieClip("mask", 0);
clipMask._visible = false;
drawPip(clipMask);
clip.hitArea = clipMask;
// Create TextFormat object for applying formatting.
txt_fmt = new TextFormat( );
txt_fmt.font = "_sans";
txt_fmt.size = 12;
// Create textField (i.e., the button label).
clip.createTextField(buttonName + "_txt", 1, 10, -10, 100, 20);
clipText = clip[buttonName+ "_txt"];
clipText.text = dynaLabel;
clipText.setTextFormat(txt_fmt);
return clip;
}
private function drawPip(clip):Void {
clip.lineStyle(15, 0x0, 100);
clip.moveTo(0, 0);
clip.lineTo(0.2, 0);
}
}
[/code]

Seguimos abusando del Hack 14, y pongamos el código necesario para crear botones en nuestro .fla:

[code lang="actionscript"]//Creamos una instacia del generador de botones
var buttonGen:RoundButtonGen=new RoundButtonGen(this);var home:MovieClip= buttonGen.createBtn("inicio","inicio",this.getNextHighestDepth( ), 100, 10);
var products:MovieClip = buttonGen.createBtn("productos", "productos", this.getNextHighestDepth( ), 100, 30);
var about:MovieClip = buttonGen.createBtn("acerca de", "acerca de", this.getNextHighestDepth( ), 100, 50);
var links:MovieClip = buttonGen.createBtn("enlaces", "enlaces predilectos", this.getNextHighestDepth( ), 100, 70);
home.onRelease = function( ) {
trace("You clicked the home button");
};
products.onRelease = function( ) {
trace("You clicked the products button");
};
about.onRelease = function( ) {
trace("You clicked the about button");
};
links.onRelease = function( ) {
trace("You clicked the links button");
};[/code]

Aquí se encuentra uno con una aberración. Existiendo como existe la herencia en Flash, ¿Por qué tengo que reescribir en la linea de tiempo el comportamiento de mi clase?. Al escribir el comportamiento de cada botón se repite código innecesariamente, y se destrozan las reglas de programación ordenada. Puedo crear un objeto que se llame RoundButton al cual le agrege la función deseada en la función onRelease, y todo volvería a la normalidad. Esto complica muchísimo las cosas, y ahora veremos por qué.

La manera en la que cualquier programador crearía una nueva instancia del botón RoundButton es la siguiente:

[code lang="actionscript"]var boton:RoundButton;
boton= new RoundButton("inicio");[/code]

Pero esto no funciona en Flash ya que la única manera de crear un MovieClip en pantalla (instanciarlo segun Flash) es utilizar los métodos duplicateMovieClip, attachMovieClip o createEmptyMovieClip, los cuales devuelven siempre un objeto MovieClip, no un RoundButton como nosotros deseamos. Habrá que buscar alguna manera de crear un MovieClip en pantalla que tenga asignada nuestra subclase. Se busca un poco en la documentación y se ecuentran sopresas.

Este texto está sacado del capítulo 13 de Essential ActionScript 2.0:

“It is, hence, not possible to create a functioning MovieClip subclass without a corresponding movie clip symbol in the Library of a .fla file.”

Con la Iglesia hemos topado. No se pueden crear instancias de Flash de subclases de MovieClip sin tenerlas en la librería. Parece que la única solución es utilizar el sistema proporcionado en el ejemplo del Hack 14. ¿Seguro? Todavía hay un rayo de luz al fondo del tunel, ya que en el ejemplo de Carlos Rovira se nos enseña como crear instancias de objetos propios que pueden heredar de MovieClip (si no lo habeis leído hacedlo ya). Podemos rizar el rizo e intentarlo. Seguimos su ejemplo y creamos nuestra clase RoundButton:
[code lang="actionscript"]class net.somms.RoundButton extends MovieClip{ static var SymbolName:String = "__Packages.net.somms.RoundButton";
static var SymbolOwner:Object= RoundButton;
static var SymbolLinked:Object = Object.registerClass(SymbolName, SymbolOwner);
private var texto:String;
onRelease()
{
trace("Ha pulsado el boton" + this.texto);
}
public setTexto(nombre:String)
{
this.texto=cadena;
}
}[/code]

Ya tenemos nuestro objeto, ahora debemos resolver otro problema. Queremos crearlo y que a la vez aparezca en pantalla.
Como la única manera de instanciarlo es un attachMovie al igual que en el ejemplo de Carlos Rovira, lo intentamos y modificamos el código de RoundButtonGen:

[code lang="actionscript"].
.
.
var clip:MovieClip;
.
.
.// Create button clip.
clip=target.attachMovie(RoundButton.SymbolName,buttonName,depth);
drawPip(clip);
clip._x = x;
clip._y = y;
.
.
.[/code]

Perfecto, si compilamos esto funciona. Ahora queremos asignar nuestra propiedad “texto”. Lo ideal sería poder hacerlo a traves del constructor, ¡pero no podemos llamar al constructor!. Otra salida es utilizar el método setTexto(cadena:String) que permite asignarle el valor a la variable. Claro que para acceder a este método hay que tener una instancia de RoundButton, no una de MovieClip como devuelve attachMovie().
[code lang="actionscript"].
.
.
var clip:RoundButton;
.
.
.// Create button clip.
clip = target.attachMovie(RoundButton.SymbolName,buttonName,depth);
drawPip(clip);
clip._x = x;
clip._y = y;
.
.
.[/code]

Este código nunca compilará, ya que la funcion attachMovie devuelve siempre un tipo MovieClip y no tiene método setTexto(). La solución la habran visto ya algunos, un cast:

[code lang="actionscript"] clip=RoundButton(target.attachMovie(RoundButton.SymbolName,buttonName,depth));[/code]

¡Tachan!. Acabamos de crear en tiempo de ejecución una instancia funcional de una subclase de MovieClip sin el correspondiente movieclip en la librería (lease la nota que decía que era imposible un poco mas arriba).

Solo me queda el mal sabor de boca de no poder pasar parámetros al constructor de RoundButton, pero esto se soluciona facilmente agregando un método Initialize() a RoundButton al que pasamos los parametros que queramos, y sera para nosotros el constructor. Hay que acordarse de llamar siempre a Initialize despues de crear una instancia de nuestro objeto.

Un detalle curioso, al compilar con Flash MX 2004 se produce un Warning al hacer el casting, pero se ejecuta con normalidad.

Un saludo

  • qrcode link

Ir al sitio para móviles