Cocos2d-iPhone

Cocos2d: Working with Sprites

Cocos2d is first and foremost a rich graphical API which allows a game developer easy access to a broad range of functionality. In this article, we will take a look at the basic uses of sprites.

In this article by Nathan Burba, author of Cocos2d for iPhone 1 Game Development Cookbook, we will cover the following topics:

Drawing sprites
The most fundamental task in 2D game development is drawing a sprite. Cocos2d provides the user with a lot of flexibility in this area. In this recipe we will cover drawing sprites using 

CCSprite

, spritesheets, 

CCSpriteFrameCache

, and 

CCSpriteBatchNode

. We will also go over mipmapping. In this recipe we see a scene with Alice from Through The Looking Glass.

Posted Image
Getting ready
Please refer to the project RecipeCollection01 for the full working code of this recipe.

How to do it...
Execute the following code:

@implementation Ch1_DrawingSprites
-(CCLayer*) runRecipe {
  /*** Draw a sprite using CCSprite ***/
  CCSprite *tree1 = [CCSprite spriteWithFile:@"tree.png"];

  //Position the sprite using the tree base as a guide (y anchor 
point = 0)
[tree1 setPosition:ccp(20,20)];
  tree1.anchorPoint = ccp(0.5f,0);
  [tree1 setScale:1.5f];
  [self addChild:tree1 z:2 tag:TAG_TREE_SPRITE_1];

  /*** Load a set of spriteframes from a PLIST file and draw one by
name ***/

  //Get the sprite frame cache singleton
  CCSpriteFrameCache *cache = [CCSpriteFrameCache
sharedSpriteFrameCache];

  //Load our scene sprites from a spritesheet
  [cache addSpriteFramesWithFile:@"alice_scene_sheet.plist"];

  //Specify the sprite frame and load it into a CCSprite
  CCSprite *alice = [CCSprite spriteWithSpriteFrameName:@"alice.png"];

  //Generate Mip Maps for the sprite
  [alice.texture generateMipmap];
  ccTexParams texParams = { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_
CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE };
  [alice.texture setTexParameters:&texParams];

  //Set other information.
  [alice setPosition:ccp(120,20)];
  [alice setScale:0.4f];
  alice.anchorPoint = ccp(0.5f,0);

  //Add Alice with a zOrder of 2 so she appears in front of other
sprites
  [self addChild:alice z:2 tag:TAG_ALICE_SPRITE];

  //Make Alice grow and shrink.
  [alice runAction: [CCRepeatForever actionWithAction:
   [CCSequence actions:[CCScaleTo actionWithDuration:4.0f scale
:0.7f], [CCScaleTo actionWithDuration:4.0f scale:0.1f], nil] ] ];

  /*** Draw a sprite CGImageRef ***/
  UIImage *uiImage = [UIImage imageNamed: @"cheshire_cat.png"];
  CGImageRef imageRef = [uiImage CGImage];
  CCSprite *cat = [CCSprite spriteWithCGImage:imageRef key:@
"cheshire_cat.png"];
  [cat setPosition:ccp(250,180)];
  [cat setScale:0.4f];
  [self addChild:cat z:3 tag:TAG_CAT_SPRITE];

  /*** Draw a sprite using CCTexture2D ***/
  CCTexture2D *texture = [[CCTextureCache sharedTextureCache]
addImage:@"tree.png"];
  CCSprite *tree2 = [CCSprite spriteWithTexture:texture];
  [tree2 setPosition:ccp(300,20)];
  tree2.anchorPoint = ccp(0.5f,0);
  [tree2 setScale:2.0f];
  [self addChild:tree2 z:2 tag:TAG_TREE_SPRITE_2];

  /*** Draw a sprite using CCSpriteFrameCache and CCTexture2D ***/
  CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:texture
rect:tree2.textureRect];
  [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFrame:
frame name:@"tree.png"];
  CCSprite *tree3 = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrame
Cache sharedSpriteFrameCache] spriteFrameByName:@"tree.png"]];
  [tree3 setPosition:ccp(400,20)];
  tree3.anchorPoint = ccp(0.5f,0);
  [tree3 setScale:1.25f];
  [self addChild:tree3 z:2 tag:TAG_TREE_SPRITE_3];

  /*** Draw sprites using CCBatchSpriteNode ***/

  //Clouds
  CCSpriteBatchNode *cloudBatch = [CCSpriteBatchNode
batchNodeWithFile:@"cloud_01.png" capacity:10];
  [self addChild:cloudBatch z:1 tag:TAG_CLOUD_BATCH];
  for(int x=0; x   CCSprite *s = [CCSprite spriteWithBatchNode:cloudBatch
rect:CGRectMake(0,0,64,64)];
   [s setOpacity:100];
   [cloudBatch addChild:s];
   [s setPosition:ccp(arc4random()%500-50, arc4random()%150+200)];
  }

  //Middleground Grass
  int capacity = 10;
  CCSpriteBatchNode *grassBatch1 = [CCSpriteBatchNode
batchNodeWithFile:@"grass_01.png" capacity:capacity];
  [self addChild:grassBatch1 z:1 tag:TAG_GRASS_BATCH_1];
  for(int x=0; x   CCSprite *s = [CCSprite spriteWithBatchNode:grassBatch1
rect:CGRectMake(0,0,64,64)];
   [s setOpacity:255];
   [grassBatch1 addChild:s];
   [s setPosition:ccp(arc4random()%500-50, arc4random()%20+70)];
  }

  //Foreground Grass
  CCSpriteBatchNode *grassBatch2 = [CCSpriteBatchNode
batchNodeWithFile:@"grass_01.png" capacity:10];
  [self addChild:grassBatch2 z:3 tag:TAG_GRASS_BATCH_2];
  for(int x=0; x   CCSprite *s = [CCSprite spriteWithBatchNode:grassBatch2
rect:CGRectMake(0,0,64,64)];
   [s setOpacity:255];
   [grassBatch2 addChild:s];
   [s setPosition:ccp(arc4random()%500-50, arc4random()%40-10)];
  }

  /*** Draw colored rectangles using a 1px x 1px white texture ***/

  //Draw the sky using blank.png
  [self drawColoredSpriteAt:ccp(240,190) withRect:CGRectMa
ke(0,0,480,260) withColor:ccc3(150,200,200) withZ:0];

  //Draw the ground using blank.png
  [self drawColoredSpriteAt:ccp(240,30)
withRect:CGRectMake(0,0,480,60) withColor:ccc3(80,50,25) withZ:0];

  return self;
}

-(void) drawColoredSpriteAt:(CGPoint)position withRect:(CGRect)rect
withColor:(ccColor3B)color withZ:(float)z {
  CCSprite *sprite = [CCSprite spriteWithFile:@"blank.png"];
  [sprite setPosition:position];
  [sprite setTextureRect:rect];
  [sprite setColor:color];
  [self addChild:sprite];

  //Set Z Order
  [self reorderChild:sprite z:z];
}

@end

How it works...
This recipe takes us through most of the common ways of drawing sprites:

  • Creating a CCSprite from a file:
    First, we have the simplest way to draw a sprite. This involves using the CCSprite class method as follows:
    +(id)spriteWithFile:(NSString*)filename;

    This is the most straightforward way to initialize a sprite and is adequate for many situations.
  • Other ways to load a sprite from a file:
    After this, we will see examples of CCSprite creation using UIImage/CGImageRefCCTexture2D, and a CCSpriteFrame instantiated using aCCTexture2D object. CGImageRef support allows you to tie Cocos2d into other frameworks and toolsets. CCTexture2D is the underlying mechanism for texture creation.
  • Loading spritesheets using CCSpriteFrameCache:
    Next, we will see the most powerful way to use sprites, the CCSpriteFrameCache class. Introduced in Cocos2d-iPhone v0.99, theCCSpriteFrameCache singleton is a cache of all sprite frames. Using a spritesheet and its associated PLIST file we can load multiple sprites into the cache. From here we can create CCSprite objects with sprites from the cache:
    +(id)spriteWithSpriteFrameName:(NSString*)filename;
  • Mipmapping:
    Mipmapping allows you to scale a texture or to zoom in or out of a scene without aliasing your sprites. When we scale Alice down to a small size, aliasing will inevitably occur. With mipmapping turned on, Cocos2d dynamically generates lower resolution textures to smooth out any pixelation at smaller scales. Go ahead and comment out the following lines:
    [alice.texture generateMipmap];
      ccTexParams texParams = { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR,
    GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE };
      [alice.texture setTexParameters:&texParams];

    Now you should see this pixelation as Alice gets smaller.
  • Drawing many derivative sprites with CCSpriteBatchNode:
    The CCSpriteBatchNode class, added in v0.99.5, introduces an efficient way to draw and re-draw the same sprite over and over again. A batch node is created with the following method:
    CCSpriteBatchNode *cloudBatch = [CCSpriteBatchNode
    batchNodeWithFile:@"cloud_01.png" capacity:10];

    Then, you create as many sprites as you want using the follow code:
    CCSprite *s = [CCSprite spriteWithBatchNode:cloudBatch
    rect:CGRectMake(0,0,64,64)];
      [cloudBatch addChild:s];

    Setting the capacity to the number of sprites you plan to draw tells Cocos2d to allocate that much space. This is yet another tweak for extra efficiency, though it is not absolutely necessary that you do this. In these three examples we draw 10 randomly placed clouds and 60 randomly placed bits of grass.
  • Drawing colored rectangles:
    Finally, we have a fairly simple technique that has a variety of uses. By drawing a sprite with a blank 1px by 1px white texture and then coloring it and setting its textureRect property we can create very useful colored bars:
    CCSprite *sprite = [CCSprite spriteWithFile:@"blank.png"];
    [sprite setTextureRect:CGRectMake(0,0,480,320)];
    [sprite setColor:ccc3(255,128,0)];

    In this example we have used this technique to create very simple ground and sky backgrounds.

Coloring sprites
In the previous recipe we used colored rectangles to draw both the ground and the sky. The ability to set texture color and opacity are simple tools which, if used properly, can create very cool effects. In this recipe we will create a cinematic scene where two samurai face each other with glowing swords.

Posted Image
Getting ready
Please refer to the project 

RecipeCollection01

 for full working code of this recipe. Also, note that some code has been omitted for brevity.

How to do it...
Execute the following code:

#import "CCGradientLayer.h

@implementation Ch1_ColoringSprites

-(CCLayer*) runRecipe {
  [self initButtons];

  //The Fade Scene Sprite
  CCSprite *fadeSprite = [CCSprite spriteWithFile:@"blank.png"];
  [fadeSprite setOpacity:0];
  [fadeSprite setPosition:ccp(240,160)];
  [fadeSprite setTextureRect:CGRectMake(0,0,480,320)];
  [self addChild:fadeSprite z:3 tag:TAG_FADE_SPRITE];

  //Add a gradient below the mountains
//CCGradientDirectionT_B is an enum provided by CCGradientLayer
  CCGradientLayer *gradientLayer = [CCGradientLayer layerWithColor:
ccc4(61,33,62,255) toColor:ccc4(65,89,54,255) withDirection:
CCGradient DirectionT_B width:480 height:100];
  [gradientLayer setPosition:ccp(0,50)];
  [self addChild:gradientLayer z:0 tag:TAG_GROUND_GRADIENT];

  //Add a sinister red glow gradient behind the evil samurai
  CCGradientLayer *redGradient = [CCGradientLayer
layerWithColor:ccc4(0,0,0,0) toColor:ccc4(255,0,0,100) withDirection
:CCGradientDirectionT_B width:200 height:200];
  [redGradient setPosition:ccp(280,60)];
  [redGradient setRotation:-90];
  [self addChild:redGradient z:2 tag:TAG_RED_GRADIENT];

  // Make the swords glow
  [self glowAt:ccp(230,280) withScale:CGSizeMake(3.0f, 11.0f)
withColor:ccc3(0,230,255) withRotation:45.0f withSprite:goodSamurai];
  [self glowAt:ccp(70,280) withScale:CGSizeMake(3.0f, 11.0f)
withColor:ccc3(255,200,2) withRotation:-45.0f withSprite:evilSamurai];

  return self;
}

-(void) initButtons {
  [CCMenuItemFont setFontSize:16];

  //'Fade To Black' button
  CCMenuItemFont* fadeToBlack = [CCMenuItemFont itemFromString:@
"FADE TO BLACK" target:self selector:@selector(fadeToBlackCallback:)];
  CCMenu *fadeToBlackMenu = [CCMenu menuWithItems:fadeToBlack, nil];
   fadeToBlackMenu.position = ccp( 180 , 20 );
   [self addChild:fadeToBlackMenu z:4 tag:TAG_FADE_TO_BLACK];
}

/* Fade the scene to black */
-(void) fadeToBlackCallback:(id)sender {
  CCSprite *fadeSprite = [self getChildByTag:TAG_FADE_SPRITE];
  [fadeSprite stopAllActions];
  [fadeSprite setColor:ccc3(0,0,0)];
  [fadeSprite setOpacity:0.0f];
  [fadeSprite runAction:
  [CCSequence actions:[CCFadeIn actionWithDuration:2.0f], [CCFadeOut
actionWithDuration:2.0f], nil] ];
}

/* Create a glow effect */
-(void) glowAt:(CGPoint)position withScale:(CGSize)size
withColor:(ccColor3B)color withRotation:(float)rotation
withSprite:(CCSprite*)sprite {
  CCSprite *glowSprite = [CCSprite spriteWithFile:@"fire.png"];
  [glowSprite setColor:color];
  [glowSprite setPosition:position];
  [glowSprite setRotation:rotation];
  [glowSprite setBlendFunc: (ccBlendFunc) { GL_ONE, GL_ONE }];
  [glowSprite runAction: [CCRepeatForever actionWithAction:
   [CCSequence actions:[CCScaleTo actionWithDuration:0.9f
scaleX:size.width scaleY:size.height], [CCScaleTo
actionWithDuration:0.9f scaleX:size.width*0.75f scaleY:size.
height*0.75f], nil] ] ];
  [glowSprite runAction: [CCRepeatForever actionWithAction:
   [CCSequence actions:[CCFadeTo actionWithDuration:0.9f
opacity:150], [CCFadeTo actionWithDuration:0.9f opacity:255], nil] ]
];
  [sprite addChild:glowSprite];
}

@end

How it works...
This recipe shows a number of color based techniques.

  • Setting sprite color:
    The simplest use of color involves setting the color of a sprite using the following method:
    -(void) setColor:(ccColor3B)color;

    Setting sprite color effectively reduces the color you can display but it allows some programmatic flexibility in drawing. In this recipe we usesetColor for a number of things, including drawing a blue sky, a yellow sun, black "dramatic movie bars", and more.
    ccColor3B is a C struct which contains three GLubyte variables. Use the following helper macro to create ccColor3B structures:
    ccColor3B ccc3(const GLubyte r, const GLubyte g, const GLubyte
    b);

    Cocos2d also specifies a number of pre-defined colors as constants. These include the following:
    ccWHITE, ccYELLOW, ccBLUE, ccGREEN, ccRED,
    ccMAGENTA, ccBLACK, ccORANGE, ccGRAY
  • Fading to a color:
    To fade a scene to a specific color we use the blank.png technique we went over in the last recipe. We first draw a sprite as large as the screen, then color the sprite to the color we want to fade to, and then finally run a CCFadeIn action on the sprite to fade to that color:
    [fadeSprite setColor:ccc3(255,255,255)];
    [fadeSprite setOpacity:0.0f];
    [fadeSprite runAction: [CCFadeIn actionWithDuration:2.0f] ];
  • Using CCGradientLayer:
    Using the CCGradientLayer class we can programmatically create gradients. To make the mountains in the background fade into the ground the two samurai are standing on we created a gradient using this method:
      CCGradientLayer *gradientLayer = [CCGradientLayer layerWithColor
    :ccc4(61,33,62,255) toColor:ccc4(65,89,54,255) withDirection:CCGra
    dientDirectionT_B width:480 height:100];
      [gradientLayer setPosition:ccp(0,50)];
      [self addChild:gradientLayer z:0 tag:TAG_GROUND_GRADIENT];

    Because CCGradientLayer lets you control opacity as well as color, it has many uses. As you can see there is also a sinister red glow behind the evil samurai.
  • Making a sprite glow: To make the swords in the demo glow we use subtle color manipulation, additive blending and fading and scaling actions. First we load the fire.png sprite supplied by Cocos2d. By changing its X and Y scale independently we can make it thinner or fatter. Once you have the desired scale ratio (in this demo we use x:y 3:11 because the sword is so thin) you can constantly scale and fade the sprite in and out to give some life to the effect. You also need to set the blend function to { GL_ONE, GL_ONE } for additive blending. Finally this effect sprite is added to the actual sprite to make it seem like it glows.
    CCSprite *glowSprite = [CCSprite spriteWithFile:@"fire.png"];
      [glowSprite setColor:color];
      [glowSprite setPosition:position];
      [glowSprite setRotation:rotation];
      [glowSprite setBlendFunc: (ccBlendFunc) { GL_ONE, GL_ONE }];
      [glowSprite runAction: [CCRepeatForever actionWithAction:
      [CCSequence actions:[CCScaleTo actionWithDuration:0.9f
    scaleX:size.width scaleY:size.height], [CCScaleTo
    actionWithDuration:0.9f scaleX:size.width*0.75f scaleY:size.
    height*0.75f], nil] ] ];
      [glowSprite runAction: [CCRepeatForever actionWithAction:
      [CCSequence actions:[CCFadeTo actionWithDuration:0.9f
    opacity:150], [CCFadeTo actionWithDuration:0.9f opacity:255], nil]
    ] ];
      [sprite addChild:glowSprite];

Animating sprites
Now it is time to add some animation to our sprites. One thing that should be stressed about animation is that it is only as complicated as you make it. In this recipe we will use very simple animation to create a compelling effect. We will create a scene where bats fly around a creepy looking castle. I've also added a cool lightning effect based on the technique used to make the swords glow in the previous recipe.

Posted Image
Getting ready
Please refer to the project 

RecipeCollection01

 for full working code of this recipe. Also note that some code has been omitted for brevity.

How to do it...
Execute the following code:

//SimpleAnimObject.h
@interface SimpleAnimObject : CCSprite {
  int animationType;
  CGPoint velocity;
}

@interface Ch1_AnimatingSprites {
  NSMutableArray *bats;
  CCAnimation *batFlyUp;
  CCAnimation *batGlideDown;
  CCSprite *lightningBolt;
  CCSprite *lightningGlow;
  int lightningRemoveCount;
}

-(CCLayer*) runRecipe {
  //Add our PLIST to the SpriteFrameCache
  [[CCSpriteFrameCache sharedSpriteFrameCache] 
addSpriteFramesWithFile:@"simple_bat.plist"];

  //Add a lightning bolt
  lightningBolt = [CCSprite spriteWithFile:@"lightning_bolt.png"];
  [lightningBolt setPosition:ccp(240,160)];
  [lightningBolt setOpacity:64];
  [lightningBolt retain];

  //Add a sprite to make it light up other areas.
  lightningGlow = [CCSprite spriteWithFile:@"lightning_glow.png"];
  [lightningGlow setColor:ccc3(255,255,0)];
  [lightningGlow setPosition:ccp(240,160)];
  [lightningGlow setOpacity:100];
  [lightningGlow setBlendFunc: (ccBlendFunc) { GL_ONE, GL_ONE }];
  [lightningBolt addChild:lightningGlow];

  //Set a counter for lightning duration randomization
  lightningRemoveCount = 0;

  //Bats Array Initialization
  bats = [[NSMutableArray alloc] init];

  //Add bats using a batch node.
  CCSpriteBatchNode *batch1 = [CCSpriteBatchNode
batchNodeWithFile:@"simple_bat.png" capacity:10];
  [self addChild:batch1 z:2 tag:TAG_BATS];

  //Make them start flying up.
  for(int x=0; x   //Create SimpleAnimObject of bat
   SimpleAnimObject *bat = [SimpleAnimObject
spriteWithBatchNode:batch1 rect:CGRectMake(0,0,48,48)];
  [batch1 addChild:bat];
  [bat setPosition:ccp(arc4random()%400+40, arc4random()%150+150)];

  //Make the bat fly up. Get the animation delay (flappingSpeed).
  float flappingSpeed = [self makeBatFlyUp:bat];

  //Base y velocity on flappingSpeed.
  bat.velocity = ccp((arc4random()%1000)/500 + 0.2f, 0.1f/
flappingSpeed);

  //Add a pointer to this bat object to the NSMutableArray
  [bats addObject:[NSValue valueWithPointer:bat]];
  [bat retain];

  //Set the bat's direction based on x velocity.
  if(bat.velocity.x > 0){
   bat.flipX = YES;
  }
 }

  //Schedule physics updates
  [self schedule:@selector(step:)];

  return self;
}

-(float)makeBatFlyUp:(SimpleAnimObject*)bat {
  CCSpriteFrameCache * cache = [CCSpriteFrameCache
sharedSpriteFrameCache];

  //Randomize animation speed.
  float delay = (float)(arc4random()%5+5)/80;
  CCAnimation *animation = [[CCAnimation alloc] initWithName:@
"simply_bat_fly" delay:delay];

  //Randomize animation frame order.
  int num = arc4random()%4+1;
  for(int i=1; i   [animation addFrame:[cache spriteFrameByName:[NSString
stringWithFormat:@"simple_bat_0%i.png",num]]];
   num++;
   if(num > 4){ num = 1; }
  }

  //Stop any running animations and apply this one.
  [bat stopAllActions];
  [bat runAction:[CCRepeatForever actionWithAction: [CCAnimate 
actionWithAnimation:animation]]];

  //Keep track of which animation is running.
  bat.animationType = BAT_FLYING_UP;

  return delay; //We return how fast the bat is flapping.
}

-(void)makeBatGlideDown:(SimpleAnimObject*)bat {
  CCSpriteFrameCache * cache = [CCSpriteFrameCache
sharedSpriteFrameCache];

  //Apply a simple single frame gliding animation.
  CCAnimation *animation = [[CCAnimation alloc] initWithName:@
"simple_bat_glide" delay:100.0f];
 [animation addFrame:[cache spriteFrameByName:@"simple_bat_01.png"]];

  //Stop any running animations and apply this one.
  [bat stopAllActions];
  [bat runAction:[CCRepeatForever actionWithAction: [CCAnimate 
actionWithAnimation:animation]]];

  //Keep track of which animation is running.
  bat.animationType = BAT_GLIDING_DOWN;
}

-(void)step:(ccTime)delta {
  CGSize s = [[CCDirector sharedDirector] winSize];

for(id key in bats){
  //Get SimpleAnimObject out of NSArray of NSValue objects.
  SimpleAnimObject *bat = [key pointerValue];

  //Make sure bats don't fly off the screen
  if(bat.position.x > s.width){
   bat.velocity = ccp(-bat.velocity.x, bat.velocity.y);
   bat.flipX = NO;
  }else if(bat.position.x	bat.velocity = ccp(-bat.velocity.x, bat.velocity.y);
   bat.flipX = YES;
  }else if(bat.position.y > s.height){
   bat.velocity = ccp(bat.velocity.x, -bat.velocity.y);
   [self makeBatGlideDown:bat];
  }else if(bat.position.y	bat.velocity = ccp(bat.velocity.x, -bat.velocity.y);
   [self makeBatFlyUp:bat];
  }

  //Randomly make them fly back up
  if(arc4random()%100 == 7){
   if(bat.animationType == BAT_GLIDING_DOWN){ [self
makeBatFlyUp:bat]; bat.velocity = ccp(bat.velocity.x, -bat.
velocity.y); }
   else if(bat.animationType == BAT_FLYING_UP){ [self
makeBatGlideDown:bat]; bat.velocity = ccp(bat.velocity.x, -bat.
velocity.y); }
  }

  //Update bat position based on direction
  bat.position = ccp(bat.position.x + bat.velocity.x, bat.position.y
+ bat.velocity.y);
  }

  //Randomly make lightning strike
  if(arc4random()%70 == 7){
  if(lightningRemoveCount	[self addChild:lightningBolt z:1 tag:TAG_LIGHTNING_BOLT];
   lightningRemoveCount = arc4random()%5+5;
   }
  }

  //Count down
  lightningRemoveCount -= 1;

  //Clean up any old lightning bolts
  if(lightningRemoveCount == 0){
   [self removeChildByTag:TAG_LIGHTNING_BOLT cleanup:NO];
  }
}

@end

How it works...
This recipe shows us how to structure animation based classes through the use of 

SimpleAnimObject

:

  • Animated object class structure:
    When switching from one animation to another it is often important to keep track of what state the animated object is in. In our example we useSimpleAnimObject, which keeps an arbitrary animationType variable. We also maintain a velocity variable that has a Y scalar value that is inversely proportional to the animation frame delay:
    @interface SimpleAnimObject : CCSprite {
      int animationType;
      CGPoint velocity;
    }

    Depending on how in-depth you want your animation system to be you should maintain more information such as, for example, a pointer to the running CCAnimation instance, frame information, and physical bodies.

There's more...
As you get more involved with Cocos2d game development you will become more and more tempted to use asynchronous actions for gameplay logic and AI. Derived from the 

CCAction

 class, these actions can be used for everything from moving a 

CCNode

 using 

CCMoveBy

 to animating a 

CCSprite

using 

CCAnimate

. When an action is run, an asynchronous timing mechanism is maintained in the background. First time game programmers often over-rely on this feature. The extra overhead required by this technique can multiply quickly when multiple actions are being run. In the following example we have used a simple integer timer that allows us to regulate how long lightning lasts onscreen:

//Randomly make lightning strike
if(arc4random()%70 == 7){
 if(lightningRemoveCount   [self addChild:lightningBolt z:1 tag:TAG_LIGHTNING_BOLT];
  lightningRemoveCount = arc4random()%5+5;
 }
}

//Count down
lightningRemoveCount -= 1;

//Clean up any old lightning bolts
if(lightningRemoveCount == 0){
 [self removeChildByTag:TAG_LIGHTNING_BOLT cleanup:NO];
}

Synchronous timers like the one shown in the preceding code snippet are often, but not always, preferable to asynchronous actions. Keep this in mind as your games grow in size and scope.

Summary
In this article we took a look at the basic uses of sprites.

Cocos2d: Working with Sprites

原文地址:原文

译者:晋文格墨

注:译者水平有限,欢迎大家批评斧正。

Cocos2d是一个具有丰富的图形处理API,它可以使游戏开发者可以轻松地使用广泛的功能。本文将讨论一下sprites的基本用法。

绘制sprites

在2D游戏开发中最基本的任务就是绘制一个sprite。在这个领域里,cocosd给用户提供了很多的灵活性。在本小节里,我们将介绍如何使用CCSprite, spritesheets, CCSpriteFrameCache, 和 CCSpriteBatchNode来绘制sprite。我们也会再次熟悉一下mipmaping。本节中我们会看到《愛麗絲鏡中奇遇》中的一个场景。



准备

请参考此工程RecipeCollection01,它包含了本节的所有代码。

继续....

执行如下代码:

@implementation Ch1_DrawingSprites
-(CCLayer*) runRecipe {
/*** Draw a sprite using CCSprite ***/
CCSprite *tree1 = [CCSprite spriteWithFile:@"tree.png"];


//Position the sprite using the tree base as a guide (y anchor
point = 0)
[tree1 setPosition:ccp(20,20)];
tree1.anchorPoint = ccp(0.5f,0);
[tree1 setScale:1.5f];
[self addChild:tree1 z:2 tag:TAG_TREE_SPRITE_1];

/*** Load a set of spriteframes from a PLIST file and draw one by
name ***/

//Get the sprite frame cache singleton
CCSpriteFrameCache *cache = [CCSpriteFrameCache
sharedSpriteFrameCache];

//Load our scene sprites from a spritesheet
[cache addSpriteFramesWithFile:@"alice_scene_sheet.plist"];

//Specify the sprite frame and load it into a CCSprite
CCSprite *alice = [CCSprite spriteWithSpriteFrameName:@"alice.png"];

//Generate Mip Maps for the sprite
[alice.texture generateMipmap];
ccTexParams texParams = { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_
CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE };
[alice.texture setTexParameters:&texParams];

//Set other information.
[alice setPosition:ccp(120,20)];
[alice setScale:0.4f];
alice.anchorPoint = ccp(0.5f,0);

//Add Alice with a zOrder of 2 so she appears in front of other
sprites
[self addChild:alice z:2 tag:TAG_ALICE_SPRITE];

//Make Alice grow and shrink.
[alice runAction: [CCRepeatForever actionWithAction:
[CCSequence actions:[CCScaleTo actionWithDuration:4.0f scale
:0.7f], [CCScaleTo actionWithDuration:4.0f scale:0.1f], nil] ] ];

/*** Draw a sprite CGImageRef ***/
UIImage *uiImage = [UIImage imageNamed: @"cheshire_cat.png"];
CGImageRef imageRef = [uiImage CGImage];
CCSprite *cat = [CCSprite spriteWithCGImage:imageRef key:@
"cheshire_cat.png"];
[cat setPosition:ccp(250,180)];
[cat setScale:0.4f];
[self addChild:cat z:3 tag:TAG_CAT_SPRITE];

/*** Draw a sprite using CCTexture2D ***/
CCTexture2D *texture = [[CCTextureCache sharedTextureCache]
addImage:@"tree.png"];
CCSprite *tree2 = [CCSprite spriteWithTexture:texture];
[tree2 setPosition:ccp(300,20)];
tree2.anchorPoint = ccp(0.5f,0);
[tree2 setScale:2.0f];
[self addChild:tree2 z:2 tag:TAG_TREE_SPRITE_2];

/*** Draw a sprite using CCSpriteFrameCache and CCTexture2D ***/
CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:texture
rect:tree2.textureRect];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFrame:
frame name:@"tree.png"];
CCSprite *tree3 = [CCSprite spriteWithSpriteFrame:[[CCSpriteFrame
Cache sharedSpriteFrameCache] spriteFrameByName:@"tree.png"]];
[tree3 setPosition:ccp(400,20)];
tree3.anchorPoint = ccp(0.5f,0);
[tree3 setScale:1.25f];
[self addChild:tree3 z:2 tag:TAG_TREE_SPRITE_3];

/*** Draw sprites using CCBatchSpriteNode ***/

//Clouds
CCSpriteBatchNode *cloudBatch = [CCSpriteBatchNode
batchNodeWithFile:@"cloud_01.png" capacity:10];
[self addChild:cloudBatch z:1 tag:TAG_CLOUD_BATCH];
for(int x=0; x CCSprite *s = [CCSprite spriteWithBatchNode:cloudBatch
rect:CGRectMake(0,0,64,64)];
[s setOpacity:100];
[cloudBatch addChild:s];
[s setPosition:ccp(arc4random()%500-50, arc4random()%150+200)];
}

//Middleground Grass
int capacity = 10;
CCSpriteBatchNode *grassBatch1 = [CCSpriteBatchNode
batchNodeWithFile:@"grass_01.png" capacity:capacity];
[self addChild:grassBatch1 z:1 tag:TAG_GRASS_BATCH_1];
for(int x=0; x CCSprite *s = [CCSprite spriteWithBatchNode:grassBatch1
rect:CGRectMake(0,0,64,64)];
[s setOpacity:255];
[grassBatch1 addChild:s];
[s setPosition:ccp(arc4random()%500-50, arc4random()%20+70)];
}

//Foreground Grass
CCSpriteBatchNode *grassBatch2 = [CCSpriteBatchNode
batchNodeWithFile:@"grass_01.png" capacity:10];
[self addChild:grassBatch2 z:3 tag:TAG_GRASS_BATCH_2];
for(int x=0; x CCSprite *s = [CCSprite spriteWithBatchNode:grassBatch2
rect:CGRectMake(0,0,64,64)];
[s setOpacity:255];
[grassBatch2 addChild:s];
[s setPosition:ccp(arc4random()%500-50, arc4random()%40-10)];
}

/*** Draw colored rectangles using a 1px x 1px white texture ***/

//Draw the sky using blank.png
[self drawColoredSpriteAt:ccp(240,190) withRect:CGRectMa
ke(0,0,480,260) withColor:ccc3(150,200,200) withZ:0];

//Draw the ground using blank.png
[self drawColoredSpriteAt:ccp(240,30)
withRect:CGRectMake(0,0,480,60) withColor:ccc3(80,50,25) withZ:0];

return self;
}

-(void) drawColoredSpriteAt:(CGPoint)position withRect:(CGRect)rect
withColor:(ccColor3B)color withZ:(float)z {
CCSprite *sprite = [CCSprite spriteWithFile:@"blank.png"];
[sprite setPosition:position];
[sprite setTextureRect:rect];
[sprite setColor:color];
[self addChild:sprite];

//Set Z Order
[self reorderChild:sprite z:z];
}

@end

它是如何工作的....

本节给我们介绍了绘制sprite的一般方法:

从一个文件中创建一个CCSprite:

首先,介绍一些绘制一个sprite的最简单的方法。这涉及到使用CCSprite类方法,如下:

+(id)spriteWithFile:(NSString*)filename;

这是初始化一个sprite的最直接的方法,并且适合很多种情况。

从一个文件中加载一个sprite的另一种方法:

之后我们将看到使用UIImage/CG/ImageRef,CCTexture2D来创建CCSprite的例子,还有使用一个CCTexture2D对象创建CCSpriteFrame的例子。CGImageRef支持将Cocos2D绑定到其他的框架和工具。CCTexture2D是创建纹理的底层机制。

使用CCSpriteFrameCache加载spritesheet:

下面,我们将看到使用sprite的最美妙的方法,就是CCSpriteFrameCache类。CCSpriteFrameCache对象是所有sprite帧的缓存,在CocosD-iPhone v0.99中有介绍。使用一个spritesheet和它相关联的PLIST文件,我们可以加载多个sprite到缓存中。使用下面的代码我们可以使用缓存中的sprite创建CCSprite:

+(id)spriteWithSpriteFrameName:(NSString*)filename;

Mipmaping:

Mipmaping允许你缩放一个纹理或者不混淆你的sprites的情况下缩放一个背景。当我们将爱丽丝缩小时,混淆将不能避免地发生。但是开启Mipmaping之后,Cocos2D将在一个更小区域里动态地产生低分辨率的纹理来事像素化平滑过渡。返回前面的代码,将如下代码注释掉:

[alice.texture generateMipmap];
ccTexParams texParams = { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR,
GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE };
[alice.texture setTexParameters:&texParams];


现在你可看到爱丽丝的像素画变小了。

使用CCSpriteBatchNode绘制多个sprite副本。

在v0.99.5版本引入的CCSpriteBatchNode类介绍了一个重复绘制同一个sprite对象的高效方方达。这是另一个额外高效的方法,虽然你可以不需要不这样做。在这三个例子中我们绘制了10个随机位置的白云和60个随机位置的小草。

绘制彩色的矩形。

最后,有一个经常会用的一个简单技术。通过绘制一个一个空白像素的sprite纹理,然后向其充颜色并设置它的textrueRect属性,这样我们就可以创建一个非常有用的色条:

CCSprite *sprite = [CCSprite spriteWithFile:@"blank.png"];
[sprite setTextureRect:CGRectMake(0,0,480,320)];
[sprite setColor:ccc3(255,128,0)];


值这个例子中,我们使用这个技术创建了一个简单的草地和天空背景。

给sprites上色

在之前的小节中我们使用带颜色的矩形绘制了简单的草地和天空背景。能够设置纹理颜色和透明度的是一个简单工具,如果巧妙运用的话可以创造出非常酷的效果。在本小节中我们将创建两个武士各自拿着一个发光武士刀对峙的场景。



准备

请参考RecipeCollection01工程的代码并注意一些为简洁而删除的代码。

如何做?

执行如下代码:

//Add a sinister red glow gradient behind the evil samurai
CCGradientLayer *redGradient = [CCGradientLayer
layerWithColor:ccc4(0,0,0,0) toColor:ccc4(255,0,0,100) withDirection
:CCGradientDirectionT_B width:200 height:200];
[redGradient setPosition:ccp(280,60)];
[redGradient setRotation:-90];
[self addChild:redGradient z:2 tag:TAG_RED_GRADIENT];


// Make the swords glow
[self glowAt:ccp(230,280) withScale:CGSizeMake(3.0f, 11.0f)
withColor:ccc3(0,230,255) withRotation:45.0f withSprite:goodSamurai];
[self glowAt:ccp(70,280) withScale:CGSizeMake(3.0f, 11.0f)
withColor:ccc3(255,200,2) withRotation:-45.0f withSprite:evilSamurai];

return self;
}

-(void) initButtons {
[CCMenuItemFont setFontSize:16];

//'Fade To Black' button
CCMenuItemFont* fadeToBlack = [CCMenuItemFont itemFromString:@
"FADE TO BLACK" target:self selector:@selector(fadeToBlackCallback:)];
CCMenu *fadeToBlackMenu = [CCMenu menuWithItems:fadeToBlack, nil];
fadeToBlackMenu.position = ccp( 180 , 20 );
[self addChild:fadeToBlackMenu z:4 tag:TAG_FADE_TO_BLACK];
}

/* Fade the scene to black */
-(void) fadeToBlackCallback:(id)sender {
CCSprite *fadeSprite = [self getChildByTag:TAG_FADE_SPRITE];
[fadeSprite stopAllActions];
[fadeSprite setColor:ccc3(0,0,0)];
[fadeSprite setOpacity:0.0f];
[fadeSprite runAction:
[CCSequence actions:[CCFadeIn actionWithDuration:2.0f], [CCFadeOut
actionWithDuration:2.0f], nil] ];
}

/* Create a glow effect */
-(void) glowAt:(CGPoint)position withScale:(CGSize)size
withColor:(ccColor3B)color withRotation:(float)rotation
withSprite:(CCSprite*)sprite {
CCSprite *glowSprite = [CCSprite spriteWithFile:@"fire.png"];
[glowSprite setColor:color];
[glowSprite setPosition:position];
[glowSprite setRotation:rotation];
[glowSprite setBlendFunc: (ccBlendFunc) { GL_ONE, GL_ONE }];
[glowSprite runAction: [CCRepeatForever actionWithAction:
[CCSequence actions:[CCScaleTo actionWithDuration:0.9f
scaleX:size.width scaleY:size.height], [CCScaleTo
actionWithDuration:0.9f scaleX:size.width*0.75f scaleY:size.
height*0.75f], nil] ] ];
[glowSprite runAction: [CCRepeatForever actionWithAction:
[CCSequence actions:[CCFadeTo actionWithDuration:0.9f
opacity:150], [CCFadeTo actionWithDuration:0.9f opacity:255], nil] ]
];
[sprite addChild:glowSprite];
}

@end

它是如何工作的?

本小节展示了一些基本的色彩处理技术。

设置sprite颜色

设置sprite颜色的最简单的的方法如下:

-(void) setColor:(ccColor3B)color;

设置sprite颜色有效减少了你可以显示的颜色,但是它允许在绘制的时候提供更多的编程灵活性。在本节中我们使用setColor设置了很多东西,只用它画了一个蓝色的天空,一个黄色的太阳,一个黑色的戏剧电影显示条,ccColor3B是一个C结构,它包含了3个GLubyte变量。使用如下的代码创建一个ccClor3B结构:

ccColor3B ccc3(const GLubyte r, const GLubyte g, const GLubyte
b);


Cocos2d也定义了一系列的预定义的常量颜色,它们是:

ccWHITE, ccYELLOW, ccBLUE, ccGREEN, ccRED,
ccMAGENTA, ccBLACK, ccORANGE, ccGRAY


过渡到一个颜色

让场景过渡到一个指定的颜色,我们可以使用一个blank.png技术,在最后我们将介绍它。我们首先绘制一个和场景一样大的sprite,然后将其填充我们要过渡的目标颜色,然后执行CCFadin动作将sprite过渡到这个颜色。

[fadeSprite setColor:ccc3(255,255,255)];
[fadeSprite setOpacity:0.0f];
[fadeSprite runAction: [CCFadeIn actionWithDuration:2.0f] ];


使用CCGradienLayer

使用CCGradienLayer类我们可以创建渐变效果。让背景中的山过渡到两个武士站在的草地,我们可以使用如下代码来实现渐变过渡:

CCGradientLayer *gradientLayer = [CCGradientLayer layerWithColor
:ccc4(61,33,62,255) toColor:ccc4(65,89,54,255) withDirection:CCGra
dientDirectionT_B width:480 height:100];
[gradientLayer setPosition:ccp(0,50)];
[self addChild:gradientLayer z:0 tag:TAG_GROUND_GRADIENT];


因为CCGradienLayer同样可以控制透明度,这个功能很有用。正如你看到的,那个邪恶的武士背后是发红光的。

让sprite发光:为了让demo中的刀发光我们使用巧妙的颜色操作,加上混合,调和和缩放动作。首先加载Cocos2d提供的fire.png。通过改变它X和Y方向尺寸,我们可以让其变瘦或者变胖。如果你想改变其纵横比(因为例子中的纵横比是3:11,已经很瘦了),你可以将其缩放的更生动些。你同样需要设置混合函数{GL_ONE,GL_ONE}添加混合。最后这个sprite的效果添加到真实的sprite上。

CCSprite *glowSprite = [CCSprite spriteWithFile:@"fire.png"];
[glowSprite setColor:color];
[glowSprite setPosition:position];
[glowSprite setRotation:rotation];
[glowSprite setBlendFunc: (ccBlendFunc) { GL_ONE, GL_ONE }];
[glowSprite runAction: [CCRepeatForever actionWithAction:
[CCSequence actions:[CCScaleTo actionWithDuration:0.9f
scaleX:size.width scaleY:size.height], [CCScaleTo
actionWithDuration:0.9f scaleX:size.width*0.75f scaleY:size.
height*0.75f], nil] ] ];
[glowSprite runAction: [CCRepeatForever actionWithAction:
[CCSequence actions:[CCFadeTo actionWithDuration:0.9f
opacity:150], [CCFadeTo actionWithDuration:0.9f opacity:255], nil]
] ];
[sprite addChild:glowSprite];


sprite的动画

现在给sprite添加动画,这里要强调的是动画的复杂度和你制作动画的难度差不多。在本节中,我们将使用简单的动画创建一个引人入胜的效果。我们创建一群蝙蝠飞向一个让人惊悚的城堡。并使用之前介绍的技术添加一个冷色调的光照效果。

开始

请参考RecipeCollection01工程的代码,并注意遗漏的部分的代码。

如何做?

执行如下代码:

//SimpleAnimObject.h
@interface SimpleAnimObject : CCSprite {
int animationType;
CGPoint velocity;
}


@interface Ch1_AnimatingSprites {
NSMutableArray *bats;
CCAnimation *batFlyUp;
CCAnimation *batGlideDown;
CCSprite *lightningBolt;
CCSprite *lightningGlow;
int lightningRemoveCount;
}

-(CCLayer*) runRecipe {
//Add our PLIST to the SpriteFrameCache
[[CCSpriteFrameCache sharedSpriteFrameCache]
addSpriteFramesWithFile:@"simple_bat.plist"];

//Add a lightning bolt
lightningBolt = [CCSprite spriteWithFile:@"lightning_bolt.png"];
[lightningBolt setPosition:ccp(240,160)];
[lightningBolt setOpacity:64];
[lightningBolt retain];

//Add a sprite to make it light up other areas.
lightningGlow = [CCSprite spriteWithFile:@"lightning_glow.png"];
[lightningGlow setColor:ccc3(255,255,0)];
[lightningGlow setPosition:ccp(240,160)];
[lightningGlow setOpacity:100];
[lightningGlow setBlendFunc: (ccBlendFunc) { GL_ONE, GL_ONE }];
[lightningBolt addChild:lightningGlow];

//Set a counter for lightning duration randomization
lightningRemoveCount = 0;

//Bats Array Initialization
bats = [[NSMutableArray alloc] init];

//Add bats using a batch node.
CCSpriteBatchNode *batch1 = [CCSpriteBatchNode
batchNodeWithFile:@"simple_bat.png" capacity:10];
[self addChild:batch1 z:2 tag:TAG_BATS];

//Make them start flying up.
for(int x=0; x //Create SimpleAnimObject of bat
SimpleAnimObject *bat = [SimpleAnimObject
spriteWithBatchNode:batch1 rect:CGRectMake(0,0,48,48)];
[batch1 addChild:bat];
[bat setPosition:ccp(arc4random()%400+40, arc4random()%150+150)];

//Make the bat fly up. Get the animation delay (flappingSpeed).
float flappingSpeed = [self makeBatFlyUp:bat];

//Base y velocity on flappingSpeed.
bat.velocity = ccp((arc4random()%1000)/500 + 0.2f, 0.1f/
flappingSpeed);

//Add a pointer to this bat object to the NSMutableArray
[bats addObject:[NSValue valueWithPointer:bat]];
[bat retain];

//Set the bat's direction based on x velocity.
if(bat.velocity.x > 0){
bat.flipX = YES;
}
}

//Schedule physics updates
[self schedule:@selector(step:)];

return self;
}

-(float)makeBatFlyUp:(SimpleAnimObject*)bat {
CCSpriteFrameCache * cache = [CCSpriteFrameCache
sharedSpriteFrameCache];

//Randomize animation speed.
float delay = (float)(arc4random()%5+5)/80;
CCAnimation *animation = [[CCAnimation alloc] initWithName:@
"simply_bat_fly" delay:delay];

//Randomize animation frame order.
int num = arc4random()%4+1;
for(int i=1; i [animation addFrame:[cache spriteFrameByName:[NSString
stringWithFormat:@"simple_bat_0%i.png",num]]];
num++;
if(num > 4){ num = 1; }
}

//Stop any running animations and apply this one.
[bat stopAllActions];
[bat runAction:[CCRepeatForever actionWithAction: [CCAnimate
actionWithAnimation:animation]]];

//Keep track of which animation is running.
bat.animationType = BAT_FLYING_UP;

return delay; //We return how fast the bat is flapping.
}

-(void)makeBatGlideDown:(SimpleAnimObject*)bat {
CCSpriteFrameCache * cache = [CCSpriteFrameCache
sharedSpriteFrameCache];

//Apply a simple single frame gliding animation.
CCAnimation *animation = [[CCAnimation alloc] initWithName:@
"simple_bat_glide" delay:100.0f];
[animation addFrame:[cache spriteFrameByName:@"simple_bat_01.png"]];

//Stop any running animations and apply this one.
[bat stopAllActions];
[bat runAction:[CCRepeatForever actionWithAction: [CCAnimate
actionWithAnimation:animation]]];

//Keep track of which animation is running.
bat.animationType = BAT_GLIDING_DOWN;
}

-(void)step:(ccTime)delta {
CGSize s = [[CCDirector sharedDirector] winSize];

for(id key in bats){
//Get SimpleAnimObject out of NSArray of NSValue objects.
SimpleAnimObject *bat = [key pointerValue];

//Make sure bats don't fly off the screen
if(bat.position.x > s.width){
bat.velocity = ccp(-bat.velocity.x, bat.velocity.y);
bat.flipX = NO;
}else if(bat.position.x bat.velocity = ccp(-bat.velocity.x, bat.velocity.y);
bat.flipX = YES;
}else if(bat.position.y > s.height){
bat.velocity = ccp(bat.velocity.x, -bat.velocity.y);
[self makeBatGlideDown:bat];
}else if(bat.position.y bat.velocity = ccp(bat.velocity.x, -bat.velocity.y);
[self makeBatFlyUp:bat];
}

//Randomly make them fly back up
if(arc4random()%100 == 7){
if(bat.animationType == BAT_GLIDING_DOWN){ [self
makeBatFlyUp:bat]; bat.velocity = ccp(bat.velocity.x, -bat.
velocity.y); }
else if(bat.animationType == BAT_FLYING_UP){ [self
makeBatGlideDown:bat]; bat.velocity = ccp(bat.velocity.x, -bat.
velocity.y); }
}

//Update bat position based on direction
bat.position = ccp(bat.position.x + bat.velocity.x, bat.position.y
+ bat.velocity.y);
}

//Randomly make lightning strike
if(arc4random()%70 == 7){
if(lightningRemoveCount [self addChild:lightningBolt z:1 tag:TAG_LIGHTNING_BOLT];
lightningRemoveCount = arc4random()%5+5;
}
}

//Count down
lightningRemoveCount -= 1;

//Clean up any old lightning bolts
if(lightningRemoveCount == 0){
[self removeChildByTag:TAG_LIGHTNING_BOLT cleanup:NO];
}
}

@end

它是图和工作的?

本节将介绍使用SImpleAnimObject构造一个基本的动画类:

动画对象类的结构:

当一个动画转向另一个动画时,记录动画对象的状态是很重要的一个工作。在本例子中,我们使用SimpleAnimObject,它保存了随意的animationType的变量。我们同样保存了一个速度变量,它有一个Y值,此值和动画帧的延迟成反比:

@interface SimpleAnimObject : CCSprite {
int animationType;
CGPoint velocity;
}


根据动画系统的深度,你同样需要保存更多的信息,比如指向一个正在执行的CCAnimation实例的指针,帧信息,和物理形状。

更多....

当你越来越深入学习Cocos2d游戏开发,你将会变得越来越倾向于使用assynchronous action来实现游戏的逻辑和人工智能。从CCAction这个类继承的类,任何移动的动作都可以使用CCMoveBy来实现CCNode的移动,实现CCSprite动画可以使用CCAnimation。当动作执行时,一个异步的定时机制保留在了后台中。刚开始程序员通常会过度依赖这个功能。当执行多动作时使用这个技术可以提高效率。在下面的例子中我们将使用一个整合计时器,它将允许我们
控制光照在屏幕上持续的时间。

//Randomly make lightning strike
if(arc4random()%70 == 7){
if(lightningRemoveCount [self addChild:lightningBolt z:1 tag:TAG_LIGHTNING_BOLT];
lightningRemoveCount = arc4random()%5+5;
}
}


//Count down
lightningRemoveCount -= 1;

//Clean up any old lightning bolts
if(lightningRemoveCount == 0){
[self removeChildByTag:TAG_LIGHTNING_BOLT cleanup:NO];
}

就前面这段代码来说,通常使用同步计时器较异步计时器更胜一筹,记住这个,特别是当你的游戏规模越来越大时。

总结:

在本文中我们简单介绍了sprites的基本使用方法。

coco2d Javascript跨平台游戏开发:入门指南

翻译:有二

校对:glory, 子龙山人

原文:How To Make a Cross-Platform Game with Cocos2D Javascript Tutorial: Getting Started

这不是一张图片这是个游戏!用鼠标点击

你是否知道你可以做个COCO2D游戏,让这个游戏可以同时运行在IPhone, Mac, Andriod和Web中,并且它们都共享同一份代码吗? 不相信我吗?你试着点击右边上的截图,实际上这就是一个Coco2d游戏!

你可以做到的,你只需要采用一种新的神奇的Coco2D技术----Cocos2D Javascript。 它允许你写一次游戏代码,并能够以完全本地化app的速度在任何地方运行它。现在新技术的唯一问题就是文档不足,但是我们绝不会投降,我们可以弥补它。

在本教程中,你将采用cocos2d-javascript来移植raywenderlich.com的经典游戏Ninjas Going Pew-Pew. 当我学习新框架时,我就会想去实现这款游戏。因为这个游戏不仅简单,而且涵盖了游戏开发中的大部分重要思想。

这个教程适合以下2类人,一种是完全不懂Cocos2D, 还有一种是已经熟悉了Cocos2DIOS,并想学习Cocos2D Javascript。 但是,这个教程还是假设你是有一些Javascript基础的一类人。如果你没有接触过 Javascript,我推荐一本《Javascript:The Definitive Guide》,我就是通过阅读该书来学习这门语言的。

继续阅读并学习本教程,成为Cocos2D-Javascript大神吧!

关于Cocos2D Javascript

在你开始Coding 之前,先了解Cocos2D Javascript 是什么, 它是怎么工作的,这对你很重要。目前有三种Cocos2D变体(cocos2d-iphone, cocos2d-x, 和 coco2d-html5),它们一起共享了一套Javascript API。它的基本思想是,你只需要使用Javascript编写核心游戏玩法。

  • Cocos2d-iphone和cocos2d-x引用了一个叫做SpiderMonkey的库来运行你写的Javascript代码,但在幕后,所有的核心Cocos2D框架代码本身仍然是用OpenGL / objective - C / C + +编写,所以它还是跑得很快。
  • cocos2d-html 直接在浏览器中跑Javascript,正如你预想的那样。另外,CocosBuilder 场景编辑器完全兼容Javascript API。想要看一个完整的采用cocos2d Javascript编写的游戏例子,可以check out我在一月份的 One Game A Month 中提到的游戏: :Confinement.,这个游戏就是利用Javascript 绑定,能够在web ,ios, mac等等平台上运行。Cocos2D Javascript 绑定是一项很时髦的新技术,新技术出来都会有些问题。但它是未来,所以这绝对是值得学习和关注的!

先有JavaScript代码,还是先有框架?

总的来说,你编写一次Javascript代码,就能够采用上述任何游戏框架来运行它。但你必须先从某一个框架开始学习,在初学者中我发现最简单的是使用cocos2d-html5。

这是因为cocos2d-html5他本身就是用Javascript编写的,所以当你调试的时候,可以进入框架内部查看代码。查看框架代码是很有必要的,这能让我们更好地了解错误原因。你将要在本教程中学习的内容,就是用cocos2d-html5先做出一个游戏(web版)并把这代码放到上述其他框架中运行(使之能够同时运行在Android,ios上面)。

入门指南:

首先:在这里下载starter project,解压zip文件, 你将会看到下面目录结构:

im1

Starter project包含以下文件夹:

  • Art: 制作SimpleGame游戏时所需要的图片目录-一个怪物,一个忍者,和他的忍者飞镖。
  • Platform: 存放每种框架代码的四个空目录,在教程后面将会填充这些空目录。现在我给你列出这些空目录,只是为了目录结构的完整。
  • Sounds: 背景音乐 和“pew-pew”声音效果,这些文件的作者是 truly。
  • 注意,Starter project 没有包含任何的代码,因为那是你的工作。

Hello ,Cocos2D-Javascript!

你准备好最后看到这个神奇的跨平台的Javascript代码了吗?让我们试一试,并创建一个简单的Cocos2D场景显示Hero精灵。当你引用art目录中的images 及Cocos2D-Javascript中的其他资源,你必须用特殊的数组包含他们,数组取名为g_ressources(是的,这命名是有意义的),以致框架能加载他们,所以第一步是添加英雄精灵到特殊的数组中。用你喜欢的编辑器创建一个新文件,文件名为Cocos2DSimpleGame\Src\resource.js(最近我使用的的Sublime Text2),并用以下内容修改文件中的内容。

var dirArt = "Art/";
var s_player = dirArt + "player.png";
 
var g_ressources = [
 
    {type:"image", src:s_player}
 
];

这仅仅只是添加了一张图片到你的资源列表中(你的玩家图片)。稍后你将要添加Art目录和Sounds目录下的资源到这个资源列表中。但是,现在我们只要这一张图片就行。

接下来,你讲创建一个简单的Cocos2D 层来放置这种图片。创建一个新文件名为Cocos2DSimpleGame\Src\MainLayer.js并用以下内容替换之:

// 1
var MainLayer = cc.LayerColor.extend({
 
    // 2
    ctor:function() {
        this._super();
 
        // 3
        cc.associateWithNative( this, cc.LayerColor );
    },
 
    // 4
    onEnter:function () {
        this._super();
 
        // 5
        var player = cc.Sprite.create(s_player);
 
        // 6
        player.setPosition(player.getContentSize().width / 2, winSize.height / 2);
 
        // 7
        this.addChild(player);
    }
 
});

让我们一行一行的阅读下这段代码:

1. 创建一个MainLayer类,该类继承了Cocos2D的LayerColor类,注意,在Cocos2DJavascript bindings中,所有的Cocs2D类都有个cc前缀。

2. 创建一个构造函数,该构造函数会调用基类的构造函数。

3. 在Cocos2D Javascript 绑定中, 无论什么时候,只要你继承了Cocos2D类,你就必须要调用这个方面来保证你的类与cocos2D类能更好的合作。

4. 当这个节点第一次添加到场景中,Cocos2D将会调用这个方法,所以这是把layer初始化代码替换的好地方。

5. 这行将创建一个精灵并用变量player保存,注意:是通过参数(该参数为一个已经创建了的常量精灵)传递给该变量

6. 设置精灵在屏幕中间,其中winSize是一个常量,之后我们将会定义它。

7. 最后把精灵添加到图层中。

然后,在文件的后面添加下面的方法。

// 1
MainLayer.create = function () {
    var sg = new MainLayer();
    if (sg && sg.init(cc.c4b(255, 255, 255, 255))) {
        return sg;
    }
    return null;
};
 
// 2
MainLayer.scene = function () {
    var scene = cc.Scene.create();
    var layer = MainLayer.create();
    scene.addChild(layer);
    return scene;
};

这是创建对象的2个辅助方法

1. 第一个方法是,创建一个MainLayer实例

2. 第二个方法是,创建一个新的场景并把创建MainLayer的对象以孩子形式添加到场景中。

现在这只是创建一个MainLayer.js文件,最好代码是你能写出跨平台的代码,并能工作在ios,Android等等平台中。

Hello, Cocos2D-HTML5!

由于之前我提到过,你首先需要制作一个cocos2d-HTML5版本的游戏。然后把它移植到你想要的平台上去。

下载最新的版本latest version of Coos2D-HTML5,,这是我写教程时用到的最新的版本V2.1,并解压到你硬盘中的任何目录下,只要这个目录是安全的。

然后,把Cocos2D-HTML5目录下的cocos2d, CocosDenshion, 和extensions 目录拷贝到
Cocos2DSimpleGame\Platform\HTML5目录下。

im2

把Cocos2D-HTML5所有代码拷贝到你工程的目录下,以便你更好的引用他。注意:你不能这样就直接发布游戏,在第二部分你将会学习到更多关于这方面的内容。目前这样做会有利于提高你的调试及开发能力。

接下来,创建一个新文件,命令为Cocos2DSimpleGame\index.html并用以下内容修改文件内容。




    
    Cocos2D-JS Simple Game Demo | raywenderlich.com



 

这是一些简单的HTML语言,目的是让cocos2D出现在页面的中间。如果你对HTML语言比较陌生,不要担心,你可以用这部分代码为一个模板,基本能满足你的需求。

有2处比较重要的地方我要指出:


这是设置了HTML5层,就是Cocos2D画面要出现的地方,并给这个层的ID为“gameCanvas”,框架则根据ID来识别层。如果你想改变层的大小,则你可以修改上面的数字。


这代码告诉浏览器去寻找一个名为cocosd.js的Javascript文件,这个文件你现在还没有写。

所以现在让我们来添加这个文件,创建一个文件,命名为Cocos2DSimpleGame\cocos2d.js并添加以下内容:

(function () {
    var d = document;
    var c = {
 
        // 1
        menuType:'canvas',
        COCOS2D_DEBUG:2,
        box2d:false,
        chipmunk:false,
        showFPS:true,
        frameRate:60,
        loadExtension:true,
        tag:'gameCanvas', 
 
        // 2
        engineDir:'./Platform/HTML5/cocos2d/',
        appFiles:[
            './Src/resource.js',
            './Src/MainLayer.js',
            './Src/main.js'
        ]
    };
 
    // 3
    window.addEventListener('DOMContentLoaded', function () {
        var s = d.createElement('script');
 
        if (c.SingleEngineFile && !c.engineDir) {
            s.src = c.SingleEngineFile;
        }
        else if (c.engineDir && !c.SingleEngineFile) {
            s.src = c.engineDir + 'platform/jsloader.js';
        }
        else {
            alert('You must specify either the single engine file OR the engine directory in "cocos2d.js"');
        }        
 
        document.ccConfig = c;
        s.id = 'cocos2d-html5';
        d.body.appendChild(s);
    });
})();

这个文件基本可以作为模板来用,但有一部分你可能想自己改改,所以让我们一起看看这些代码吧。

1. 有各种各样的Cocos2D配置, 如激活框架调试功能,是否引用库文件(Box2D库)等等。

2. 这里你应该指定源文件的目录及文件名,你还记得你是怎么拷贝Platform/HTML5/cocos2d目录的吗?你也要指定你游戏中的Javascript文件。

3. 这仅仅是一些Cocos2D框架运行的入口模板。

你也许发现了2中的main.js文件了,但是你现在还没有写这个文件。这是你运行游戏不得不写的最后一个文件了。

创建一个新文件,命名为Cocos2DSimpleGame\Src\main.js并添加如下代码:

var cocos2dApp = cc.Application.extend({
    config:document['ccConfig'],
    ctor:function (scene) {
        this._super();
        this.startScene = scene;
        cc.COCOS2D_DEBUG = this.config['COCOS2D_DEBUG'];
        cc.initDebugSetting();
        cc.setup(this.config['tag']);
        cc.Loader.getInstance().onloading = function () {
            cc.LoaderScene.getInstance().draw();
        };
        cc.Loader.getInstance().onload = function () {
            cc.AppController.shareAppController().didFinishLaunchingWithOptions();
        };
        cc.Loader.getInstance().preload(g_ressources);
    },
    applicationDidFinishLaunching:function () {
        var director = cc.Director.getInstance();
        director.setDisplayStats(this.config['showFPS']);
        director.setAnimationInterval(1.0 / this.config['frameRate']);
        // 1
        director = cc.Director.getInstance();
        winSize = director.getWinSize();
        centerPos = cc.p( winSize.width/2, winSize.height/2 );
        director.runWithScene(new this.startScene());
        return true;
    }
});
 
// 2
var director;
var winSize;
var centerPos;
var myApp = new cocos2dApp(MainLayer.scene);

为了运行游戏,你需要创建一个类,该类继承cc.Application,实际做事是在这里。它有个applicationDidFinishLaunching方法,该方法很像ios。除了我标出的2部分,其他部分都是程序的通用方式。

1. 初始化全局变量,我之间就提过的。

2. 声明全局变量,并指出在特定的环境中运行(MainLayer.scene 是你之前创建的一个场景)

最后你可以运行了, 用你喜欢的浏览器打开index.html,就测试了,注意:我很幸运我使用的是Firefox浏览器,Safari浏览器可能有时运行不起来(因为safari的file协议限制,不过可以采用web server的方式来运行))。

如果运行正常,你将会在你的屏幕出现忍者:

im3

移动怪兽

下面你应该想添加一些怪兽到你的屏幕去跟忍者战斗,为了更有趣,你想让怪兽移动。另外不要有太多的怀疑,让我们创建怪兽从屏幕的右边轻飘出来,并设置一个向左运动的行为。

首先,打开Cocos2DSimpleGame\Src\resource.js修改资源文件,并包含以下2个图片文件:

var dirArt = "Art/";
var s_player = dirArt + "player.png";
var s_monster = dirArt + "monster.png";
var s_projectile = dirArt + "projectile.png";
 
var g_ressources = [
 
    {type:"image", src:s_player},
    {type:"image", src:s_monster},
    {type:"image", src:s_projectile}
 
];

然后打开Cocos2DSimpleGame\Src\MainLayer.js并用以下内容替换文件开头部分:

var MainLayer = cc.LayerColor.extend({
 
    _monsters:[],
 
    ctor:function() {
 
        // Rest of file...

这在场景中创建了怪物实例变量,并初始化为空数组,在onEnter方法后添加一个停顿,以及添加一个新方法:

addMonster:function() {
 
    var monster = cc.Sprite.create(s_monster);
 
    // Determine where to spawn the monster along the Y axis
    var minY = monster.getContentSize().height / 2;
    var maxY = winSize.height - monster.getContentSize().height / 2;
    var rangeY = maxY - minY;
    var actualY = (Math.random() * rangeY) + minY; // 1
 
    // Create the monster slightly off-screen along the right edge,
    // and along a random position along the Y axis as calculated above
    monster.setPosition(winSize.width + monster.getContentSize().width/2, actualY);
    this.addChild(monster); // 2
 
    // Determine speed of the monster
    var minDuration = 2.0;
    var maxDuration = 4.0;
    var rangeDuration = maxDuration - minDuration;
    var actualDuration = (Math.random() % rangeDuration) + minDuration;
 
    // Create the actions
    var actionMove = cc.MoveTo.create(actualDuration, cc.p(-monster.getContentSize().width/2, actualY)); // 3
    var actionMoveDone = cc.CallFunc.create(function(node) { // 4
        cc.ArrayRemoveObject(this._monsters, node); // 5
        node.removeFromParent();
    }, this); 
    monster.runAction(cc.Sequence.create(actionMove, actionMoveDone));
 
    // Add to array
    monster.setTag(1);
    this._monsters.push(monster); // 6
 
}

在这里我将以一种啰嗦的方式讲解,以便尽可能的理解。首要的还是我们尽量讨论一些有意义的问题:你简单的计算一下在什么时候创建对象,该对象放置在什么的位置,并添加它到场景中。这跟你创建英雄精灵是一样的。

我将以2种不同的方法解释剩下的代码,以你是否已经熟悉了Cocos2D ios API为准。如果你熟悉cocos2d-ios,你就看情况一。如果不熟悉,则看情况二。你不需要同时看这两种情况的内容。

情况一:我熟悉Cocos2D-IOS

对于从Cocos2D-IOS转Cocos2D-Javascript这里给出了一写参考:

1. Javascript有很方便的参考手册,如Math.random(). 你能在Javascript book I recommended.找到相关的资料。

2. 在objective - c中你会使用monster.postion这样的dot语法,但是在javascript bindings中都是采用monster.setPosition这样的setters语法。

3. cc.p等于cpp宏。

4. 你能通过方法名进入cc.CallFunc.create, 或者你能进入匿名函数,像你在这看到的一样(很像Objective-C中using CCCallBlock)

5. Cocos2D 有一个方法从Javascript数组中移除元素:cc.ArrayRemoveObject.

6. 在Javascript中运用this,而不是self,我很多时候都忘了这个规则,结果导致代码经常出现问题。

情况二:我是个Cocos2D新手

在这里添加动作元素,Cocos2D提供了一些非常方便的生成Action的方法,你能运用这个使你的精灵更灵活,如添加一个移动动作,跳的动作,淡化的动作,动画动作等等,在怪物中我们运用3个动作:

1. ccMoveTo: 你用cc.MoveTo动作物体直接移出屏幕左边,注意:你应该指定运行动作需要的时间,这里你还可以随机给出一个2s~4s的速度。

2. cc.CallFunc:这cc.CallFunc动作允许你指定一个回调函数去运行,当这个动作被调用时。你或者能进入函数名(像这样:怪物移动)或者你能用一个无名的函数显示他

们。在这个游戏中,在怪物移动到屏幕左边时,你应添加一些代码来实现怪物从游戏(或者是怪物数组)中移除,这代码是很重要的,不然就会出现内存泄露。注意:有其他(或更好的)方法去解决这个问题,如可以重复使用精灵数组,但是作为初学者,你还是用移除的这种简单的方法。

3. cc.Sequence: 这cc.Sequence动作允许你一序列动作按顺序执行,一次一个的。这种方法,你可以首先执行cc.MoveTo动作,然后完成cc.CallFun动作。

回到刚才问题:

现在让我们确定你addMonster函数能被定期的调用,这是一种方法。要做这事,添加addMonster, 这个新方法。

gameLogic:function(dt) {
    this.addMonster();
}

并在onEnter方法最后一行添加如下代码:

this.schedule(this.gameLogic, 3);

这会每隔3秒定时的运行你的gameLogic方法去产生一个怪物。保存你的文件,并重启你的浏览器,现在应该有怪物从你的屏幕中飞出来。

im4

射击炮弹

在这节,忍者想要一些技能,所以让我们添加射击技能。有很多的方法你能实现射击功能,但是这个游戏你将这样做,当用户点击屏幕时, 忍者则朝着点击的方向发射炮弹。

我想用cc.MoveTo动作来实现这种发射子弹的动作,这样初学者也能看懂,但是想要灵活运用,你不得不具备一些数学知识。这是因为这个cc.MoveTo需要你给定飞镖前进的目的地,但是你又不能使用这个touch点,因为touch点只是代表了飞镖飞出去的方向。实际上你想保持子弹移动穿过你点击的位置直到子弹移除屏幕。

下图表示的就是这种情况:

im5

正如你所看到的,来自相对于原点及点击点的x,y坐标构成的一个小三角形。你还需要构建一个大的三角形,放大一些比例,你知道了你离开屏幕时的终点位置了。好的,现在进入代码,首先,在文件头添加一个存放炮弹的数组。

_projectiles:[],

然后你必须能响应点击事件在你的层中,但是怎样处理这样依据你的额运行环境,如手机,桌面,或者浏览器。所以在onEnter函数开始的地方添加如下代码,正确的还要调用
this._super():

if( 'touches' in sys.capabilities ) {
    this.setTouchEnabled(true);
}
if( 'mouse' in sys.capabilities ) {
    this.setMouseEnabled(true);
}

这onMouse被回调,如果这里有鼠标输入,或者onTouch被回调如果有触屏输入。

gameLogic方法后,添加如下代码:

locationTapped:function(location) {
    // Set up initial location of the projectile
    var projectile = cc.Sprite.create(s_projectile);
    projectile.setPosition(20, winSize.height/2);
 
    // Determine offset of location to projectile
    var offset = cc.pSub(location, projectile.getPosition()); // 1
 
    // Bail out if you are shooting down or backwards
    if (offset.x <= 0) return;
 
    // Ok to add now - we've double checked position
    this.addChild(projectile);
 
    // Figure out final destination of projectile
    var realX = winSize.width + (projectile.getContentSize().width / 2);
    var ratio = offset.y / offset.x;
    var realY = (realX * ratio) + projectile.getPosition().y;
    var realDest = cc.p(realX, realY);
 
    // Determine the length of how far you're shooting
    var offset = cc.pSub(realDest, projectile.getPosition());
    var length = cc.pLength(offset);
    var velocity = 480.0;
    var realMoveDuration = length / velocity;
 
    // Move projectile to actual endpoint
    projectile.runAction(cc.Sequence.create( // 2
        cc.MoveTo.create(realMoveDuration, realDest),
        cc.CallFunc.create(function(node) {
            cc.ArrayRemoveObject(this._projectiles, node);
            node.removeFromParent();
        }, this)
    ));
 
    // Add to array
    projectile.setTag(2);
    this._projectiles.push(projectile);
},
 
onMouseUp:function (event) {
    var location = event.getLocation();
    this.locationTapped(location);
},
 
onTouchesEnded:function (touches, event) {
    if (touches.length <= 0)
        return;
    var touch = touches[0];
    var location = touch.getLocation();
    this.locationTapped(location);
}

我要再一次依据你是否已经熟悉Cocos2D-IOS来做不同的解释:

解释一:假设你熟悉Cocos2D-IOS

这里提出几点建议给Cocos2D-IOS转Cocos2D-Javascript的程序员

1.这方法用点演示数学,你知道你喜欢的ccpAdd,ccpSub在这仍然存在。他们仅仅是改变了以下名字,例如:cc.pSub就等同于ccpSub。

2.你能创建一序列的动作就像你在Cocos2D-IOS一样。

解释二:你完全是个Cocos2D新手

onMouseUp和onTounchesEnded方法会获取鼠标或触摸位置,并把这个值通过locationTapped函数分发出去。

这方法开始就载入一个炮弹精灵,并和往常一样初始化了放置子弹的位置。这时你决定你需要炮弹移动到哪里,可以使用忍者与触摸点之间的向量为参考,根据之前描述的逻辑算法去计算子弹最终移动的位置。

注意,这个算法并不是完美的。假设你让子弹一直运动到屏幕之外的X位置,此时,子弹的y坐标可能早就超过屏幕之外了。你可以使用一些其它的最短距离算法,判断到底是x方向先出屏幕还是y方向先出屏幕。 作为一个新手入门教程,我们就不在这里再作讨论了。

最后一件事,你计算子弹移动的时间。为了让子弹在任何方向上都是以恒定的速度运行,你不得不再用一些数学来处理了。你能通过cc.pLength函数计算出运动的速度。

一旦你有了这个距离,你仅仅通过除以速度就能获取时间了。这是因为速度=距离除以时间,或者是时间=距离除以速度。

其他部分的设置动作就想你朝着目标设置一样。保存文件,刷新你的浏览器,现在你的忍者应该能发射子弹来迎接面朝你扑过来的敌人了。

im6

碰撞检测

所以现在你的忍者可以朝任何方向发射子弹了,但是你的忍者真的想去做的是打到怪物,所以让我们添加一些代码来检测你的子弹与怪物什么时候碰撞。

有很多方法去解决这个问题。采用Cocos2D,可以运用一个物理引擎库:Box2D 或者Chipmunk,但是为了保持简单,我们自己将实现一个简单的碰撞检测。

你已经把怪物,炮弹存放在数组中了。现在你要做的是定期的检查是否发生了碰撞。

为了做到这些,我们添加如下方法在onTouchesEnded函数中:

update:function (dt) {
    for (var i = 0; i < this._projectiles.length; i++) {
        var projectile = this._projectiles[i];
        for (var j = 0; j < this._monsters.length; j++) {
            var monster = this._monsters[j];
            var projectileRect = projectile.getBoundingBox();
            var monsterRect = monster.getBoundingBox();
            if (cc.rectIntersectsRect(projectileRect, monsterRect)) {
                cc.log("collision!");
                cc.ArrayRemoveObject(this._projectiles, projectile);
                projectile.removeFromParent();
                cc.ArrayRemoveObject(this._monsters, monster);
                monster.removeFromParent();                
            }
        }
    }
}

以上代码相当于做了一个清除。你不停的通过你的子弹跟怪物,生成的矩形边框,并用CGRectIntersectsRect去检查重叠。如果有重叠,你把他们从场景和数组中移除。

注意:你不要像Objective-C一样创建分离“toDelete”数组,因为通过迭代器从一个数组中移除元素在Javascript中是安全的。

在你开始射子弹前你还需要在做一件事,通过在你的onEnter方法中加入以下代码让这个方法总是被调用:

this.scheduleUpdate();

保存文件并刷新你的浏览器,现在当你的弹丸碰到目标的时候他们都会消失!

音乐和音效

现在你相当于完全了能跑的游戏了,你还需要添加一些声音效果(你还么有的声音效果)和一些简单的游戏逻辑。

首先更新Cocos2DSimpleGame\Src\resource.js添加声音效果:

var dirArt = "Art/";
var dirSounds = "Sounds/";
 
var s_player = dirArt + "player.png";
var s_monster = dirArt + "monster.png";
var s_projectile = dirArt + "projectile.png";
 
var s_bgMusic = dirSounds + "background-music.mp3";
var s_bgMusicOgg = dirSounds + "background-music.ogg";
var s_bgMusicCaf = dirSounds + "background-music.caf";
 
var s_shootEffect = dirSounds + "pew-pew-lei.mp3";
var s_shootEffectOgg = dirSounds + "pew-pew-lei.ogg";
var s_shootEffectWav = dirSounds + "pew-pew-lei.wav";
 
var g_ressources = [
 
    {type:"image", src:s_player},
    {type:"image", src:s_monster},
    {type:"image", src:s_projectile},
 
    {type:"sound", src:s_bgMusic},
    {type:"sound", src:s_bgMusicOgg},
    {type:"sound", src:s_bgMusicCaf},
 
    {type:"sound", src:s_shootEffect},
    {type:"sound", src:s_shootEffectOgg},
    {type:"sound", src:s_shootEffectWav}
 
];

注意:背景音乐和声音效果应该保存三种格式:mp3, ogg, and wav. 这是因为不是所有浏览器,都支持所有的格式,所有通过添加三种格式,我们要尽可能的让玩家能听到声音。Cocos2D将判断浏览器支持的格式,我们将添加相应的格式文件。只要他们是相同的文件名。

注意:如果你想知道如何对这些格式进行相互转换,这是我做的:

1. 我通常用一个WAV文件。

2.我然后运用iTunes去转换为一个MP3.为了做这事,去iTunes Preferences\General\Import Settings and change Import Using to MP3 Encoder。然后在iTunes工具导入一个WAV文

件,并点击右键,选择Create MP3 Version将创建一个MP3格式文件。

3. 我用oggenc工具将MP3格式转换为一个OGG,我有一个手册介绍怎么安装它。

现在,是时候添加声音效果了,回到Cocos2DSimpleGame\Src\MainLayer.js,在文件头添加如下代码:

var audioEngine = cc.AudioEngine.getInstance();

这获取一个全局引用一个声音引擎,之后你将会用到它,然后添加这行到你的onEnter函数末尾:

audioEngine.playMusic(s_bgMusic, true);

添加以下代码到locationTapped的结尾

audioEngine.playEffect(s_shootEffect);

保存文件并重新刷新浏览器,现在你应该有些音乐了。

输和赢

最后,让我们创建一个新的场景和层,该层将为你胜利或者失败提供说明。创建一个新文件Cocos2DSimpleGame\Src\GameOver.js并添加如下内容:

var GameOver = cc.LayerColor.extend({
 
    _won:false,
 
    ctor:function() {
        this._super();
        cc.associateWithNative( this, cc.LayerColor );
    },
 
    onEnter:function () {
 
        this._super();
 
        var director = cc.Director.getInstance();
        var winSize = director.getWinSize();
        var centerPos = cc.p( winSize.width/2, winSize.height/2 );
 
        var message;
        if (this._won) {
            message = "You Won!";
        } else {
            message = "You Lose :[";
        }
 
        var label = cc.LabelTTF.create(message, "Arial", 32);
        label.setColor(cc.c3b(0, 0, 0));
        label.setPosition(winSize.width/2, winSize.height/2);
        this.addChild(label);
 
        this.runAction(cc.Sequence.create(
            cc.DelayTime.create(3),
            cc.CallFunc.create(function(node) {
                var scene = MainLayer.scene();
                cc.Director.getInstance().replaceScene(scene);
            }, this)
        ));
 
    }
});
 
GameOver.create = function (won) {
    var sg = new GameOver();
    sg.won = won;
    if (sg && sg.init(cc.c4b(255, 255, 255, 255))) {
        return sg;
    }
    return null;
};
 
GameOver.scene = function (won) {
    var scene = cc.Scene.create();
    var layer = GameOver.create(won);
    scene.addChild(layer);
    return scene;
};

这是一个层包含一个标签在你的屏幕中间(cc.LabelTTF),该标签包含一些信息。创建一个动作等待三秒回到主界面。

为了使用这个新场景,回到Cocos2DSimpleGame\Src\MainLayer.js并修改一些代码:

// Add this new instance variable to the top of the file
_monstersDestroyed:0,
 
// Add inside update, right after monster.removeFromParent():
this._monstersDestroyed++;
if (this._monstersDestroyed >= 2) {
    var scene = GameOver.scene(true);
    cc.Director.getInstance().replaceScene(scene);
}
 
// Add inside addMonster, right after node.removeFromParent():
var scene = GameOver.scene(false);
cc.Director.getInstance().replaceScene(scene);

最后,当你添加了一个新文件,你需要在cocos2d.js引用它。所以打开Cocos2DSimpleGame\cocos2d.js修改如下appFiles数组:

appFiles:[
    './Src/resource.js',
    './Src/MainLayer.js',
    './Src/GameOver.js',
    './Src/main.js'
]

就是这样,保存文件,重新刷新浏览器,现在你的Cocos2D Javascript游戏完成了。

im7

还有一件事情

Cocos2D-Javascript不是一直强调跨平台吗,对么?

让我展示你看,最多5秒钟让你相同的代码怎么运行在iPhone。

好的,假设你有了最新的Coscos2D-iOS版本已经安装了,如果没有,下载最新的不稳定版 2.X version (2.1-rc0a)然后安装模板。

注意:下载最新你能安装的Cocos2D版本,在终端中运行如下命令cd~/Downloads/

cocos2d-iphone-2.1-rc0

./install-templates.sh -f -u

然后在XCode创建一个iOS\cocos2d v2.x\cocos2d iOS with JavaScript新工程模板,命名为Cocos2DSimpleGame并保存到Cocos2DSimpleGame\Platform\iOS目录下。

然后找到你的Cocos2DSimpleGame文件夹,选中Art, SoundsSrc文件夹,把他们拖到你的XCode工程中。注意:你弹出框出现时,选择Create folder references for any added folders并像如下图一样设置一些属性:

im8

如果你没有错误,你的文件夹应该是蓝色的在XCode像如下截图一样,如果你的是黄色的,你选中groups”替换,移除他们重新试一次。

im9

接下来,打开Resources\main.js并用以下内容替换:

require("jsb.js");
require("Src/resource.js");
require("Src/MainLayer.js");
require("Src/GameOver.js");
 
director = cc.Director.getInstance();
winSize = director.getWinSize();
centerPos = cc.p( winSize.width/2, winSize.height/2 );
 
function run()
{
    director.runWithScene( MainLayer.scene() );
}
 
run();

这里你加载你的游戏文件,设置全局变量,并运行主场景。

像这样,编译,运行,现在你可以相同的代码运行在你的iPhone了,但使用本机Cocso2DiPhone !

im10

注意:如果你Javascript代码是错误的,有时你会看到一个错误在Xcode console,但是大多数默认的Xcode会给你一些没有用的错误提示。

这也是其中一个我为什么选择Cocos2D-HTML5作为初学者开发的理由。

在C和Objective-C中绑定JavaScript

翻译:七夜小魔君、Jack★魏凡缤、肇庆、荣荣、crazy duck、紫夜行者

校对:u0u0

原文:JavaScript Bindings for C and Objective-C

引言

C/Objective-C语言中的JavaScript Bindings 是介于本地代码(C or Objective-C)和 JavaScript(JS)代码之间的“胶水”代码(封装代码)。JSB能实现JS和本地代码之间的 互相调用 。

这意味着你可以使用JS在你喜欢的本地库之间互相调用。例如,你可以在JS中创建一个 cocos2d的粒子系统,而它的逻辑和渲染是本地代码执行。或者你可以在JS中创建一个 Chipmunk的物理世界,而所有物理仿真,包括碰撞检测都是本地代码执行。

jsb-layer
JSB Layer

JS代码由Mozilla的JS虚拟机(VM)SpiderMonkey解析。使用SpiderMonekey最新的稳 定版本(本文使用的是v14.0.1)。JS VM通过JSB扩展支持自定义类型,自定义结构体和 Objective-C对象。

JSB有一套灵活的重命名规则用来设定哪些类、方法、函数和结构体将被解析或忽略,并确 定哪些方法是回调。为了方便的创建这些规则,它支持正则表达式。

这项技术正被Zynga公司用在游戏的快速开发和原型设计中。现在,让我们在cocos2d-iphone,Chipmunk和CoCosBuilder Reader上使用它。

主要特点

JSB的亮点:

  • 支持Objective-C/C的任何库
  • 自动生成JS Bindings(“胶水”代码)
  • 不需要修改库的源代码,或自动生成的代码
  • 有一套强大的规则可定制JS API 的生成
  • 自动在JS对象/类型 和 Objective-C对象/结构体/类型 之间相互转换
  • 在JS中支持本地对象的子类
  • 支持回调

创建自己的Bindings

简明步骤
1.下载JSB(git下载地址)

$git clone git://github.com/zynga/jsbindings.git

2.生成BridgeSupport 文件,假设项目名是CocosDenshion

$cd ~/src/CocosDenshion/CocosDenshion
$gen_bridge_metadata -F complete --no-64-bit -c '-DNDEBUG -I.' *.h -o ~/some/path/CocosDenshion.bridgesupport

3.生成补充文件

$cd ~/src/CocosDenshion/CocosDenshion
$~/jsb/generate_complement.py -o ~/some/path/CocosDenshion-complement.txt *.h

4.创建JSB配置文件

$vim ~/some/path/CocosDenshion_jsb.ini

5.运行JSB生成脚本

$~/jsb/generate_js_bindings.py -c ~/som/path/CocosDenshion_jsb.ini

6.导入刚才自动生成的文件和JSB源码文件到你的xcode项目中

7.导入SpiderMonkey和JRSwizzle到你的xcode项目
所有文件能在这里找到:

https://github.com/zynga/jsbindings/tree/master/configs/CocosDenshion
详细步骤

JSB中有一个名为generate_js_bindings.py的脚本, 这个脚本会生成“胶水”代码。脚本需 要一个配置文件,其中包含解析规则和BridgeSupport文件。

BridgeSupport文件由OS X下的gen_bridge_metada脚本生成,生成的是一些xml文件包 含了类名,方法名,参数,返回值,内部结构体,常量等信息.

gen_bridge_metada,内部使用clang 解析本地代码。输出的内容是非常可靠的,但不幸的 是,它不完整:丢失了类继承关系、协议和属性信息。这就是为什么JSB包含另一个python脚本generate_js_complement.py, 它生成丢失的信息。

当配置文件准备好,就可以运行generate_js_bindings.py 脚本生成“胶水”代码。

总结,JSB配置文件的结构是

  • 解析规则(可选):重命名规则、类的解析/忽略 等...
  • BridgeSupport文件(必需):类、方法、函数、结构信息
  • 补充文件(Objective - CGON):继承结构、协议和属性信息

index
“glue” code generation

配置规则

有一套强大的配置规则可以转换本地API到自定义的JS API。让我们看看其中一些规则.

重命名规则:
重命名规则允许我们重命名方法名,类名,函数名或结构名。下面示例代码默认的JS API:

// CCAnimation (from cocos2d-iphone v2.0)
+(id) animationWithAnimationFrames:(NSArray*)arrayOfAnimationFrames delayPerUnit:(float)delayPerUnit loops:(NSUInteger)loops;

将是:

// ugly
cc.CCAnimation.animationWithAnimationFrames_delayPerUnit_loops_( frames, delay, loops );

我们可以利用一套简单的规则把这个JS API命名为如下所示的函数:

// more JS friendly
cc.Animation.create( frames, delay, loops );

为了做到这一点,我们要把前缀CC从类名中移除, 因为它已经使用了名为cc的JS名字空间。
obj_class_prefix_to_remove = CC
最后,我们添加一个方法的命名规则:
method_properties = CCAnimation # animationWithAnimationFrames:delayPerUnit:loops: = name:"create",

合并规则:
但是CCAnimation的其它构造函数会发生什么呢?

// CCAnimation supports 4 different constructors
+(id) animationWithSpriteFrames:(NSArray*)arrayOfSpriteFrameNames;
+(id) animationWithSpriteFrames:(NSArray*)arrayOfSpriteFrameNames delay:(float)delay;
+(id) animationWithAnimationFrames:(NSArray*)arrayOfAnimationFrames delayPerUnit:(float)delayPerUnit loops:(NSUInteger)loops;

我们应该做的是创建一个规则,把四个构造函数合并为一个。JSB会根据参数的个数正确 执行. 这个规则如下所示:
method_properties = CCAnimation # animationWithAnimationFrames:delayPerUnit:loops: = name:"create"; merge: "animation" | "animationWithSpriteFrames:" | "animationWithSpriteFrames:delay:",

JS的代码将看起来像这样:

// calls [CCAnimation animation]
var anim = cc.Animation.create();
// calls [CCAnimation animnationWithSpriteFrames:]
var anim = cc.Animation.create(array);
// calls [CCAnimation animnationWithSpriteFrames:delay:]
var anim = cc.Animation.create(array, delay);
// calls [CCAnimation animationWithAnimationFrames:delayPerUnit:loops:]
var anim = cc.Animation.create(array, delay, loops);

回调规则:
JSB支持回调方法,为了把一个函数注册为回调函数,你需要在配置文件中添加一条回调 规则. 例如:
method_properties = CCNode#onEnter = callback,

你也可以重命名回调函数:
method_properties = CCLayer # ccTouchesBegan:withEvent: = callback; name:"onTouchesBegan",

示例配置:
进一步学习了解配置文件, 可参考下面的例子。

 

JS Bingdings内幕(“胶水”代码)

JS代码绑定允许本地代码和JS代码互相调用。让我们来看看细节:

JS调用本地函数
下面的代码将调用本地C函数ccpAdd():

var p1 = cc.p(0,0);
var p2 = cc.p(1,1);
// cc.pAdd is a "wrapped" function, and it will call the cocos2d ccpAdd() C function
var ret = cc.pAdd(p1, p2);

让我们看看本地ccpAdd的声明

CGPoint ccpAdd(const CGPoint v1, const CGPoint v2);

当执行cc.pAdd时,它会调用“粘合剂”的功能代码JSB_ccpAdd。JSB_ccpAdd做了以下事情:

  • 把JS参数转换为本地参数
  • 调用本地的ccpAdd()函数
  • 把本地的返回值转换成JS的返回值
  • 转换参数或返回值时 如果遇到错误,则失败

function-call-flow
function call flow

JS调用本地实例/类方法
可以从JS调用实例或类的方法。内部逻辑和调用本机函数相似。让我们来看看:

//创建一个精灵并设置其位置为200,200
var sprite = cc.Sprite.create('image.png');
sprite.setPosition( cc.p(200,200) );
cc.Sprite.create("image.png")

将调用“胶水”函数
JSB_CCSprite_spriteWithFile_rect__static
做了以下事情:

  • JS字符串转换成本地字符串
  • 通过调用 [CCSprite spriteWithFile:@"image.png"]创建一个本地实例
  • 本地实例转换成JS对象
  • 新创建的实例添加到字典,使用JS对象作为key
  • 返回转换后的JS实例

sprite.setPosition(cc.p(200,200)) 将调用“胶水”函数JSB_CCNode_setPosition_,
做了以下事情:

  • 通过JS对象作为key,从字典中获取本机实例。
  • JS对象转换成CGPoint
  • 调用[instance setPosition:p]
  • setPosition没有返回值,它返回一个“void”对象给JS

class-instantiation-flow
class instantiation flow
instance-method-flow
instance method flow

从本地调用JS代码
回调函数
可以生成两种类型的回调:

  • 在JS中触发回调而在本地执行(前面提到过)
  • 在本地触发回调并执行JS代码。也就是所谓的”回调”

例如,cocos2d-iphone的onEnter,onExit,update回调函数。onEnter在本地触发,JS中 可重写这个方法. 在下面这个例子里,JS函数onEnte会被本地代码调用:

var MyLayer = cc.Layer.extend({
ctor:function () {
cc.associateWithNative( this, cc.Layer );
this.init();
},

onEnter:function() {
cc.log("onEnter called");
},
});

JSB支持回调,且不需要修改被解析库的源代码。JSB要做的是,把原始回调方法和JSB回调方法交换。

完整的流程:

  • JSB注册所有交换方法为回调
  • 在交换方法调用时:
    *调用本地回调函数
    *然后调用JS回调函数(如果有)

callbacks-flow
callbacks flow

脚本执行:

本地代码调用JS脚本,方法如下:

// JSBCore is responsible registering the native objects in JS, among other things
if( ! [[JSBCore sharedInstance] runScript:@"my_js_script.js"] )
  NSLog(@"Error running script");

谁在使用JSB?
JSB已成功应用于下列开源项目:

cocos2d-iphone的开发分支有3个demo工程使用了JSB:

  • JS Tests: cocos2d JS bindings tests
  • JS Watermelon With Me: a simple physics game that uses JS bindings for cocos2d, Chipmunk, CocosDenshion and CocosBuilder Reader.
  • JS MoonWarriors: A top-down shooter that uses JS bindings for cocos2d

值得一提的是,得益于一套强大的规则,cocos2d-iphone生成的JS API和
cocos2d-HTML5 API是相同的。
Moon Warriors 可以使用cocos2d­iphone+JSB 或 cocos2d­html5运行,无需修改一行代码. 试试web版本:

Moon Warriors Web

Bugs/局限性
这篇博章时,JSB有下面的bugs或局限。最新清单请访问JSB Homepage.

  • 没有JS调试器。SpiderMonKey 15发布后,会添加远程调试功能。
  • 没有JS分析工具
  • 本地对象控制JS对象的生命周期
    这意味着本机对象被释放了,而JS对象仍然存在。
    在某些情况下JS对象可能指向一个已经被释放的本机对象, 从而产生逻辑错误。
  • 回调函数不支持返回值. 支持参数有限。
  • 脚本无法解析BridgeSupport常量。
  • 避免使用OS X 10.6(或更老)系统的gen_bridge_metada。建议使用OS X 10.8(Mountain Lion)的gen_bridge_metada来生成BridgeSupport文件。
  • 从头做一个新项目是不容易的。Xcode模板即将来临。在此之际,更简单的方法就是通过复制JS“targets”来绑定cocos2d-iphone v2.1。

cocos2d-iPhone官方文档

原文地址:http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:index
翻译:sile(泰然翻译组
sile:在子龙的鼓励下翻译了这个系列的文章,也算是为自己和初学者提供一个方便学习的机会,自己一直是搞acm的,第一次接触项目之类的文章,翻译不好的地方还希望大家指出,我好改正,谢谢大家了。

一、指导前言:

cocos2d编程入门指导提供富含深度的文档,专来为那些使用cocos2d开发应用的人提供参考,这里讨论的许多主题同时也参考cocos2dAPI,它们之间都是相互独立的。

如果你是第一次阅读cocos2d,我们建议你从新手入门开始。

二、范围:

这些文件对v0.99x及其以后版本都适用,如果你发现了任何错误,请联系我们,谢谢!

三、新手指导:

四、基本概念:

五、动作、转换和实现

六、精灵

七、标签(label)和字型

八、瓦片地图(tildedMap)(已完成)

九、粒子(Particles) (已完成)

十、绘制与更新 Draw & Update(已完成)

十一、整合cocos2d和社交网络High Score/Social Networks(已完成)

十二、导演Director(已完成)

十三、cocos2d与UIKit的控制Autorotation(已完成)

十四、retina显示How to create a RetinaDisplay App(已完成)

十五、最佳实践Best_practices(已完成)

十六、样例/测试samples and tests(已完成)

 

如何优化cocos2d程序的内存使用和程序大小:PART-2(完)

本文由子龙山人翻译,转载请注明原出处!!

前言:从上周发布教程的微博反应情况来看,cocos2der们对于游戏的内存问题还是非常关心的。本文是上一篇博文的续,旨在教大家如何减少cocos2d程序的大小。

 

全文如下:

减少你的程序的大小

把纹理的颜色位深度减少到16位,不仅可以减少内存压力,还可以有效地减少程序的体积。但是,我们还有其它方法可以更进一步地减少程序的大小。

 

TexturePacker PNG 图片优化

如果你有某些原因,让你坚持要使用PNG文件格式而不是我之前极力向你推荐的pvr.ccz文件格式,那么TexturePacker有一个选项,叫做“Png Opt Level”(Png优化级别),可以帮助我们减少png文件的大小(注意:这样并不会影响图片加载时间)


阅读全文»

如何优化cocos2d程序的内存使用和程序大小:part-1

本文由子龙山人翻译,转载请注明原出处!!

译者:

在我完成第一个游戏项目的时候,我深切地意识到“使用cocos2d来制作游戏的开发者们,他们大多会被cocos2d的内存问题所困扰”。而我刚开始接触cocos2d的时候,社区里面的人们讨论了一个非常有意义的话题:“请简单地讲述你认为新手cocos2d程序员在他开始编码之前,最应该先知道,或者应该关注和注意的事项。”这个问题的答案很多,有人讲是“如何加载和保存游戏数据”,有人讲的是“如何实现有限状态机”等等。而最吸引我的则是,有一个人讲到,新手cocos2d程序员或者新手cocoa程序,他们所遇到的80%的问题都与内存相关。

因为有着c/c++背景的我,看到这句话的时候,很是赞同,因此刚开始cocos2d编程的时候格外注意内存方面的问题。即便如此,在我完成自己第一个游戏的过程中,还是遇到了大量的内存问题,它们让我头疼,让我睡不着觉。庆幸的是,我通过社区都找到了答案并且解决了我的问题。

我在《我的第一个游戏FoodieTheBug完成之后的几点心得体会》这篇博文中也讲述过一些内存方面的使用心得。但是,不够具体,我当时想讲的内容有很多。因为有些难以用文字具象化,我也就偷了一回懒了。这次,当我看到Steffen Itterheim写了两篇这么经典的优化cocos2d内存使用和程序大小的文章之后,我有一种“于我心有戚戚焉”的感觉。我迫不及待地想跟大家分享,可惜很多人抱怨说访问不了,被墙了等等。可能也有一些同行,对E文不是很感冒。趁着周末,我花一个下午的时间,给大家翻译一下,与大家共勉。

全文如下:

我目前正完成我的最后一个合约项目。在这个项目的最后阶段,我需要考虑的一件事情就是如何优化游戏的内存使用。

在今天的iDevBlogADay文章中,我将向大家讲述,我是如何减少25-30MB游戏内存消耗的(现在游戏消耗内存90-95MB,我还通过这个过程,消除了一些由于内存警告而引起的程序崩溃问题)。同时,我还将游戏程序的大小从25MB减少到了20MB以下(如果苹果没有在不久前将蜂窝网下载应用的限制从20MB提高到50MB的话,那么我这个小的优化就太棒了,它可以潜在地给我带来更多的下载量)。

我还会给大家介绍,如何在你加载游戏资源的时候展示一个带有动画的Loading界面,我还会加入一些最佳实践和小技巧。

阅读全文»

如何在使用Cocos2D中实现A星(A*)寻路算法

这篇blog是由iOS Tutorial Team的成员  Johann Fradj发表的,他目前是一位全职的资深iOS开发工程师。他还是Hot Apps Factory的创始人,该公司开发了App Cooker

Add the A* Pathfinding Algorithm to this simple Cocos2D game!
添加A星寻路算法到简单的Cocos2D游戏中!

在本篇教程中,你将学到如何在一款简单的cocos2D游戏中使用A星寻路算法。

在学习本篇教程之前,如果你先阅读《A星(A*)寻路算法在iOS开发的运用》将会有所帮助。该文章图文并茂的介绍了这个我们将要实现的算法的基本概念。

在学习本篇教程之前,如果你有Cocos2D的iOS开发经验,将会有所帮助。如果没有也没关系,因为你可以将这里讲解的例子迁移到其他的语言或者框架中。

找到到达你键盘的最短路径,开始吧!:]

阅读全文»

如何使用cocos2d制作类似DNF的2D横版格斗过关游戏 PART-2

本文由eseedo翻译。

原译文:http://blog.sina.com.cn/s/blog_4b55f6860101aaav.html

原文参考:http://www.raywenderlich.com/24452/how-to-make-a-side-scrolling-beat-em-up-game-like-scott-pilgrim-with-cocos2d-part-2

欢迎继续之前的横版过关游戏教程系列,这一部分将是该系列的第二部分,同时也是终结篇。

在第一部分的内容里面,我们已经创建了会发呆和挥动拳头的英雄,并且把D-pad方向键摆在了游戏的左下角。

而在这部分的内容中,我们将会学到更多东西,比如添加运动,卷轴滚动,碰撞,敌人,AI,当然还有令人热血沸腾的音乐和音效。

在开始学习这部分的内容之前,请先下载第一部分结束时的项目示例代码(http://cdn1.raywenderlich.com/downloads/PompaDroidPart1.zip)。

同时别忘了下载项目所需的资源文件(http://cdn4.raywenderlich.com/downloads/pd_resources.zip)。

准备好了吗?来吧,让我们干掉这些可恶的androids机器人!哦,不好意思,口误,是droid机器人。

如何使用cocos2d制作类似Scott <wbr>Pilgrim的2D横版格斗过关游戏part2(翻译)

阅读全文»

如何使用cocos2d制作类似DNF的2D横版格斗过关游戏 PART-1

原文参考:http://www.raywenderlich.com/24155/how-to-make-a-side-scrolling-beat-em-up-game-like-scott-pilgrim-with-cocos2d-part-1

感谢本文原作者:来自raywenderlich的iOS教程团队的成员Allen Tan,同时也是http://www.whitewidget.com的创始人。

本文由eseedo翻译。

原译文地址:http://blog.sina.com.cn/s/blog_4b55f6860101a9b7.html

首先来个背景简介吧,除了游戏高玩,可能很多开发者并不知道Scott Pilgrim这款游戏。实际上它是改编自经典漫画Scott Pilgrim vs. the World,由Ubisoft开发,最初发行在Xbox live上。该系列漫画的详细信息请参考维基百科:http://en.wikipedia.org/wiki/Scott_Pilgrim

好吧,如果你还是对这类游戏毫无头绪,想想曾经玩过的三国无双,还有,。。。 DNF,是的,就DNF这种类似街机的格斗过关游戏。如果连DNF也不知道,那你来看这篇教程干毛?

如何使用cocos2d制作类似Scott <wbr>Pilgrim的2D横版格斗过关游戏part1(翻译)

阅读全文»

?>