Desarrollando videojuegos con cocos2d-html5 (4): Comenzando un proyecto

cocos2d-tutorial-4Comenzando un proyecto

El resultado final de esta sección
El resultado final de esta sección

En esta parte del tutorial comenzaremos un proyecto de cocos2d-html5 a partir de la plantilla que la librería provee. Vamos a agregar una imagen, un texto y un botón a la escena y vamos a hacer que la imagen y el texto ejecuten unas acciones a partir de presionar el botón.

Copiemos el template

Para iniciar un proyecto con cocos2d-html5 basta con copiar el directorio template que se encuentra en el directorio que descomprimimos en la parte 2 del tutorial.

cocos2d-html5-4-1

¿Qué haremos ahora?

A partir de ahora iremos haciendo cambios sobre este proyecto para agregar algunos de los nodos que conocimos en la parte anterior. De ahora en adelante conoceremos de primera mano los pasos que hay que dar para crear los diferentes tipos de nodos.

El directorio template contiene prácticamente todos los elementos que vimos en la parte anterior, en el directorio HelloWorldHTML5, sin el Sprite de ejemplo. De esta manera, sólo hace falta quitar unos pocos elementos para comenzar a poner los de nuestro juego.

Si accedemos a nuestro juego, con la dirección http://localhost/a/leGame veremos nuestros inicios en ejecución:

helloworld1

Elaborando la primera escena

Cambiando un sprite

Vamos nuevamente a myApp.js. Observemos el código que se encarga de crear la imagen y colocarla sobre la capa.

//myApp.js
// add "Helloworld" splash screen"
this.sprite = cc.Sprite.create("res/HelloWorld.png");
this.sprite.setAnchorPoint(cc.p(0.5, 0.5));
this.sprite.setPosition(cc.p(size.width / 2, size.height / 2));

Casi todos los nodos en cocos2d se crean con su propia función create(). En el caso de cc.Sprite, puedes no pasar ningún parámetro para crear un sprite vacío, o un parámetro con el nombre de un archivo, o el nombre de un archivo junto con un rectángulo que determina las coordenadas dentro de ese archivo. Después de hacer este tutorial, cuando te encuentres dudando de cuáles son los parámetros que debes pasar a un nodo, revisa la función create respectiva para consultar.

Son muy específicos los casos donde debes hacer new de un objeto. La razón de esto es porque las funciones create se encargan también de manejar los objetos en memoria, por lo que cocos2d se encarga de liberar el objeto cuando tengas que destruir la escena, evitando memory leaks, algo doblemente importante cuando trabajas con los bindings en Javascript. En cocos2d este concepto se conoce como objetos autorelease.

La segunda función establece el ancla del objeto, o su origen en su sistema de coordenadas locales. En este caso el ancla está puesto en la mitad del sprite. Recuerda que estamos empleando el sistema de coordenadas de OpenGL, aquí una guía visual de lo que representa el anchor point en varios puntos:

cocos2d-html5-4-2

Observa que este sprite, que tiene de tamaño 42×40, tiene su centro en un punto que no corresponde a un único pixel.

La función cc.p es la misma función que cc.PointMake, y cc.Point, que retornan un objeto tipo punto. Estos nombres tienen más sentido cuando nos vamos a sus homólogos en C++.

La tercera función establece la posición del sprite. Recordemos que estamos en el sistema de coordenadas de OpenGL. El origen está en la esquina inferior izquierda, el eje x incrementa hacia la derecha, y el eje y incrementa hacia arriba. El objeto size que se encuentra en el código se obtiene de la función cc.Director.getInstance().getWinSize(), y es la que nos permite posicionar los objetos con respecto al tamaño de la ventana. En el caso de este código, el sprite se colocará exactamente en la mitad de la ventana.

Vamos entonces a descargar este sprite. Colócalo en el directorio ‘res’ del proyecto. Ahora cambia el nombre del archivo del sprite en el código

//myApp.js
// add "Helloworld" splash screen"
this.sprite = cc.Sprite.create(s_foobar);
this.sprite.setAnchorPoint(cc.p(0.5, 0.5));
this.sprite.setPosition(cc.p(size.width / 2, size.height / 2));

Hace falta tener que agregar también la definición de la variable s_foobar, y en consecuencia, la dirección de este archivo en resource.js. Así que vamos a este archivo, agreguemos una variable que contenga la dirección y agreguemos una entrada al arreglo g_ressources.

//resource.js
var s_foobar = "res/avatar_0.png";
var g_ressources = [
//...
{type:”image”, src:s_foobar},
//..
];

Por convención, iniciamos todos los nombres de los sprites con el prefijo s_, de tal manera que los podamos referenciar en los demás archivos sin tener que repetir el string con la dirección. Así, si tenemos que cambiar el nombre de un sprite, no tenemos que ir pasando sobre cada archivo cambiando el nombre, sino solamente cambiar la dirección en resource.js.

Es importante que incluyas todos los recursos que vayas a emplear para la carga, incluyendo las imágenes que son cargadas por otros archivos de texto, como los archivos .plist, o los archivos .fnt.

Ejecutemos nuevamente el juego y ahora obtendremos el siguiente resultado:

helloworld2

El Director y otros singletons

Habremos visto en el pedazo de código anterior de myApp.js esta llamada para obtener las dimensiones de la ventana:

var size = cc.Director.getInstance().getWinSize();

El objeto cc.Director es un objeto que cocos2d crea al inicializar toda la aplicación, y al igual que otros objetos, durante la ejecución de tu aplicación existe sólo una instancia a la que vas a acceder a través de la función getInstance(). Acostúmbrate a conseguir estas llamadas en varias partes del código.

Veremos también que con el director podremos cambiar de escenas y hacer otras cosas de interés. Pero esto lo dejaremos para otra parte del tutorial.

Controles rápidos con botones

Los botones encapsulan un comportamiento muy usual con los juegos: un elemento que presente un sprite cuando no hay un dedo tocándolo, presenta otro cuando lo toca, y una función que se va a llamar cuando lo presionas.

Tenemos varios tipos de botones, y todos parten del objeto MenuItem. Todos estos objetos deben ir en una capa especial de tipo Menu. El MenuItem de este código es un MenuItemImage, el cual se crea pasándole directamente las ubicaciones de las imágenes.

Otros tipos de MenuItem son:

  • MenuItemLabel: se crea pasándole un label (texto). Sobre los Labels hablaremos al final de esta sección.
  • MenuItemSprite: se crea pasándole dos sprites que hayas creado. Es útil para cuando las imágenes las extraes de un spritesheet.
  • MenuItemToggle: acepta dos MenuItems. Sirve para implementar botones «toggle«, que tienen dos estados, «encendido» y «apagado», dependiendo de cuando los tocas.

Observemos entonces el código que crea el Menu y el botón actualmente:

// este código crea el MenuItemImage
var closeItem = cc.MenuItemImage.create(
    "res/CloseNormal.png",
    "res/CloseSelected.png",
    function () {
        history.go(-1);
    },this);
closeItem.setAnchorPoint(cc.p(0.5, 0.5));

//este código crea la capa de menu y agrega el MenuItemImage
var menu = cc.Menu.create(closeItem, null);
menu.setPosition(cc.PointZero());
this.addChild(menu, 1);
closeItem.setPosition(cc.p(size.width - 20, 20));

Observemos que MenuItemImage acepta 4 parámetros, la imagen normal del botón, la imagen al ser presionada, la función que se va a llamar (que puede ser perfectamente una función anónima), y el «target» de la función (a qué se refiere el «this» dentro de la función). Esta función puede aceptar también 3 imágenes, la imagen normal, la seleccionada, y la que aparece cuando desactivas el botón con la función setEnabled(false). La función callback en este caso lo que hace es retroceder una página en el navegador. Si accedes al juego escribiendo el URL directamente no va a hacer nada.

Vamos a crear un nuevo botón descargando el estado normal y el estado seleccionado. Copiemos estas dos imágenes en el directorio res/. Vamos a escribir nuevo código debajo del bloque que acabamos de citar.

//myApp.js
closeItem = cc.MenuItemImage.create(
    s_buttonNormal,
    s_buttonSelected,
    function () {
        cc.log("Hola");
    }, this);
closeItem.setAnchorPoint(cc.p(0.5, 0.5));
closeItem.setPosition(cc.p(size.width/2, 40));

menu.addChild(closeItem);

Veremos que la función que estamos pasando llama a la función cc.log(). Esto nos permite imprimir texto en la consola del navegador, bastante útil para depurar código cuando lo necesites. En el código hay nuevas variables, s_buttonNormal y s_buttonSelected, vamos a definirlas en resource.js:

//resource.js
var s_HelloWorld = "res/HelloWorld.png";
var s_CloseNormal = "res/CloseNormal.png";
var s_CloseSelected = "res/CloseSelected.png";

var s_foobar = "res/avatar_0.png";

var s_buttonNormal = "res/button-normal.png";
var s_buttonSelected = "res/button-selected.png";

var g_ressources = [
    {type:"image", src:s_HelloWorld},
    {type:"image", src:s_CloseNormal},
    {type:"image", src:s_CloseSelected},
    {type:"image", src:s_foobar},
    {type:"image", src:s_buttonNormal},
    {type:"image", src:s_buttonSelected},
];

Volvamos a acceder a nuestro juego y veremos los cambios. En este ejemplo muestro la consola de Chrome con el texto «Hola» impreso.

helloworld3

Los botones tienen mucha flexibilidad para implementar lo que necesites de interfaces. Los utilizarás frecuentemente.

Cambiando textos

En cocos2d se manejan dos tipos de texto, los textos hechos con fuentes TrueType (cc.LabelTTF), y los textos hechos con fuentes bitmap (cc.LabelBMFont).

Las fuentes TrueType tienen la flexibilidad de cambiarles el tamaño, utilizar su versión Bold o itálica, pero dibujar fuentes TrueType es costoso en recursos de procesamiento (son curvas matemáticas que se deben convertir a imagen), amén de las problemas que podrían surgir de los requisitos de la licencia que emplee la fuente en cuestión (recuerda que algunas no permiten distribuirse libremente con el proyecto).

Las fuentes bitmap son imágenes con todas las letras de la fuente y un archivo que indica las coordenadas de cada letra. Se pueden fabricar a mano, o a partir de una fuente TrueType con un software. Como son imágenes como cualquier sprite, dibujarlas se hace muchísimo más rápido que su contraparte en TTF.

nombres_0
Este es un ejemplo de una imagen que contiene una fuente bitmap

Para generar las fuentes en bitmap puedes emplear el programa BMFont de AngelCode.com (disponible gratuitamente). El uso de este programa se sale del alcance de esta sección, así que para usar una fuente bitmap de ejemplo descarga la imagen de ejemplo anterior y este archivo. Colócalos en el directorio res/ de tu proyecto, y agrega ambos archivos al registro de recursos en resource.js.

//resource.js
//...
var s_nombresText = "res/nombres.txt";
var s_nombresImage = "res/nombres_0.png";
//...
    {type:"image", src:s_nombresImage},
    {type:"fnt", src:s_nombresText},
//...

En myApp.js ya tenemos un ejemplo de cómo crear un label con fuentes TrueType:

//myApp.js
this.helloLabel = cc.LabelTTF.create("Hello World", "Arial", 38);
// position the label on the center of the screen
this.helloLabel.setPosition(cc.p(size.width / 2, size.height - 40));
// add the label as a child to this layer
this.addChild(this.helloLabel, 5);

En la función create pasamos tres parámetros, el texto inicial, el nombre de la fuente, y el tamaño inicial. Posteriormente le colocamos una posición y lo agregamos a la capa principal de la escena.

Si posteriormente queremos cambiar el texto del label, empleamos la función setString sobre el label. Agreguemos este código después del bloque que acabamos de ver:

this.helloLabel.setString("Hola mundo!");

Al recargar el juego veremos el cambio del label.

Para finalizar esta sección, agreguemos un label con bitmapfont, después del bloque que agregamos, escribamos este código:

this.helloBMLabel = cc.LabelBMFont.create("Chao", s_nombresText);
this.helloBMLabel.setAnchorPoint(cc.p(0, 0.5));
this.helloBMLabel.setPosition(cc.p(30, size.height/2));
this.addChild(this.helloBMLabel);

Al recargar el juego veremos estos cambios:

helloworld4

Las fuentes bitmap no son tan flexibles como las fuentes TrueType. Al escalar este tipo de fuentes se verán distorsionadas, por lo que hay que crear bitmaps para cada tamaño de la fuente. Además, se deben incluir todos los caracteres que vayamos a emplear, incluyendo el espacio, o de otra manera el juego no correrá bien al no poder ubicar el caracter que necesita.

Finalizando todo

Acabamos de ver algunos tipos básicos de nodos, textos, imágenes y botones, y cómo se agregan al escenario. En la próxima parte de este tutorial veremos con más detalle cómo puede el jugador controlar el juego, y más adelante cómo se trabaja el cambio entre escenas.

Avatar de la mujer cortesía de NatashaHaggard en OpenGameArt – http://opengameart.org/content/female-avatar

Contenido del botón cortesía de Buch en OpenGameArt – http://opengameart.org/content/horrific-button

Navegar la seriePost anterior en la seriePróximo post en la serie

10 comentarios en «Desarrollando videojuegos con cocos2d-html5 (4): Comenzando un proyecto»

  1. ¡Buena guía!

    Dejo de regalo un ejemplo apra crear checkbox (llamados «menuitemtoggle» en cocos2d) ya que originalmente encontré esta página por error buscando eso:

    var chkAccionesPasanTurno = cc.MenuItemToggle.create(
    //s_botonNormal, <- esto es incorrecto, deben parse menuitems ya construídos (ver debajo)
    //s_botonSeleccionado, <- esto es incorrecto, deben parse menuitems ya construídos (ver debajo)
    cc.MenuItemImage.create(s_botonNormal),
    cc.MenuItemImage.create(s_botonSeleccionado),
    function(){
    //Aquí realizar acciones, como cambiar el valor de un booleano global que tengamos definido en algún lugar, comenzar/detener la reproducción de música, etc
    },
    this
    );

    1. ¿Ese «nombres.txt» cómo lo generaste? Si hay un error parseando el BMFont puede haber sido un error configurando AngelFont (no siempre es capaz de leerlos con algunas configuraciones)

        1. Hola! encontre la solucion al problema. Al archivo nombres.txt, que contiene las coordenadas de cada letra dentro del nombres_0.png lo tenes que renombrar como nombres.fnt osea de la extension font. Espero que te sirva. Saludos!!!

          1. Hola, he cambiado al extensión a *.fnt, tal como dices pero me sale el siguiente error:

            syntax error nombres.fnt:1

Deja un comentario