Cómo hacer un juego como Fruit Ninja utilizando Box2D y Cocos2D – Parte 1

Allen Tan

Esta entrada está disponible también en: Chino simplificado, Inglés

Create a Sprite-Cutting Game with Cocos2D!

Create a Sprite-Cutting Game with Cocos2D!

Este es un post de Tan Allen miembro del Equipo de Tutoriales iOS, desarrollador iOS y fundador de White Widget.

En este tutorial aprenderás cómo hacer un juego de corte de sprites para el iPhone similar a Fruit Ninja de Halfbrick Studios usando las potentes librerías Cocos2D y Box2D junto con algunas herramientas pre-realizadas.

En la mayoría de los juegos de cortes, cuando se dibuja una línea de corte a través de un sprite, el juego normalmente convierte la imagen del sprite en dos imágenes con el corte siempre por la mitad, sin importar donde usted realmente corta.

Pero este tutorial demostrará una técnica aún mejor. Nuestras frutas podrán ser cortadas varias veces, ¡y se dividirán dinámicamente basándonos exactamente en las líneas de corte!

Como se pueden imaginar, esta es una técnica avanzada, por lo que este tutorial es para desarrolladores Cocos2D y Box2D avanzados. Si usted es nuevo en Cocos2D o Box2D, debe ir primero a través de (al menos) el tutorial de introducción a Cocos2D e introducción a Box2D antes de continuar con este tutorial.

Esta serie de tutoriales se divide en tres partes:

  • En esta primera parte de la serie, sentará las bases para el juego y aprenderá a crear polígonos texturizados.
  • La segunda parte le mostrará cómo cortar y dividir estos polígonos texturizados.
  • La tercera parte le mostrará cómo convertir esto en un juego completo mediante la adición de jugabilidad (gameplay) y efectos.

Me gustaría dar un especial agradecimiento a Rick Smorawski por sentar las bases del proyecto en el que está basado este tutorial. Fue el responsable de portar este demo de cortes basado en flash a Cocos2D, y también por portar CCBlade y PRKit a Cocos2D 2.0.

Sigue leyendo para ver el video de lo que vas a hacer y ¡empezar a aprender algunas técnicas nuevas!

Demostración del juego

Aquí está un vídeo de demostración que les muestra lo que van a hacer en esta serie de tutoriales:

http://www.youtube.com/watch?v=BG4lqnFlXKk

Como ya he mencionado, usted verá que el efecto de corte de fruta es realmente dinámico. El fruto es cortado de forma dinámica en función de donde se realice el corte, y ya que usted puede cortar los objetos en múltiples ocasiones, ¡realmente puedes desmenuzar bien las cosas!

Podrá ver que también podrá implementar un efecto elegante para el rastro del cortado, algunos sistemas de partículas, la lógica de juego, y sonidos para condimentar un poco las cosas.

Hay mucho que cubrir – así que ¡vamos a empezar!

Introducción: Configuración del proyecto

Usted va a utilizar Cocos2D 2.X en este proyecto, así que adelante y descargarlo si no lo ha hecho. Tenga en cuenta que usted es libre de utilizar Cocos2D 1.X en lugar de 2.X. Con 1.X, puede omitir las partes acerca de la conversión de PRKit y CCBlade a Cocos2D 2.X, pero asegúrese de prestar atención a los otros pequeños cambios que realice en esas clases.

Una vez descargado, haga doble clic en el tar para descomprimirlo, a continuación, instale las plantillas con los siguientes comandos en Terminal:

cd ~/Downloads/cocos2d-iphone-2.0-beta
./install-templates.sh -f -u

Abre Xcode y crea un nuevo proyecto con la plantilla “iOSCocos2D v2.xCocos2D iOS with Box2d” y nómbrelo CutCutCut.

Su nuevo proyecto debería verse algo como esto:

Project Start

Lo primero es lo primero – usted debe limpiar la plantilla un poco para llegar a un buen punto de partida.

Abre HelloWorldLayer.h y elimine la siguiente línea:

CCTexture2D *spriteTexture_;// weak ref

Cambia a HelloWorldLayer.mm y realizar los siguientes cambios

// Remove this line from the top
#import "PhysicsSprite.h"
 
// Replace the init method with this
-(id) init
{
    if( (self=[super init])) {
        // enable events
        self.isTouchEnabled = YES;
        self.isAccelerometerEnabled = YES;
        CGSize s = [CCDirector sharedDirector].winSize;
 
        // init physics
        [self initPhysics];
 
        [self scheduleUpdate];
    }
    return self;
}
 
// Remove these two methods
-(void) createMenu {
    //all content
}
-(void) addNewSpriteAtPosition:(CGPoint)p methods {
    //all content
}
 
// Remove this line from ccTouchesEnded
[self addNewSpriteAtPosition: location];

En este punto, usted ha eliminado todas las referencias a PhysicsSprite de HelloWorldLayer, pero no elimine los archivos del proyecto todavía. Más tarde, usted tendrá que copiar un método que contiene PhysicsSprite.mm en otro lugar, así que déjalo por ahora.

Pulsa Command+R para compilar y ejecutar el proyecto, y usted debería ver una pantalla en blanco con un borde verde alrededor de ella:

Clean Slate

El código restante de la plantilla ha establecido la depuración de dibujo de Box2D, que dibuja bordes alrededor de los cuerpos Box2D en la pantalla. ¿Ves las finas líneas verdes dibujadas alrededor de la pantalla? Esas son las paredes generadas por defecto por el método initPhysics que viene con la plantilla.

Echa un vistazo al código restante de la plantilla para asegurarte de que entiende lo que está pasando hasta ahora – se inicializa un mundo Box2D, establece el suelo (fronteras verdes), establece la depuración de dibujo, etc. Este es un muy buen “casi blanco” punto de partida con Box2D, podemos empezar a construir desde aquí.

Kit de recursos

Descargue los recursos para este proyecto y descomprime el archivo.

No agregue todo en el proyecto por el momento, algunos de los archivos son en realidad opcional. Sin embargo mantenga la carpeta a mano, a medida que avanza a través del tutorial, de vez en cuando, voy a pedirle añadir algunos de estos archivos al proyecto.

Esto es lo que encontrará en el interior:

  • Una imagen de fondo y un montón de arte de frutas hechas por Vicki y otras imágenes misceláneas en la carpeta Images
  • La mezcla de sonido para el sonido de fondo hechas usando gomix.it en la carpeta Sounds
  • Los efectos de sonido hechas usando bfxr o descargado desde freesound en la carpeta Sounds
  • Todos los sistemas de partículas creado con Particle Designer en la carpeta Particles
  • Un archivo PLIST generado por PhysicsEditor que contiene información sobre los vértices para las clases Fruit y Bomb en la carpeta Misc
  • Las clases Fruit y Bomb en la carpeta Classes
  • Las versiones de PRKit y CCBlade que va a utilizar en el tutorial en la carpeta Classes
  • Una lista de atribución de los recursos que están bajo la licencia Attribution License en la carpeta Misc

Dibujando Polígonos Texturizados con PRKit

Nuestro objetivo es cortar sprites en varias piezas. Un CCSprite típico contiene una textura y un cuadro límite sin importar que forma tiene la imagen. Esto no es adecuado para nuestro juego ya que conocer las formas reales dentro de las imágenes es un paso crucial para crear sprites que se puedan cortar, lonchar y separar.

Es necesario crear Polígonos Texturizados, que:

  • Creen una correspondencia entre polígono/forma y una imagen (Mapeado de Texturas)
  • Mostrar sólo las partes de la imagen que están dentro de los límites del polígono (Relleno de Textura)

Ni Cocos2D ni Box2D vienen con una clase integrada que se encargue de las funciones personalizadas que desees, y normalmente, esto tomaría algo de triangulación junto con código personalizado de dibujo de OpenGL.

Suena difícil ¿no?

Afortunadamente, todos los cálculos complicados y el código de dibujo necesarios para lograr esto ya han sido escritas por los buenos amigos de Precognitive Research. Crearon una adición a la libreria Cocos2D llamada PRKit, que maneja el mapeado de texturas y relleno.

Para empezar a trabajar en los polígonos texturizados, descarga PRKit, extraerlo, y arrastre la carpeta PRKit a su proyecto. Asegúrese de que “Copy items into destination group’s folder” está marcada y “Create groups for any added folders” está seleccionada.

Tenga en cuenta que PRKit es mantenido por Precognitive Research, por lo que puede actualizarse en un futuro. Para evitar confusiones, nuestro kit de recursos también contiene la versión exacta de PRKit que he usado para hacer este tutorial.

Su proyecto ahora debe incluir los siguientes archivos:

PRKit Yey!

Compila y ejecuta, y se encontrará con algunos errores:

PRKit Needs to be Converted

Los errores que aparezcen debido a que PRKit fue hecho para Cocos2D 1.X, que utiliza OpenGL ES 1.1, mientras que usted está utilizando Cocos2D 2.X, que utiliza OpenGL ES 2.0, y hay diferencias significativas entre los dos.

Para solucionar este problema, abra PRFilledPolygon.m y haga los siguientes cambios:

// Add inside the initWithPoints: andTexture: usingTriangulator: method
self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionTexture];
 
// Replace the calculateTextureCoordinates method with this
-(void) calculateTextureCoordinates {
    for (int j = 0; j < areaTrianglePointCount; j++) {
        textureCoordinates[j] = ccpMult(areaTrianglePoints[j],1.0f/texture.pixelsWide*CC_CONTENT_SCALE_FACTOR());
        textureCoordinates[j].y = 1 - textureCoordinates[j].y;
    }
}
 
// Replace the draw method with this
-(void) draw{
    CC_NODE_DRAW_SETUP();
 
    ccGLBindTexture2D( self.texture.name );
 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 
    ccGLBlendFunc( blendFunc.src, blendFunc.dst);
 
    ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position | kCCVertexAttribFlag_TexCoords );
 
    glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, areaTrianglePoints);
    glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, textureCoordinates);
 
    glDrawArrays(GL_TRIANGLES, 0, areaTrianglePointCount);
}

Vamos a repasar estos cambios poco a poco.

En primer lugar, en Cocos2D cada CCNode tiene un programa shader (shader program) de OpenGL ES 2.0 unido a él. Para dibujar el PRFilledPolygon, es necesario utilizar el shader “Position/Texture” incorporado, que se asignó en el método init.

Después, usted necesita establecer las coordenadas correctas de la textura para cada punto en el polígono. Para ello, hay que hacer dos cambios al método calculateTextureCoordinates:

  • Scale: Dado que esta clase hace sus propios cálculos en las coordenadas de sus texturas, no maneja automáticamente la pantalla retina. Para solucionar este problema, debe multiplicar texture.pixelsWide por CC_CONTENT_SCALE_FACTOR – un valor multiplicador conveniente proporcionado por Cocos2D para convertir valores entre normales y equivalentes a retina.
  • Flip Y: Por alguna razón, PRFIlledPolygon dibuja texturas al revés, por lo que simplemente volteamos el valor de y aquí.

Por último, el código de dibujo se actualiza para OpenGL ES 2.0, de una manera similar a cómo el dibujado de CCSprite cambió de Cocos2D 1.X a Cocos2D 2.X:

  • Comienza por llamar a CC_NODE_DRAW_SETUP() para preparar el nodo para el dibujado.
  • Las llamadas a glDisableClientState() y glEnableClientState() están obsoletas y se descartan.
  • Los comandos glVertexPointer() y glTexCoordPointer() se sustituyen por glVertexAttribPointer(), que ahora acepta la posición del vértice o coordenada de la textura como su primera opción.
  • La configuración de glTexEnvf(), que fue responsable de repetir el sprite en caso de que el polígono fuese más grande que la textura, se sustituye por las llamadas a glTexParameteri().

Si está confundido por todo esto, es posible que desee revisar nuestros tutoriales OpenGL ES 2.0 for iPhone y Custom Cocos2D 2.X Shaders para obtener más información general. Pero no tiene que preocuparse demasiado, porque en este momento todo lo que estamos haciendo es portar la clase para trabajar en Cocos2D 2.X :]

Compila y ejecuta, y ¡todos los errores de PRKit deben desaparecer!

Es hora de poner PRKit en acción. Usted hará una sub-clase de PRFilledPolygon de PRKit para hacer una clase base PolygonSprite que dibujará nuestras frutas.

PolygonSprite se basará en PRFilledPolygon para unir un cuerpo Box2D al sprite, y también contendrá otras variables y métodos personalizados para las frutas en nuestra implementación del juego.

Vamos a hacerlo. Pulsa Command+N y crea un nuevo archivo con la plantilla iOSCocos2D v2.xCCNode Class. Que sea una subclase de PRFilledPolygon y nómbrala PolygonSprite.m.

Cámbiate a PolygonSprite.h y realice los cambios siguientes:

// Add to top of file
#import "Box2D.h"
#import "PRFilledPolygon.h"
#define PTM_RATIO 32
 
// Add inside @interface
b2Body *_body;
BOOL _original;
b2Vec2 _centroid;
 
// Add after the @interface
@property(nonatomic,assign)b2Body *body;
@property(nonatomic,readwrite)BOOL original;
@property(nonatomic,readwrite)b2Vec2 centroid;
 
// Add before the @end
-(id)initWithTexture:(CCTexture2D*)texture body:(b2Body*)body original:(BOOL)original;
-(id)initWithFile:(NSString*)filename body:(b2Body*)body original:(BOOL)original;
+(id)spriteWithFile:(NSString*)filename body:(b2Body*)body original:(BOOL)original;
+(id)spriteWithTexture:(CCTexture2D*)texture body:(b2Body*)body original:(BOOL)original;
-(id)initWithWorld:(b2World*)world;
+(id)spriteWithWorld:(b2World*)world;
-(b2Body*)createBodyForWorld:(b2World*)world position:(b2Vec2)position rotation:(float)rotation vertices:(b2Vec2*)vertices vertexCount:(int32)count density:(float)density friction:(float)friction restitution:(float)restitution;
-(void)activateCollisions;
-(void)deactivateCollisions;

En el código anterior se declaran las variables base y los métodos necesarios para crear un PolygonSprite. Estos son:

  • body: Es el cuerpo Box2D que se une a nuestro sprite. Es necesario para la simulación de la física.
  • original: Los sprites completos y las rodajas utilizarán la misma clase PolygonSprite, por lo tanto, el diferenciando entre los dos va a ser importante. Si el valor de este es YES, significa que es el objeto sin cortar, u original, de lo contrario, es sólo una parte del todo.
  • centroid: El centro del polígono dentro de la imagen no siempre será el mismo que el centro de la imagen, por lo que es útil almacenar este valor.
  • propiedades: Expone todas las variables a través de propiedades para que otras clases puedan acceder a ellas libremente.
  • init/spriteWith*: Nuestros principales métodos init siguen la misma convención de nombres que Cocos2D.
  • otros métodos: Métodos que crean y trabajar con el cuerpo Box2D adjunto y sus propiedades.
  • PTM_RATIO: Relación entre pixeles y metros. Box2D necesita este valor de conversión porque trabaja con metros en lugar de pixeles.

Cambia rápidamente a PolygonSprite.m y cambiale el nombre a PolygonSprite.mm. Todas las clases que mezclan código Objective-C (Cocos2D) y C++ (Box2D) deben tener una extensión “.mm” para notificar al compilador de la sintaxis mixta.

A continuación, realice los cambios siguientes en PolygonSprite.mm:

// Add inside the @implementation
@synthesize body = _body;
@synthesize original = _original;
@synthesize centroid = _centroid;
 
+(id)spriteWithFile:(NSString *)filename body:(b2Body *)body  original:(BOOL)original
{
    return [[[self alloc]initWithFile:filename body:body original:original] autorelease];
}
 
+(id)spriteWithTexture:(CCTexture2D *)texture body:(b2Body *)body  original:(BOOL)original
{
    return [[[self alloc]initWithTexture:texture body:body original:original] autorelease];
}
 
+(id)spriteWithWorld:(b2World *)world
{
    return [[[self alloc]initWithWorld:world] autorelease];
}
 
-(id)initWithFile:(NSString*)filename body:(b2Body*)body  original:(BOOL)original
{
    NSAssert(filename != nil, @"Invalid filename for sprite");
    CCTexture2D *texture = [[CCTextureCache sharedTextureCache] addImage: filename];
    return [self initWithTexture:texture body:body original:original];
}
 
-(id)initWithTexture:(CCTexture2D*)texture body:(b2Body*)body original:(BOOL)original
{
    // gather all the vertices from our Box2D shape
    b2Fixture *originalFixture = body->GetFixtureList();
    b2PolygonShape *shape = (b2PolygonShape*)originalFixture->GetShape();
    int vertexCount = shape->GetVertexCount();
    NSMutableArray *points = [NSMutableArray arrayWithCapacity:vertexCount];
    for(int i = 0; i < vertexCount; i++) {
        CGPoint p = ccp(shape->GetVertex(i).x * PTM_RATIO, shape->GetVertex(i).y * PTM_RATIO);
        [points addObject:[NSValue valueWithCGPoint:p]];
    }
 
    if ((self = [super initWithPoints:points andTexture:texture]))
    {
        _body = body;
        _body->SetUserData(self);
        _original = original;
        // gets the center of the polygon
        _centroid = self.body->GetLocalCenter();
        // assign an anchor point based on the center
        self.anchorPoint = ccp(_centroid.x * PTM_RATIO / texture.contentSize.width, 
                               _centroid.y * PTM_RATIO / texture.contentSize.height);
        // more init stuff here later when you expand PolygonSprite
    }
    return self;
}
 
-(id)initWithWorld:(b2World *)world
{
    //nothing to do here
    return nil;
}

Similar a Cocos2D, todos los métodos spriteWith* son la contraparte autorelease de los métodos initWith*, mientras que initWithWorld no tiene ningún uso real para esta clase todavía, sino que será utilizada por las subclases de PolygonSprite más adelante.

La mayor parte de los cambios se puede encontrar en los métodos initWithFile e initWithTexture. Para entender el flujo de las cosas, la creación de una fruta se llamará en esta secuencia:

Init Sequence

  • initWithWorld: Está pensado para las subclases de PolygonSprite por lo que no hagas nada excepto retornar nil, y trataremos con él más tarde.
  • initWithFile: Agrega la textura de nuestro archivo y pasa todo a initWithTexture.
  • initWithTexture: Nuestra inicialización principal. PRFilledPolygon necesita una textura y todos los vértices del polígono que él llena. Como el paso anterior ya manejó la parte de la textura, este paso se ocupa de los vértices a partir de recolectarlos desde el cuerpo Box2D del sprite. Después de pasarlos a PRFilledPolygon, procede a inicializar las variables previamente declaradas.
  • initWithPoints: Todo lo que este método realiza está dentro de PRKit y lo bueno es que usted realmente no necesita tocar PRKit nunca más ahora que ha actualizado su código.

Todavía dentro PolygonSprite.mm, agregue los siguientes métodos:

-(void)setPosition:(CGPoint)position
{
    [super setPosition:position];
    _body->SetTransform(b2Vec2(position.x/PTM_RATIO,position.y/PTM_RATIO), _body->GetAngle());
}
 
-(b2Body*)createBodyForWorld:(b2World *)world position:(b2Vec2)position rotation:(float)rotation vertices:(b2Vec2*)vertices vertexCount:(int32)count density:(float)density friction:(float)friction restitution:(float)restitution
{
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.position = position;
    bodyDef.angle = rotation;
    b2Body *body = world->CreateBody(&#038;bodyDef);
 
    b2FixtureDef fixtureDef;
    fixtureDef.density = density;
    fixtureDef.friction = friction;
    fixtureDef.restitution = restitution;
    fixtureDef.filter.categoryBits = 0;
    fixtureDef.filter.maskBits = 0;
 
    b2PolygonShape shape;
    shape.Set(vertices, count);
    fixtureDef.shape = &shape;
    body->CreateFixture(&#038;fixtureDef);
 
    return body;
}
 
-(void)activateCollisions
{
    b2Fixture *fixture = _body->GetFixtureList();
    b2Filter filter = fixture->GetFilterData();
    filter.categoryBits = 0x0001;
    filter.maskBits = 0x0001;
    fixture->SetFilterData(filter);
}
 
-(void)deactivateCollisions
{
    b2Fixture *fixture = _body->GetFixtureList();
    b2Filter filter = fixture->GetFilterData();
    filter.categoryBits = 0;
    filter.maskBits = 0;
    fixture->SetFilterData(filter);
}

En el código anterior, primero sobrecarga el método setPosition de CCNode de modo que cuando se actualiza la posición del sprite, la posición del cuerpo Box2D asociado se actualiza también.

A continuación, crea un método conveniente para crear y definir un cuerpo Box2D. Para crear un cuerpo, es necesario definir una definición de cuerpo, un objeto cuerpo, una forma, y una “fixture definition”. Ningun valor duro está siendo asignados aquí todavía ya que este método será utilizado por las subclases de PolygonSprite más adelante.

La única cosa a tener en cuenta es categoryBits y maskBits. Estos dos se utilizan para filtrar las colisiones entre objetos tales que si un bit categoría de objeto coincide con un bit de la máscara de otro objeto, y viceversa, habrá una colisión entre estos dos objetos. Se establecen a 0 en primer lugar porque no se desea que ocurran colisiones cuando los objetos están siendo inicializados por primera vez.

Por último, se definen dos métodos que simplemente permiten reemplazar categoryBits y maskBits de modo que usted puede activar y desactivar las colisiones de nuestros PolygonSprites.

Hay una cosa más que añadir a PolygonSprite.mm:

-(CGAffineTransform) nodeToParentTransform
{
    b2Vec2 pos  = _body->GetPosition();
 
    float x = pos.x * PTM_RATIO;
    float y = pos.y * PTM_RATIO;
 
    if ( !isRelativeAnchorPoint_ ) {
        x += anchorPointInPoints_.x;
        y += anchorPointInPoints_.y;
    }
 
    // Make matrix
    float radians = _body->GetAngle();
    float c = cosf(radians);
    float s = sinf(radians);
 
    if( ! CGPointEqualToPoint(anchorPointInPoints_, CGPointZero) ){
        x += c*-anchorPointInPoints_.x+ -s*-anchorPointInPoints_.y;
        y += s*-anchorPointInPoints_.x+ c*-anchorPointInPoints_.y;
    }
 
    // Rot, Translate Matrix
    transform_ = CGAffineTransformMake( c,  s,
                                       -s,c,
                                       x,y );
 
    return transform_;
}

¿Recuerdas cuando dije que necesitabas algo de PhysicsSprite? Bueno, esto es. Todo lo que esto hace es para asegurarse de que nuestra forma Box2D y nuestro sprite se encuentran en la misma posición cuando se mueven. Está preparado para nosotros por Cocos2D y eso hace que sea aún más impresionante.

Después de copiar el código anterior, puedes eliminar tanto PhysicsSprite.h y PhysicsSprite.mm del proyecto debido a que se han eliminado por completo su utilidad.

Compilar y ejecuta, y todo debería estar bien. Ya hemos acabado con PolygonSprite por ahora.

Planificando nuestras frutas

Antes de crear clases para nuestros frutos, es necesario tener claro las reglas que las imágenes y sus formas deben seguir. Como usted estará mapeando las texturas a polígonos Box2D, también debe cumplir con las limitaciones de los polígonos de Box2D. Es necesario tener en cuenta dos cosas:

  • Los polígonos deben ser convexo, lo que significa que ningún ángulo interior es mayor que 180 grados.
  • Los polígonos no debe exceder de 8 vértices.

En realidad se puede evitar esta limitación si se permite que cada cuerpo pueda contener múltiples formas. Box2D puede manejar formas cóncavas si se utiliza un método de triangulación y creamos las formas cóncavas a partir de varios triángulos, pero esto está fuera del alcance de este tutorial.

Para simplificar las cosas, en este tutorial usted tendrá 1 Cuerpo es a 1 Forma como regla.

Nota: PhysicsEditor, la herramienta a la cual le daremos cobertura más adelante en este tutorial, contiene código para triangular automáticamente, los polígonos que dibujas, en un conjunto de formas convexas. Sin embargo, como he dicho que estamos tratando de mantener las cosas simples, vamos a asegurarnos de sacar nuestras formas convexas, de modo que sólo tengamos una forma por cada cuerpo.

Echa un vistazo a estas dos frutas:

Cóncavo vs Convexo

Utilizar el plátano no es una buena idea porque es naturalmente cóncavo. La sandía por otro lado es muy bueno porque se puede definir un polígono convexo que se parezca mucho a su forma.

Si tuvieras que definir formas poligonales que sigan las reglas de Box2D para las dos frutas, más o menos terminarías con estos:

Box2D Shape Outline

El polígono de la sandía se ajusta a la imagen a la perfección, mientras que el polígono de plátano tiene un gran espacio vacío en el que la imagen se curva hacia adentro. Box2D tratará ese espacio como parte de nuestro objeto, y esto podría hacer que el plátano parezca poco real cuando choque con otro objeto, o cuando se corta.

Esto no significa que no puede utilizar el plátano, sino que simplemente no es recomendable utilizarlo. De hecho, el juego que va a crear en este tutorial utilizará este mismo plátano.

Creación de la primera fruta

Es el momento de crear el primer fruto: la sandía (al menos una sección de la misma).

Pensando en el flujo del proceso de inicialización de nuestro PolygonSprite, sabes que initWithTexture espera un cuerpo Box2D, pero el paso anterior, initWithFile, no proporciona esto.

La razón de esto es que es necesario que se cree y defina el cuerpo individualmente por fruto, por lo que tendrá que ser el primer paso, initWithWorld, el que crea el cuerpo y establece otros valores específicos de cada fruta.

Para crear nuestro cuerpo Box2D, primero debe conocer los vértices de la forma del polígono que desea crear. Hay diferentes maneras de hacer esto, pero para este tutorial, usted va a utilizar una ingeniosa herramienta llamada PhysicsEditor. Esta herramienta está llena de características, pero sólo se va a utilizar para guiarnos en obtener las coordenadas de los vértices de nuestro polígono.

Si no la tiene, descargue PhysicsEditor, instalarla, y ejecútela. Usted obtendrá un proyecto en blanco con 3 paneles/columnas.

Trabajar con PhysicsEditor es bastante sencillo. A la izquierda, se ponen todas las imágenes con las que desea trabajar. En el medio, usted define visualmente un polígono para su imagen. A la derecha, usted establece los parámetros para el cuerpo.

Physics Editor!

Agarre watermelon.png de la carpeta Images de la carpeta de recursos y arrástrelo hasta el panel izquierdo. Ahora debería ver la sandía en el panel central.

Aumenta la ampliación, que se encuentra en la parte inferior de este panel, a un nivel cómodo, y pulse sobre el botón del pentágono, en la parte superior de este panel, para crear una forma de un polígono de 3 lados.

Haga clic derecho en el polígono y elija la opción “Add Vertex” hasta que tenga 5-8 vértices en total. Mueva los vértices hacia los bordes de la sandía, mientras se asegura de dos cosas:

  • El polígono que está creando es convexo.
  • Todos los píxeles de la sandía se encuentran dentro del polígono.

Nota: Otro método abreviado para dibujar las formas es utilizando la herramienta de varita mágica de PhysicsEditor. Sólo tienes que configurar la tolerancia alta (5-8) a fin de que usted termine con unos 5-8 puntos, y ajustar los puntos a partir de ahí.

Añada todas las otras frutas y la imagen de la bomba desde la carpeta Images de la carpeta de recursos y haga lo mismo por ellos.

Usted debe definir las formas para las siguientes imágenes:

  • banana.png
  • bomb.png
  • grapes.png
  • pineapple.png
  • strawberry.png
  • watermelon.png

Cuando haya terminado, en la esquina superior derecha, cambie el valor de Exporter to “Box2D generic (PLIST)”, y debe obtener algo como esto:

Defina las formas facilmente con PhysicsEditor!

Haga clic en “Publish” o “Publish As” para exportar el archivo PLIST que contiene la información de los vértices. Guarde el archivo como fruits.plist.

Como ejemplo, el archivo fruits.plist que ha utilizado para este tutorial está dentro de la carpeta Misc de la carpeta de recursos.

Usted sólo quiere mirar la información contenida en el archivo PLIST, así que no añada este archivo a su proyecto, sino que sólo abra fruits.plist usando Xcode para ver su contenido de manera organizada.

Haga clic en el icono de triángulo que se encuentra al lado de “bodies” para ampliar esta sección y podrás ver la lista de las imágenes para las cuales ya has definido forma. Es necesario profundizar hasta el nivel más profundo para ver los vértices del polígono de la sandía de este modo:

El PLIST de PhysicsEditor

Expande watermelon/fixtures/Item 0/polygons y ahora debería ver otro Item 0 de tipo Array debajo de polygons. Este ultimo arreglo es su forma. Si ha definido correctamente una forma convexa con 8 o menos vértices, sólo se debe ver una arreglo debajo de polygons.

Si aparece más de uno, por ejemplo, Item 0 Array, Item 1 Array, etc, significa que PhysicsEditor creo una forma compleja porque usted definió muchos vértices, o formó un polígono cóncavo. Si esto ocurre, vuelva a PhysicsEditor y arregle su forma.

A continuación, expanda el Item 0 de tipo Array para ver la lista final de los elementos. Estos son sus vértices, y el valor que ve en el lado derecho con este formato {número, número} son sus coordenadas Y y X para cada vértice.

Ahora que tiene los valores exactos de los vértices de su polígono, se puede proceder con la creación de la clase Sandía.

En Xcode, cree un nuevo archivo con la plantilla iOSCocos2D v2.xCCNode Class. Hágala subclase de PolygonSprite y el nómbrela Watermelon. Abra Watermelon.h y realice los siguientes cambios:

// Add to top of file
#import "PolygonSprite.h"

Cámbiate a Watermelon.m, cambie el nombre a Watermelon.mm y agregue el siguiente método init:

// Add inside the @implementation
-(id)initWithWorld:(b2World *)world
{
    int32 count = 7;
    NSString *file = @"watermelon.png";
    b2Vec2 vertices[] = {
        b2Vec2(5.0/PTM_RATIO,15.0/PTM_RATIO),
        b2Vec2(18.0/PTM_RATIO,7.0/PTM_RATIO),
        b2Vec2(32.0/PTM_RATIO,5.0/PTM_RATIO),
        b2Vec2(48.0/PTM_RATIO,7.0/PTM_RATIO),
        b2Vec2(60.0/PTM_RATIO,14.0/PTM_RATIO),
        b2Vec2(34.0/PTM_RATIO,59.0/PTM_RATIO),
        b2Vec2(28.0/PTM_RATIO,59.0/PTM_RATIO)
    };
    CGSize screen = [[CCDirector sharedDirector] winSize];
 
    b2Body *body = [self createBodyForWorld:world position:b2Vec2(screen.width/2/PTM_RATIO,screen.height/2/PTM_RATIO) rotation:0 vertices:vertices vertexCount:count density:5.0 friction:0.2 restitution:0.2];
 
    if ((self = [super initWithFile:file body:body original:YES]))
    {
        // We will initialize more values for the fruit here later
    }
    return self;
}

En el código anterior, primero se define cuantos vértices hay, que en este caso es 7. Luego, se crea un arreglo de vértices que contiene todas las coordenadas que acaba de ver en el PLIST. Se utiliza esta información para crear un cuerpo utilizando el método conveniente que ha definido en PolygonSprite.

Se adiciona un poco de fricción para que las formas no se deslicen sin fin, y también un poco de restitución para que las formas no se detengan cuando reboten unas contra otras.

Por último, se crea el objeto mediante una llamada a la inicialización de la superclase pasando el nombre del archivo de la imagen, el cuerpo Box2D, y confirma que se trata de una fruta original.

Necesitas las imágenes de la sandía del kit de recursos, por lo que ahora sería un buen momento para simplemente añadir todos los recursos gráficos que necesita para el resto del tutorial.

En el panel Project Navigator, haga clic en Resources y seleccione “Add Files to CutCutCut”. Agregue la carpeta Images del kit de recursos al proyecto. Asegúrese de que “Copy items into destination group’s folder” está marcada y “Create groups for any added folders” está seleccionada.

Siga los mismos pasos para crear el plátano, uvas, piña, fresa y la bomba.

Sólo se abordó cómo crear el primer fruto paso a paso, ya que es básicamente un proceso simple y de repetición para cada fruta. El kit de recursos contiene clases prefabricadas de frutas y bomba en la carpeta Classes para que usted mire en caso de que todavía necesite orientación, o puede agregarlo todo a su proyecto si desea omitir este paso.

Compile y ejecute, y asegurarse de que todo está bien.

Adicionando una Fruta a la escena

Hasta el momento, nada ha estado sucediendo en la pantalla, y usted está obviamente ansioso por ver los frutos de su trabajo – ¡nunca mejor dicho! :]

Cambia a HelloWorldLayer.h y realice los cambios siguientes:

// Add to top of file
#import "PolygonSprite.h"
 
// Add inside the @interface
CCArray *_cache;
 
// Add after the @interface
@property(nonatomic,retain)CCArray *cache;

Vuelva a HelloWorldLayer.mm y haga los siguientes cambios:

// Add to top of file
#import "Watermelon.h"
 
// Add inside the @implementation
@synthesize cache = _cache;
 
// Add inside the init method, below [self initPhysics]
[self initSprites];
 
// Add inside the dealloc method, before calling [super dealloc]
[_cache release];
_cache = nil;
 
// Add anywhere inside the @implementation and before the @end
 
-(void)initSprites
{
    _cache = [[CCArray alloc] initWithCapacity:53];
 
    // Just create one sprite for now. This whole method will be replaced later.
    PolygonSprite *sprite = [[Watermelon alloc] initWithWorld:world];
    [self addChild:sprite z:1];
    [sprite activateCollisions];
    [_cache addObject:sprite];
}

Declara una arreglo caché, que guardará todas las frutas y las bombas que cree en el futuro. Luego, cree una sandía y adiciónela a la escena. Llama a activateCollisions para que la sandía no pase a través de las paredes.

Compila y ejecuta, debería ver una sandía que cae de la zona centro de la pantalla, y la tierra en la parte inferior, como se muestra a continuación.

El fruto de su trabajo

Usted puede notar que la sandía no está exactamente en el centro. La razón de esto es que se coloca el objeto basado en su cuerpo Box2D, y el origen de nuestro cuerpo Box2D está en la esquina inferior izquierda del objeto. El borde delgado alrededor de la sandía es visible porque el modo de depuración de dibujo se encuentra activado.

¿A dónde ir desde aquí?

He aquí un ejemplo del proyecto con todo el código de este tutorial.

Eso es todo para la parte 1 de la serie. Hasta el momento, tiene un polígono de una sandía con textura que cae a la parte inferior de la pantalla.

Pero en vez de dibujar un sprite rectangular con un espacio transparente como usted suele ver en tutoriales de Box2D, se utiliza PRKit para dibujar sólo las partes de la textura que corresponden a los vértices del cuerpo Box2D. ¡Esto será muy útil muy pronto!

Ahora ya está listo para la Parte 2 del tutorial, ¡donde se agrega la posibilidad de cortar las frutas!

Mientras tanto, si usted tiene alguna pregunta o comentario acerca de esta parte, por favor, ¡únase al foro de discusión de abajo!


This is a post by iOS Tutorial Team Member Allen Tan, an iOS developer and founder at White Widget.



Traducción:


This post was translated to Spanish by iOS Translation Team Member Geykel Moreno, a .NET & iOS developer and appregions.com founder.


Allen Tan

Allen Tan is an iOS Developer and Founder at White Widget, a Philippines-based start-up mobile apps & games development studio that does both contractual and indie work. Right now, Allen’s pretty much devoting all of his time getting the studio off the ground. To find out more, you can follow White Widget’s Facebook page & Twitter for announcements.

You can also check out Allen’s personal LinkedIn profile and Twitter, or reach him for work via email.

Comentarios de los Usuarios

0 Comment

Other Items of Interest

Newsletter Mensual de Ray

Suscríbete para recibir un boletín mensual con mis enlaces favoritos sobre programación, ¡y recibe un tutorial de longitud épica gratis como bonus!

¡Anúnciate con nosotros!

Nuestros libros

Nuestro Equipo

Equipo de totorales

  • Matt Luedke
  • Brian Broom

... 50 en total!

Update Team

... 15 en total!

Equipo Editorial

... 23 en total!

Code Team

  • Orta Therox

... 3 en total!

Equipo de Traducción

  • Myeong Hoon
  • Heejun Han
  • Jesus Guerra

... 33 en total!

Expertos en la Materia

... 4 en total!