Archive for the 'Cocos2D' Category

Cocos2d notes(7) physics engines

星期五, 六月 17th, 2011

Chipmunk SpaceManager
Chipmunk SpaceManager Documentation
Chipmunk Physics
Chipmunk Documentation
Box2D Documents
Box2D API Reference

Box2D

header定义,几乎没变化,只是增加了Box2D的头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import "cocos2d.h"
#import "Box2D.h"
#import "GLES-Render.h"

enum
{
    kTagBatchNode,
};

@interface HelloWorld : CCLayer
{
    b2World* world;
}

// returns a Scene that contains the HelloWorld as the only child
+(id) scene;

@end

初始化Box2D世界
b2Vec2 gravity = b2Vec2(0.0f, -10.0f);
bool allowBodiesToSleep = true;
world = new b2World(gravity, allowBodiesToSleep);

销毁时

1
2
3
4
5
-(void) dealloc
{
    delete world;
    [super dealloc];
}

关于什么是Sleeping body

It’s a trick that allows the physics simulation to quickly skip over
objects that do not need processing. A dynamic body goes to sleep when the forces
applied to it have been below a threshold for a certain amount of time. In other words, if
268 CHAPTER 12: Physics Engines
the dynamic body is moving and rotating very slowly or not at all, the physics engine will
flag it as sleeping and won’t apply forces to it anymore—that is, unless an impulse or force
applied to the body is strong enough to make the body move or rotate again. This trick
allows the physics engine to save time by not processing the bodies that are at rest.
Unless all of your game’s dynamic bodies are in constant motion, you should enable this
feature by setting the allowBodiesToSleep variable to true,

限制对象运动的屏幕
(Box2D的单位全是公制单位,即长度用米,重量用千克,时间用秒)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Define the static container body, which will provide the collisions at screen borders.
b2BodyDef containerBodyDef;
b2Body* containerBody = world->CreateBody(&containerBodyDef);

// for the ground body we'll need these values
// PTM pixel转换成公制长度的产量
CGSize screenSize = [CCDirector sharedDirector].winSize;
float widthInMeters = screenSize.width / PTM_RATIO;
float heightInMeters = screenSize.height / PTM_RATIO;
b2Vec2 lowerLeftCorner = b2Vec2(0, 0);
b2Vec2 lowerRightCorner = b2Vec2(widthInMeters, 0);
b2Vec2 upperLeftCorner = b2Vec2(0, heightInMeters);
b2Vec2 upperRightCorner = b2Vec2(widthInMeters, heightInMeters);

// Create the screen box' sides by using a polygon assigning each side individually.
b2PolygonShape screenBoxShape;
int density = 0;

// bottom
screenBoxShape.SetAsEdge(lowerLeftCorner, lowerRightCorner);
containerBody->CreateFixture(&screenBoxShape, density);

// top
screenBoxShape.SetAsEdge(upperLeftCorner, upperRightCorner);
containerBody->CreateFixture(&screenBoxShape, density);

// left side
screenBoxShape.SetAsEdge(upperLeftCorner, lowerLeftCorner);
containerBody->CreateFixture(&screenBoxShape, density);

// right side
screenBoxShape.SetAsEdge(upperRightCorner, lowerRightCorner);
containerBody->CreateFixture(&screenBoxShape, density);

#define PTM_RATIO 32
代表32像素代表Box2D中的1米

转换

1
2
3
4
5
6
7
8
-(b2Vec2) toMeters:(CGPoint)point
{
    return b2Vec2(point.x / PTM_RATIO, point.y / PTM_RATIO);
}
-(CGPoint) toPixels:(b2Vec2)vec
{
    return ccpMult(CGPointMake(vec.x, vec.y), PTM_RATIO);
}

创建Box2D Sprite

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Use the orthogonal tileset for the little boxes
CCSpriteBatchNode* batch = [CCSpriteBatchNode batchNodeWithFile:@"dg_grounds32.png" capacity:TILESET_ROWS * TILESET_COLUMNS];
[self addChild:batch z:0 tag:kTagBatchNode];

// Add a few objects initially
for (int i = 0; i < 11; i++)
{
    [self addNewSpriteAt:CGPointMake(screenSize.width / 2, screenSize.height / 2)];
}

[self scheduleUpdate];

self.isTouchEnabled = YES;

-(void) addNewSpriteAt:(CGPoint)pos
{
    CCSpriteBatchNode* batch = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];
   
    int idx = CCRANDOM_0_1() * TILESET_COLUMNS;
    int idy = CCRANDOM_0_1() * TILESET_ROWS;
    CGRect tileRect = CGRectMake(TILESIZE * idx, TILESIZE * idy, TILESIZE, TILESIZE);
    CCSprite* sprite = [CCSprite spriteWithBatchNode:batch rect:tileRect];
    sprite.position = pos;
    [batch addChild:sprite];
   
    // Create a body definition and set it to be a dynamic body
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;

    // position must be converted to meters
    bodyDef.position = [self toMeters:pos];
   
    // assign the sprite as userdata so it's easy to get to the sprite when working with the body
    bodyDef.userData = sprite;
    b2Body* body = world->CreateBody(&bodyDef);
   
    // Define another box shape for our dynamic body.
    b2PolygonShape dynamicBox;
    float tileInMeters = TILESIZE / PTM_RATIO;
    dynamicBox.SetAsBox(tileInMeters * 0.5f, tileInMeters * 0.5f);
   
    // Define the dynamic body fixture.
    b2FixtureDef fixtureDef;
    fixtureDef.shape = &dynamicBox;
    fixtureDef.density = 0.3f;
    fixtureDef.friction = 0.5f;
    fixtureDef.restitution = 0.6f;
    body->CreateFixture(&fixtureDef);
}

让sprite动起来
事实上,通过以上代码还不能让每个sprite运动起来,还是需要通过sheduleUpdate触发每次计算world里存在的各个body,计算一定timeStep时间内他们的运动情况,然后给sprite设置属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-(void) update:(ccTime)delta
{
    // The number of iterations influence the accuracy of the physics simulation. With higher values the
    // body's velocity and position are more accurately tracked but at the cost of speed.
    // Usually for games only 1 position iteration is necessary to achieve good results.
    float timeStep = 0.03f;
    int32 velocityIterations = 8;
    int32 positionIterations = 1;
    world->Step(timeStep, velocityIterations, positionIterations);
   
    // for each body, get its assigned sprite and update the sprite's position
    for (b2Body* body = world->GetBodyList(); body != nil; body = body->GetNext())
    {
        CCSprite* sprite = (CCSprite*)body->GetUserData();
        if (sprite != NULL)
        {
            // update the sprite's position to where their physics bodies are
            sprite.position = [self toPixels:body->GetPosition()];
            float angle = body->GetAngle();
            sprite.rotation = CC_RADIANS_TO_DEGREES(angle) * -1;
        }  
    }
}

碰撞检测
* 如果把.m文件后缀改成.mm则可以在其中同时使用objc和c++语法,box2d是c++写的,所以。。

b2ContactListener类支持两个事件,注意一下都是c++代码了
ContactListener.h

1
2
3
4
5
6
7
8
#import "Box2D.h"

class ContactListener : public b2ContactListener
{
private:
    void BeginContact(b2Contact* contact);
    void EndContact(b2Contact* contact);
};

.mm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#import "ContactListener.h"
#import "cocos2d.h"

void ContactListener::BeginContact(b2Contact* contact)
{
    b2Body* bodyA = contact->GetFixtureA()->GetBody();
    b2Body* bodyB = contact->GetFixtureB()->GetBody();
    CCSprite* spriteA = (CCSprite*)bodyA->GetUserData();
    CCSprite* spriteB = (CCSprite*)bodyB->GetUserData();
   
    if (spriteA != NULL && spriteB != NULL)
    {
        spriteA.color = ccMAGENTA;
        spriteB.color = ccMAGENTA;
    }
}


void ContactListener::EndContact(b2Contact* contact)
{
    b2Body* bodyA = contact->GetFixtureA()->GetBody();
    b2Body* bodyB = contact->GetFixtureB()->GetBody();
    CCSprite* spriteA = (CCSprite*)bodyA->GetUserData();
    CCSprite* spriteB = (CCSprite*)bodyB->GetUserData();
   
    if (spriteA != NULL && spriteB != NULL)
    {
        spriteA.color = ccWHITE;
        spriteB.color = ccWHITE;
    }
}

在layer中加入listener

1
2
contactListener = new ContactListener();
world->SetContactListener(contactListener);

Joint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Create a body definition and set it to be a dynamic body
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;

// position must be converted to meters
bodyDef.position = [self toMeters:pos];
bodyDef.position = bodyDef.position + b2Vec2(-1, -1);
bodyDef.userData = [self addRandomSpriteAt:pos];
b2Body* bodyA = world->CreateBody(&bodyDef);
[self bodyCreateFixture:bodyA];

bodyDef.position = [self toMeters:pos];
bodyDef.userData = [self addRandomSpriteAt:pos];
b2Body* bodyB = world->CreateBody(&bodyDef);
[self bodyCreateFixture:bodyB];

bodyDef.position = [self toMeters:pos];
bodyDef.position = bodyDef.position + b2Vec2(1, 1);
bodyDef.userData = [self addRandomSpriteAt:pos];
b2Body* bodyC = world->CreateBody(&bodyDef);
[self bodyCreateFixture:bodyC];

b2RevoluteJointDef jointDef;
jointDef.Initialize(bodyA, bodyB, bodyB->GetWorldCenter());
bodyA->GetWorld()->CreateJoint(&jointDef);

jointDef.Initialize(bodyB, bodyC, bodyC->GetWorldCenter());
bodyA->GetWorld()->CreateJoint(&jointDef);

// create an invisible static body to attach to
bodyDef.type = b2_staticBody;
bodyDef.position = [self toMeters:pos];
b2Body* staticBody = world->CreateBody(&bodyDef);
jointDef.Initialize(staticBody, bodyA, bodyA->GetWorldCenter());
bodyA->GetWorld()->CreateJoint(&jointDef);

Chipmunk

.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "cocos2d.h"
#import "chipmunk.h"

enum
{
    kTagBatchNode = 1,
};

@interface HelloWorld : CCLayer
{
    cpSpace* space;
}

+(id) scene;

@end

space等同于box2d中的world

初始化

1
2
3
4
5
cpInitChipmunk();

space = cpSpaceNew();
space->iterations = 8;
space->gravity = CGPointMake(0, -100);

销毁

1
2
3
4
5
-(void) dealloc
{
    cpSpaceFree(space);
    [super dealloc];
}

设置ground body(容器)

1
2
3
4
5
6
7
8
9
10
11
// for the ground body we'll need these values
CGSize screenSize = [CCDirector sharedDirector].winSize;
CGPoint lowerLeftCorner = CGPointMake(0, 0);
CGPoint lowerRightCorner = CGPointMake(screenSize.width, 0);
CGPoint upperLeftCorner = CGPointMake(0, screenSize.height);
CGPoint upperRightCorner = CGPointMake(screenSize.width, screenSize.height);

// Create the static body that keeps objects within the screen area
float mass = INFINITY;
float inertia = INFINITY;
cpBody* staticBody = cpBodyNew(mass, inertia);

创建容器边缘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cpShape* shape;
float elasticity = 1.0f;
float friction = 1.0f;
float radius = 0.0f;

// bottom
shape = cpSegmentShapeNew(staticBody, lowerLeftCorner, lowerRightCorner, radius);
shape->e = elasticity;
shape->u = friction;
cpSpaceAddStaticShape(space, shape);

// top
shape = cpSegmentShapeNew(staticBody, upperLeftCorner, upperRightCorner, radius);
shape->e = elasticity;
shape->u = friction;
cpSpaceAddStaticShape(space, shape);

// left
shape = cpSegmentShapeNew(staticBody, lowerLeftCorner, upperLeftCorner, radius);
shape->e = elasticity;
shape->u = friction;
cpSpaceAddStaticShape(space, shape);

// right
shape = cpSegmentShapeNew(staticBody, lowerRightCorner, upperRightCorner, radius);
shape->e = elasticity;
shape->u = friction;
cpSpaceAddStaticShape(space, shape);

添加Sprite

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-(CCSprite*) addRandomSpriteAt:(CGPoint)pos
{
    CCSpriteBatchNode* batch = (CCSpriteBatchNode*)[self getChildByTag:kTagBatchNode];
   
    int idx = CCRANDOM_0_1() * TILESET_COLUMNS;
    int idy = CCRANDOM_0_1() * TILESET_ROWS;
    CGRect tileRect = CGRectMake(TILESIZE * idx, TILESIZE * idy, TILESIZE, TILESIZE);
    CCSprite* sprite = [CCSprite spriteWithBatchNode:batch rect:tileRect];
    sprite.position = pos;
    [batch addChild:sprite];
   
    return sprite;
}

-(void) addNewSpriteAt:(CGPoint)pos
{
    float mass = 0.5f;
    float moment = cpMomentForBox(mass, TILESIZE, TILESIZE);
    cpBody* body = cpBodyNew(mass, moment);
   
    body->p = pos;
    cpSpaceAddBody(space, body);
   
    float halfTileSize = TILESIZE * 0.5f;
    int numVertices = 4;
    CGPoint vertices[] =
    {
        CGPointMake(-halfTileSize, -halfTileSize),
        CGPointMake(-halfTileSize, halfTileSize),
        CGPointMake(halfTileSize, halfTileSize),
        CGPointMake(halfTileSize, -halfTileSize),
    };

    CGPoint offset = CGPointZero;
    float elasticity = 0.3f;
    float friction = 0.7f;
   
    cpShape* shape = cpPolyShapeNew(body, numVertices, vertices, offset);
    shape->e = elasticity;
    shape->u = friction;
    shape->data = [self addRandomSpriteAt:pos];
    cpSpaceAddShape(space, shape);
}

update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// C method that updates sprite position and rotation:
static void forEachShape(void* shapePointer, void* data)
{
    cpShape* shape = (cpShape*)shapePointer;
    CCSprite* sprite = (CCSprite*)shape->data;
    if (sprite != nil)
    {
        cpBody* body = shape->body;
        sprite.position = body->p;
        sprite.rotation = CC_RADIANS_TO_DEGREES(body->a) * -1;
    }
}

-(void) update:(ccTime)delta
{
    float timeStep = 0.03f;
    cpSpaceStep(space, timeStep);
   
    // call forEachShape C method to update sprite positions
    cpSpaceHashEach(space->activeShapes, &forEachShape, nil);
    cpSpaceHashEach(space->staticShapes, &forEachShape, nil);
}

检测碰撞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// C callback methods for collision handling
static int contactBegin(cpArbiter* arbiter, struct cpSpace* space, void* data)
{
    bool processCollision = YES;
   
    cpShape* shapeA;
    cpShape* shapeB;
    cpArbiterGetShapes(arbiter, &shapeA, &shapeB);
   
    CCSprite* spriteA = (CCSprite*)shapeA->data;
    CCSprite* spriteB = (CCSprite*)shapeB->data;
    if (spriteA != nil && spriteB != nil)
    {
        spriteA.color = ccMAGENTA;
        spriteB.color = ccMAGENTA;
    }
   
    return processCollision;
}

static void contactEnd(cpArbiter* arbiter, cpSpace* space, void* data)
{
    cpShape* shapeA;
    cpShape* shapeB;
    cpArbiterGetShapes(arbiter, &shapeA, &shapeB);
   
    CCSprite* spriteA = (CCSprite*)shapeA->data;
    CCSprite* spriteB = (CCSprite*)shapeB->data;
    if (spriteA != nil && spriteB != nil)
    {
        spriteA.color = ccWHITE;
        spriteB.color = ccWHITE;
    }
}

添加listener

1
2
3
4
        // Add the collision handlers
        unsigned int defaultCollisionType = 0;
        cpSpaceAddCollisionHandler(space, defaultCollisionType, defaultCollisionType,
                                   &contactBegin, NULL, NULL, &contactEnd, NULL);

Cocos2d notes(6) Tiled Map

星期三, 六月 15th, 2011

相关工具

1
2
3
4
5
6
7
8
9
        CCTMXTiledMap* tileMap = [CCTMXTiledMap tiledMapWithTMXFile:@"orthogonal.tmx"];
        [self addChild:tileMap z:-1 tag:TileMapNode];
       
        // Use a negative offset to set the tilemap's start position
        //tileMap.position = CGPointMake(-160, -120);

        // hide the event layer, we only need this information for code, not to display it
        CCTMXLayer* eventLayer = [tileMap layerNamed:@"GameEventLayer"];
        eventLayer.visible = NO;

判断区域–1
获得map上的坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
-(CGPoint) locationFromTouch:(UITouch*)touch
{
    CGPoint touchLocation = [touch locationInView: [touch view]];
    return [[CCDirector sharedDirector] convertToGL:touchLocation];
}

-(CGPoint) locationFromTouches:(NSSet*)touches
{
    return [self locationFromTouch:[touches anyObject]];
}

-(CGPoint) tilePosFromLocation:(CGPoint)location tileMap:(CCTMXTiledMap*)tileMap
{
    // Tilemap position must be subtracted, in case the tilemap position is not at 0,0 due to scrolling
    CGPoint pos = ccpSub(location, tileMap.position);
   
    // Cast to int makes sure that result is in whole numbers, tile coordinates will be used as array indices
    pos.x = (int)(pos.x / tileMap.tileSize.width);
    pos.y = (int)((tileMap.mapSize.height * tileMap.tileSize.height - pos.y) / tileMap.tileSize.height);
   
    CCLOG(@"touch at (%.0f, %.0f) is at tileCoord (%i, %i)", location.x, location.y, (int)pos.x, (int)pos.y);
    NSAssert(pos.x >= 0 && pos.y >= 0 && pos.x < tileMap.mapSize.width && pos.y < tileMap.mapSize.height,
             @"%@: coordinates (%i, %i) out of bounds!", NSStringFromSelector(_cmd), (int)pos.x, (int)pos.y);
   
    return pos;
}

-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    CCNode* node = [self getChildByTag:TileMapNode];
    NSAssert([node isKindOfClass:[CCTMXTiledMap class]], @"not a CCTMXTiledMap");  
    CCTMXTiledMap* tileMap = (CCTMXTiledMap*)node;

    // get the position in tile coordinates from the touch location
    CGPoint touchLocation = [self locationFromTouches:touches];
    CGPoint tilePos = [self tilePosFromLocation:touchLocation tileMap:tileMap];
   
    // Check if the touch was on water (eg. tiles with isWater property drawn in GameEventLayer)
    bool isTouchOnWater = NO;
    CCTMXLayer* eventLayer = [tileMap layerNamed:@"GameEventLayer"];
    int tileGID = [eventLayer tileGIDAt:tilePos];
   
    if (tileGID != 0)
    {
        NSDictionary* properties = [tileMap propertiesForGID:tileGID];
        if (properties)
        {
            CCLOG(@"NSDictionary 'properties' contains:\n%@", properties);
            NSString* isWaterProperty = [properties valueForKey:@"isWater"];
            isTouchOnWater = ([isWaterProperty boolValue] == YES);
        }
    }
   
    // decide what to do depending on where the touch was ...
    if (isTouchOnWater)
    {
        [[SimpleAudioEngine sharedEngine] playEffect:@"alien-sfx.caf"];
    }
    else
    {
        // get the winter layer and toggle its visibility
        CCTMXLayer* winterLayer = [tileMap layerNamed:@"WinterLayer"];
        winterLayer.visible = !winterLayer.visible;
       
        // remove the touched tile
        //[winterLayer removeTileAt:tilePos];
       
        // adds a given tile
        //tileGID = [winterLayer tileGIDAt:CGPointMake(0, 19)];
        //[winterLayer setTileGID:tileGID at:tilePos];
    }
}

判断区域–2

1
2
3
4
5
6
7
8
9
10
-(CGRect) getRectFromObjectProperties:(NSDictionary*)dict tileMap:(CCTMXTiledMap*)tileMap
{
    float x, y, width, height;
    x = [[dict valueForKey:@"x"] floatValue] + tileMap.position.x;
    y = [[dict valueForKey:@"y"] floatValue] + tileMap.position.y;
    width = [[dict valueForKey:@"width"] floatValue];
    height = [[dict valueForKey:@"height"] floatValue];
   
    return CGRectMake(x, y, width, height);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
    // Check if the touch was within one of the rectangle objects
    CCTMXObjectGroup* objectLayer = [tileMap objectGroupNamed:@"ObjectLayer"];
    NSAssert([objectLayer isKindOfClass:[CCTMXObjectGroup class]], @"ObjectLayer not found or not a CCTMXObjectGroup");

    bool isTouchInRectangle = NO;
    int numObjects = [objectLayer.objects count];
    for (int i = 0; i < numObjects; i++)
    {
        NSDictionary* properties = [objectLayer.objects objectAtIndex:i];
        CGRect rect = [self getRectFromObjectProperties:properties tileMap:tileMap];

        if (CGRectContainsPoint(rect, touchLocation))
        {
            isTouchInRectangle = YES;
            break;
        }
    }
   
    // decide what to do depending on where the touch was ...
    if (isTouchOnWater)
    {
        [[SimpleAudioEngine sharedEngine] playEffect:@"alien-sfx.caf"];
    }
    else if (isTouchInRectangle)
    {
        CCParticleSystem* system = [CCQuadParticleSystem particleWithFile:@"fx-explosion.plist"];
        system.autoRemoveOnFinish = YES;
        system.position = touchLocation;
        [self addChild:system z:1];
    }
    else
    {
        // get the winter layer and toggle its visibility
        CCTMXLayer* winterLayer = [tileMap layerNamed:@"WinterLayer"];
        winterLayer.visible = !winterLayer.visible;
       
        // remove the touched tile
        //[winterLayer removeTileAt:tilePos];
       
        // adds a given tile
        //tileGID = [winterLayer tileGIDAt:CGPointMake(0, 19)];
        //[winterLayer setTileGID:tileGID at:tilePos];
    }

移动视点到点击位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// move tilemap so that touched tiles is at center of screen
[self centerTileMapOnTileCoord:tilePos tileMap:tileMap];

-(void) centerTileMapOnTileCoord:(CGPoint)tilePos tileMap:(CCTMXTiledMap*)tileMap
{
    // center tilemap on the given tile pos
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    CGPoint screenCenter = CGPointMake(screenSize.width * 0.5f, screenSize.height * 0.5f);
   
    // tile coordinates are counted from upper left corner, this maps coordinates to lower left corner
    tilePos.y = (tileMap.mapSize.height - 1) - tilePos.y;
   
    // point is now at lower left corner of the screen
    CGPoint scrollPosition = CGPointMake(-(tilePos.x * tileMap.tileSize.width),
                                          -(tilePos.y * tileMap.tileSize.height));
   
    // offset point to center of screen and center of tile
    scrollPosition.x += screenCenter.x - tileMap.tileSize.width * 0.5f;
    scrollPosition.y += screenCenter.y - tileMap.tileSize.height * 0.5f;

    // make sure tilemap scrolling stops at the tilemap borders
    scrollPosition.x = MIN(scrollPosition.x, 0);
    scrollPosition.x = MAX(scrollPosition.x, -screenSize.width);
    scrollPosition.y = MIN(scrollPosition.y, 0);
    scrollPosition.y = MAX(scrollPosition.y, -screenSize.height);
   
    CCLOG(@"tilePos: (%i, %i) moveTo: (%.0f, %.0f)", (int)tilePos.x, (int)tilePos.y, scrollPosition.x, scrollPosition.y);
   
    CCAction* move = [CCMoveTo actionWithDuration:0.2f position:scrollPosition];
    [tileMap stopAllActions];
    [tileMap runAction:move];
}

45度角地图
不能使用CC_DIRECTOR_INIT();
创建部分重写

1
2
3
4
5
6
7
8
9
10
11
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    if ([CCDirector setDirectorType:kCCDirectorTypeDisplayLink] == NO)
        [CCDirector setDirectorType:kCCDirectorTypeNSTimer];
   
    CCDirector *director = [CCDirector sharedDirector];                                    
    [director setAnimationInterval:1.0/60];                                                
    EAGLView *glView = [EAGLView viewWithFrame:[window bounds]
                                     pixelFormat:kEAGLColorFormatRGB565
                                     depthFormat:GL_DEPTH_COMPONENT24_OES
                              preserveBackbuffer:NO];
    [director setProjection:kCCDirectorProjection2D];

45度角地图获取点击位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-(CGPoint) tilePosFromLocation:(CGPoint)location tileMap:(CCTMXTiledMap*)tileMap
{
    // Tilemap position must be subtracted, in case the tilemap position is not at 0,0 due to scrolling
    CGPoint pos = ccpSub(location, tileMap.position);
   
    float halfMapWidth = tileMap.mapSize.width * 0.5f;
    float mapHeight = tileMap.mapSize.height;
    float tileWidth = tileMap.tileSize.width;
    float tileHeight = tileMap.tileSize.height;
   
    CGPoint tilePosDiv = CGPointMake(pos.x / tileWidth, pos.y / tileHeight);
    float inverseTileY = mapHeight - tilePosDiv.y;

    // Cast to int makes sure that result is in whole numbers, tile coordinates will be used as array indices
    float posX = (int)(inverseTileY + tilePosDiv.x - halfMapWidth);
    float posY = (int)(inverseTileY - tilePosDiv.x + halfMapWidth);

    // make sure coordinates are within isomap bounds
    posX = MAX(0, posX);
    posX = MIN(tileMap.mapSize.width - 1, posX);
    posY = MAX(0, posY);
    posY = MIN(tileMap.mapSize.height - 1, posY);
   
    pos = CGPointMake(posX, posY);

    CCLOG(@"touch at (%.0f, %.0f) is at tileCoord (%i, %i)", location.x, location.y, (int)pos.x, (int)pos.y);
    //CCLOG(@"\tinverseY: %.2f -- tilePosDiv: (%.2f, %.2f) -- halfMapWidth: %.0f\n", inverseTileY, tilePosDiv.x, tilePosDiv.y, halfMapWidth);
   
    return pos;
}

滚动地图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-(void) centerTileMapOnTileCoord:(CGPoint)tilePos tileMap:(CCTMXTiledMap*)tileMap
{
    // center tilemap on the given tile pos
    CGSize screenSize = [[CCDirector sharedDirector] winSize];
    CGPoint screenCenter = CGPointMake(screenSize.width * 0.5f, screenSize.height * 0.5f);
   
    // get the ground layer
    CCTMXLayer* layer = [tileMap layerNamed:@"Ground"];
    NSAssert(layer != nil, @"Ground layer not found!");
   
    // internally tile Y coordinates are off by 1, this fixes the returned pixel coordinates
    tilePos.y -= 1;
   
    // get the pixel coordinates for a tile at these coordinates
    CGPoint scrollPosition = [layer positionAt:tilePos];
    // negate the position for scrolling
    scrollPosition = ccpMult(scrollPosition, -1);
    // add offset to screen center
    scrollPosition = ccpAdd(scrollPosition, screenCenter);
   
    CCLOG(@"tilePos: (%i, %i) moveTo: (%.0f, %.0f)", (int)tilePos.x, (int)tilePos.y, scrollPosition.x, scrollPosition.y);
   
    CCAction* move = [CCMoveTo actionWithDuration:0.2f position:scrollPosition];
    [tileMap stopAllActions];
    [tileMap runAction:move];
}

给地图添加一个宽度10的边框后修正的位置

1
2
3
4
5
6
7
8
9
    const int borderSize = 10;
    playableAreaMin = CGPointMake(borderSize, borderSize);
    playableAreaMax = CGPointMake(tileMap.mapSize.width - 1 - borderSize, tileMap.mapSize.height - 1 - borderSize);

// make sure coordinates are within bounds of the playable area
    posX = MAX(playableAreaMin.x, posX);
    posX = MIN(playableAreaMax.x, posX);
    posY = MAX(playableAreaMin.y, posY);
    posY = MIN(playableAreaMax.y, posY);

添加player

1
2
3
4
5
6
7
#import <Foundation/Foundation.h>
#import "cocos2d.h"
@interface Player : CCSprite
{
}
+(id) player;
@end
1
2
3
4
5
6
// Create the player and add it
player = [Player player];
player.position = CGPointMake(screenSize.width / 2, screenSize.height / 2);
// approximately position player's texture to best match the tile center position
player.anchorPoint = CGPointMake(0.3f, 0.1f);
[self addChild:player];

player和遮挡的关系

1
2
3
4
5
6
-(void) updateVertexZ:(CGPoint)tilePos tileMap:(CCTMXTiledMap*)tileMap
{
    float lowestZ = -(tileMap.mapSize.width + tileMap.mapSize.height);
    float currentZ = tilePos.x + tilePos.y;
    self.vertexZ = lowestZ + currentZ - 1;
}

cocos2d notes(5)

星期三, 六月 15th, 2011

粒子效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    [self removeChildByTag:1 cleanup:YES];
   
    CCParticleSystem* system;
   
    switch (particleType)
    {
        case ParticleTypeExplosion:
            system = [CCParticleExplosion node];
            break;
        case ParticleTypeFire:
            system = [CCParticleFire node];
            break;
        case ParticleTypeFireworks:
            system = [CCParticleFireworks node];
            break;
        case ParticleTypeFlower:
            system = [CCParticleFlower node];
            break;
        case ParticleTypeGalaxy:
            system = [CCParticleGalaxy node];
            break;
        case ParticleTypeMeteor:
            system = [CCParticleMeteor node];
            break;
        case ParticleTypeRain:
            system = [CCParticleRain node];
            break;
        case ParticleTypeSmoke:
            system = [CCParticleSmoke node];
            break;
        case ParticleTypeSnow:
            system = [CCParticleSnow node];
            break;
        case ParticleTypeSpiral:
            system = [CCParticleSpiral node];
            break;
        case ParticleTypeSun:
            system = [CCParticleSun node];
            break;
           
        default:
            // do nothing
            break;
    }

    [self addChild:system z:1 tag:1];

自定义粒子效果

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
#import "cocos2d.h"
// Depending on the targeted device the ParticleEffectSelfMade class will either derive
// from CCPointParticleSystem or CCQuadParticleSystem
@interface ParticleEffectSelfMade : ARCH_OPTIMAL_PARTICLE_SYSTEM
{
}
@end

ARCH_OPTIMAL_PARTICLE_SYSTEM宏将自动判断使用效率较高的父类(CCPointParticleSystem or CCQuadParticleSystem)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#import "ParticleEffectSelfMade.h"


@implementation ParticleEffectSelfMade

-(id) init
{
    return [self initWithTotalParticles:250];
}

-(id) initWithTotalParticles:(int)numParticles
{
    if ((self = [super initWithTotalParticles:numParticles]))
    {
        // DURATION
        // most effects use infinite duration
        self.duration = kCCParticleDurationInfinity;
        // for timed effects use a number in seconds how long particles should be emitted
        //self.duration = 2.0f;
        // If the particle system runs for a fixed time, this will remove the particle system node from
        // its parent once all particles have died. Has no effect for infinite particle systems.
        self.autoRemoveOnFinish = YES;

        // MODE
        // particles are affected by gravity
        self.emitterMode = kCCParticleModeGravity;
        // particles move in a circle instead
        //self.emitterMode = kCCParticleModeRadius;
       
        // some properties must only be used with a specific emitterMode!
        if (self.emitterMode == kCCParticleModeGravity)
        {
            // centerOfGravity is misleading as it determines the offset where particles appear. The actual
            // center of gravity is the node's position.
            self.centerOfGravity = CGPointMake(-15, 0);
            // gravity determines the particle's speed in the x and y directions
            self.gravity = CGPointMake(-50, -90);
            // radial acceleration affects how fast particles move depending on their distance to the emitter
            // positive radialAccel means particles speed up as they move away, negative means they slow down
            self.radialAccel = -90;
            self.radialAccelVar = 20;
            // tangential acceleration lets particles rotate around the emitter position,
            // and they speed up as they rotate around (slingshot effect)
            self.tangentialAccel = 120;
            self.tangentialAccelVar = 10;
            // speed is of course how fast particles move in general
            self.speed = 15;
            self.speedVar = 4;
        }
        else if (self.emitterMode == kCCParticleModeRadius)
        {
            // the distance from the emitter position that particles will be spawned and sent out
            // in a radial (circular) fashion
            self.startRadius = 100;
            self.startRadiusVar = 0;
            // the end radius the particles move towards, if less than startRadius particles will move
            // inwards, if greater than startRadius particles will move outward
            // you can use the keyword kCCParticleStartRadiusEqualToEndRadius to create a perfectly circular rotation
            self.endRadius = 10;
            self.endRadiusVar = 0;
            // how fast the particles rotate around
            self.rotatePerSecond = 180;
            self.rotatePerSecondVar = 0;
        }

        // EMITTER POSITION
        // emitter position is at the center of the node (default)
        // this is where new particles will appear
        self.position = CGPointZero;
        self.posVar = CGPointZero;
        // The positionType determines if existing particles should be repositioned when the node is moving
        // (kCCPositionTypeGrouped) or if the particles should remain where they are (kCCPositionTypeFree).
        self.positionType = kCCPositionTypeFree;
       
        // PARTICLE SIZE
        // size of individual particles in pixels
        self.startSize = 40.0f;
        self.startSizeVar = 0.0f;
        self.endSize = kCCParticleStartSizeEqualToEndSize;
        self.endSizeVar = 0;

        // ANGLE (DIRECTION)
        // the direction in which particles are emitted, 0 means upwards
        self.angle = 0;
        self.angleVar = 0;
       
        // PARTICLE LIFETIME
        // how long each individual particle will "life" (eg. stay on screen)
        self.life = 5.0f;
        self.lifeVar = 0.0f;
       
        // PARTICLE EMISSION RATE
        // how many particles per second are created (emitted)
        // particle creation stops if self.particleCount >= self.totalParticles
        // you can use this to create short burst effects with pauses between each burst
        self.emissionRate = 30;
        // normally set with initWithTotalParticles but you can change that number
        self.totalParticles = 250;

        // PARTICLE COLOR
        // A valid startColor must be set! Otherwise the particles may be invisible. The other colors are optional.
        // These colors determine the color of the particle at the start and the end of its lifetime.
        startColor.r = 1.0f;
        startColor.g = 0.25f;
        startColor.b = 0.12f;
        startColor.a = 1.0f;

        startColorVar.r = 0.0f;
        startColorVar.g = 0.0f;
        startColorVar.b = 0.0f;
        startColorVar.a = 0.0f;
       
        endColor.r = 0.0f;
        endColor.g = 0.0f;
        endColor.b = 0.0f;
        endColor.a = 1.0f;
       
        endColorVar.r = 0.0f;
        endColorVar.g = 0.0f;
        endColorVar.b = 1.0f;
        endColorVar.a = 0.0f;
       
        // BLEND FUNC
        // blend func influences how transparent colors are calculated
        // the first parameter is for the source, the second for the target
        // available blend func parameters are:
        // GL_ZERO   GL_ONE   GL_SRC_COLOR   GL_ONE_MINUS_SRC_COLOR   GL_SRC_ALPHA
        // GL_ONE_MINUS_SRC_ALPHA   GL_DST_ALPHA   GL_ONE_MINUS_DST_ALPHA
        self.blendFunc = (ccBlendFunc){GL_SRC_ALPHA, GL_DST_ALPHA};
        // shortcut to set the blend func to: GL_SRC_ALPHA, GL_ONE
        //self.blendAdditive = YES;
       
        // PARTICLE TEXTURE
        self.texture = [[CCTextureCache sharedTextureCache] addImage: @"fire.png"];
    }
   
    return self;
}

@end

粒子设计工具
http://particledesigner.71squared.com/ 下载ParticleDesigner

使用ParticleDesigner生成的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
-(void) runEffect
{
    // remove any previous particle FX
    [self removeChildByTag:1 cleanup:YES];
   
    CCParticleSystem* system;
   
    switch (particleType)
    {
        case ParticleTypeDesignedFX:
            // by using ARCH_OPTIMAL_PARTICLE_SYSTEM either the CCQuadParticleSystem or CCPointParticleSystem class is
            // used depending on the current target.
            system = [ARCH_OPTIMAL_PARTICLE_SYSTEM particleWithFile:@"designed-fx.plist"];
            break;
        case ParticleTypeDesignedFX2:
            // uses a plist with the texture already embedded
            system = [CCQuadParticleSystem particleWithFile:@"designed-fx2.plist"];
            system.positionType = kCCPositionTypeFree;
            break;
        case ParticleTypeDesignedFX3:
            // same effect but different texture (scaled down by Particle Designer)
            system = [CCQuadParticleSystem particleWithFile:@"designed-fx3.plist"];
            system.positionType = kCCPositionTypeFree;
            break;
        case ParticleTypeSelfMade:
            system = [ParticleEffectSelfMade node];
            break;
       
        default:
            // do nothing
            break;
    }

    CGSize winSize = [[CCDirector sharedDirector] winSize];
    system.position = CGPointMake(winSize.width / 2, winSize.height / 2);
    [self addChild:system z:1 tag:1];
   
    [label setString:NSStringFromClass([system class])];
}

在之前的射击游戏中加入爆炸粒子效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-(void) gotHit
{
    hitPoints--;
    if (hitPoints <= 0)
    {
        self.visible = NO;
       
        // Play a particle effect when the enemy was destroyed
        CCParticleSystem* system;
        if (type == EnemyTypeBoss)
        {
            system = [ARCH_OPTIMAL_PARTICLE_SYSTEM particleWithFile:@"fx-explosion2.plist"];
        }
        else
        {
            system = [ARCH_OPTIMAL_PARTICLE_SYSTEM particleWithFile:@"fx-explosion.plist"];
        }
       
        // Set some parameters that can't be set in Particle Designer
        system.positionType = kCCPositionTypeFree;
        system.autoRemoveOnFinish = YES;
        system.position = self.position;
       
        // Add the particle effect to the GameScene, for these reasons:
        // - self is a sprite added to a spritebatch and will only allow CCSprite nodes (it crashes if you try)
        // - self is now invisible which might affect rendering of the particle effect
        // - since the particle effects are short lived, there is no harm done by adding them directly to the GameScene
        [[GameScene sharedGameScene] addChild:system];
    }
}

cocos2d的虚拟摇杆

星期二, 六月 14th, 2011

https://github.com/sneakyness/SneakyInput

Cocos2D notes(4)

星期一, 六月 13th, 2011

CCSpriteBatchNode

1
2
3
4
5
6
7
CCSpriteBatchNode* batch = [CCSpriteBatchNode batchNodeWithFile:@"bullet.png"];
[self addChild:batch];
for (int i = 0; i < 100; i++)
{
    CCSprite* sprite = [CCSprite spriteWithFile:@”bullet.png”];
    [batch addChild:bullet];
}

child必须用同样材质的sprite,否则报错

如果继承CCSprite的类中这样使用会造成死循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(id) init
{
    CCLOG(@"Ship init called ... this will repeat infinetely.");
   
    // WARNING: this will cause an infinite loop!
    // Reason: [super initWithFile:...] will call -(id) init which is overridden here ... repeat ad infinitum
    // Fix: rename this init method, for example initWithShipImage
    // Tip: never call anything but [super init] in your class' init method. If you call any initWith* method
    // then you should never do this from the -(id) init method.
    if ((self = [super initWithFile:@"ship.png"]))
    {
        [self scheduleUpdate];
    }
    return self;
}

为CCSprite添加动画效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        // load the ship's animation frames as textures and create a sprite frame
        NSMutableArray* frames = [NSMutableArray arrayWithCapacity:5];
        for (int i = 0; i < 5; i++)
        {
            NSString* file = [NSString stringWithFormat:@"ship-anim%i.png", i];
            CCTexture2D* texture = [[CCTextureCache sharedTextureCache] addImage:file];
            CGSize texSize = texture.contentSize;
            CGRect texRect = CGRectMake(0, 0, texSize.width, texSize.height);
            CCSpriteFrame* frame = [CCSpriteFrame frameWithTexture:texture rect:texRect offset:CGPointZero];
            [frames addObject:frame];
        }
       
        // create an animation object from all the sprite animation frames
        CCAnimation* anim = [CCAnimation animationWithName:@"move" delay:0.08f frames:frames];
       
        // add the animation to the sprite
        //[self addAnimation:anim];
       
        // run the animation by using the CCAnimate action
        CCAnimate* animate = [CCAnimate actionWithAnimation:anim];
        CCRepeatForever* repeat = [CCRepeatForever actionWithAction:animate];
        [self runAction:repeat];

可以通过animation的名字获取对象

1
2
3
4
5
CCAnimation* anim = [CCAnimation animationWithName:@"move" delay:1 frames:frames];
// Store the animation in the CCSprite node
[mySprite addAnimation:anim];
// Sometime later: retrieve the move animation from the CCSprite node
CCAnimation* moveAnim = [mySprite animationByName:@”move”];

animation help

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@implementation CCAnimation (Helper)

// Creates an animation from single files
+(CCAnimation*) animationWithFile:(NSString*)name frameCount:(int)frameCount delay:(float)delay
{
    // Load the animation frames as textures and create the sprite frames
    NSMutableArray* frames = [NSMutableArray arrayWithCapacity:frameCount];
    for (int i = 0; i < frameCount; i++)
    {
        // Assuming all animation files are named "nameX.png"
        NSString* file = [NSString stringWithFormat:@"%@%i.png", name, i];
        CCTexture2D* texture = [[CCTextureCache sharedTextureCache] addImage:file];

        // Assuming that image file animations always use the whole image
        CGSize texSize = texture.contentSize;
        CGRect texRect = CGRectMake(0, 0, texSize.width, texSize.height);
        CCSpriteFrame* frame = [CCSpriteFrame frameWithTexture:texture rect:texRect offset:CGPointZero];
        [frames addObject:frame];
    }
    // Return an animation object from all the sprite animation frames
    return [CCAnimation animationWithName:name delay:delay frames:frames];
}

@end

Texture Atlas
工具zwoptex(收费)
TexturePacker (免费)
TexturePacker-2.2.0-win32.zip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-(id) initWithShipImage
{
    // Load the Texture Atlas sprite frames, this also loads the Texture with the same name.
    CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];
    [frameCache addSpriteFramesWithFile:@"ship-and-bullet.plist"];

    // Loading the Ship's sprite using a sprite frame name (eg the filename)
    if ((self = [super initWithSpriteFrameName:@"ship.png"]))
    {
        // load the ship's animation frames as textures and create a sprite frame
        NSMutableArray* frames = [NSMutableArray arrayWithCapacity:5];
        for (int i = 0; i < 5; i++)
        {
            NSString* file = [NSString stringWithFormat:@"ship-anim%i.png", i];
            CCSpriteFrame* frame = [frameCache spriteFrameByName:file];
            [frames addObject:frame];
        }
       
        // create an animation object from all the sprite animation frames
        CCAnimation* anim = [CCAnimation animationWithName:@"move" delay:0.08f frames:frames];
       
        // add the animation to the sprite (optional)
        //[self addAnimation:anim];
       
        // run the animation by using the CCAnimate action
        CCAnimate* animate = [CCAnimate actionWithAnimation:anim];
        CCRepeatForever* repeat = [CCRepeatForever actionWithAction:animate];
        [self runAction:repeat];
       
       
        [self scheduleUpdate];
    }
    return self;
}

helper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Creates an animation from sprite frames.
+(CCAnimation*) animationWithFrame:(NSString*)frame frameCount:(int)frameCount delay:(float)delay
{
    // load the ship's animation frames as textures and create a sprite frame
    NSMutableArray* frames = [NSMutableArray arrayWithCapacity:frameCount];
    for (int i = 0; i < frameCount; i++)
    {
        NSString* file = [NSString stringWithFormat:@"%@%i.png", frame, i];
        CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];
        CCSpriteFrame* frame = [frameCache spriteFrameByName:file];
        [frames addObject:frame];
    }
   
    // return an animation object from all the sprite animation frames
    return [CCAnimation animationWithName:frame delay:delay frames:frames];
}

释放无用cache

1
2
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
[[CCTextureCache sharedTextureCache] removeUnusedTextures];

清除所有

1
2
[CCSpriteFrameCache purgeSharedSpriteFrameCache];
[CCTextureCache purgeSharedTextureCache];

Cocos2d notes(3)

星期日, 六月 12th, 2011

Scene显示或被替换的3个重要事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-(void) onEnter
{
        // Called right after a node’s init method is called.
        // If using a CCTransitionScene: called when the transition begins.
        [super onEnter];
}
-(void) onEnterTransitionDidFinish
{
        // Called right after onEnter.
        // If using a CCTransitionScene: called when the transition has ended.
        [super onEnterTransitionDidFinish];
}
-(void) onExit
{
        // Called right before node’s dealloc method is called.
        // If using a CCTransitionScene: called when the transition has ended.
        [super onExit];
}

Scene替换时发生的事件顺序

1
2
3
4
5
6
7
2011-06-12 19:35:39.374 ScenesAndLayers[21353:207] ===========================================
2011-06-12 19:35:39.375 ScenesAndLayers[21353:207] scene: OtherScene
2011-06-12 19:35:39.375 ScenesAndLayers[21353:207] init: <OtherScene = 06465B40 | Tag = -1>
2011-06-12 19:35:44.743 ScenesAndLayers[21353:207] onEnter: <OtherScene = 06465B40 | Tag = -1>
2011-06-12 19:35:47.760 ScenesAndLayers[21353:207] onExit: <FirstScene = 0783AD30 | Tag = -1>
2011-06-12 19:35:47.761 ScenesAndLayers[21353:207] onEnterTransitionDidFinish: <OtherScene = 06465B40 | Tag = -1>
2011-06-12 19:35:47.761 ScenesAndLayers[21353:207] dealloc: <FirstScene = 0783AD30 | Tag = -1>

由此可知,在init后才发生onEnter,如果init初始化数据较多,就会导致画面tingdun,所以要加入loading scene

一个范例方法
LoadScene.h

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef enum
{
        TargetSceneINVALID = 0,
        TargetSceneFirstScene,
        TargetSceneOtherScene,
        TargetSceneMAX,
} TargetScenes;
 
// LoadingScene is derived directly from Scene. We don't need a CCLayer for this scene.
@interface LoadingScene : CCScene
{
        TargetScenes targetScene_;
}

LoadScene.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
+(id) sceneWithTargetScene:(TargetScenes)targetScene;
{
        // This creates an autorelease object of the current class (self ==  
LoadingScene)
        return [[[self alloc] initWithTargetScene:targetScene] autorelease];
}
 
-(id) initWithTargetScene:(TargetScenes)targetScene
{
        if ((self = [super init]))
        {
                targetScene_ = targetScene;
 
                CCLabel* label = [CCLabel labelWithString:@"Loading ..."  
                                                   fontName:@"Marker Felt" fontSize:64];
                CGSize size = [[CCDirector sharedDirector] winSize];
                label.position = CGPointMake(size.width / 2, size.height / 2);
                [self addChild:label];
 
                // Must wait one frame before loading the target scene!
                [self scheduleUpdate];
        }
 
        return self;
}
 
-(void) update:(ccTime)delta
{
        [self unscheduleAllSelectors];
 
        // Decide which scene to load based on the TargetScenes enum.
        switch (targetScene_)
        {
                case TargetSceneFirstScene:
                        [[CCDirector sharedDirector] replaceScene:[FirstScene scene]];
                        break;
                case TargetSceneOtherScene:
                        [[CCDirector sharedDirector] replaceScene:[OtherScene scene]];
                        break;
 
                default:
                        // Always warn if an unspecified enum value was used
                        NSAssert2(nil, @"%@: unsupported TargetScene %i",  
                                              NSStringFromSelector(_cmd), targetScene_);
                        break;
        }
}

为什么不能在LoadScene 的init方法中直接调用replaceScene
原因1:永远不要在node的init中调用CCDirector的replaceScene方法
原因2:这样调用会让app崩溃,因为无法在初始化(init)一个node的同时replace掉它

Singleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+(MultiLayerScene*) sharedLayer
{
        NSAssert(multiLayerSceneInstance != nil, @"MultiLayerScene not available!");
        return multiLayerSceneInstance;
}
 
-(void) dealloc
{
        // MultiLayerScene will be deallocated now, you must set it to nil
        multiLayerSceneInstance = nil;
 
        // don't forget to call "super dealloc"
        [super dealloc];
}

当一个Scene上有两个层重叠,如何判断哪个层接收touch事件
假设UserInterfaceLayer* uiLayer 在GameLayer* gameLayer之上
UserInterfacelayer.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-(void) registerWithTouchDispatcher
{
    [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
}

// Implements logic to check if the touch location was in an area that this layer wants to handle as input.
-(bool) isTouchForMe:(CGPoint)touchLocation
{
    CCNode* node = [self getChildByTag:UILayerTagFrameSprite];
    return CGRectContainsPoint([node boundingBox], touchLocation);
}

-(BOOL) ccTouchBegan:(UITouch*)touch withEvent:(UIEvent *)event
{
    CGPoint location = [MultiLayerScene locationFromTouch:touch];
    bool isTouchHandled = [self isTouchForMe:location];
    if (isTouchHandled)
    {
        // Simply highlight the UI layer's sprite to show that it received the touch.
        CCNode* node = [self getChildByTag:UILayerTagFrameSprite];
        NSAssert([node isKindOfClass:[CCSprite class]], @"node is not a CCSprite");
       
        ((CCSprite*)node).color = ccRED;
        // 省略
    }

    return isTouchHandled;
}

-(void) ccTouchEnded:(UITouch*)touch withEvent:(UIEvent *)event
{
    CCNode* node = [self getChildByTag:UILayerTagFrameSprite];
    NSAssert([node isKindOfClass:[CCSprite class]], @"node is not a CCSprite");
   
    ((CCSprite*)node).color = ccWHITE;
}

在registerWithTouchDispatcher中将自己获取touch的优先级设为最高,然后在-(BOOL) ccTouchBegan:withEvent:中如果返回YES,这次的touch将被uiLayer吞掉(swallow),如果返回NO,则由gameLayer继续处理。

使用CCMultiplexLayer在不同Layer间切换

1
2
3
4
5
6
7
8
CCLayer* layer1 = [CCLayer node];
CCLayer* layer2 = [CCLayer node];
CCMultiplexLayer* mpLayer = [CCMultiplexLayer layerWithLayers:layer1, layer2, nil];
// Switches to layer2 but keeps layer1 as child of mpLayer.
[mpLayer switchTo:1];
// Switches to layer1, removes layer2 from mpLayer and releases its memory.
// After this call you must not switch back to layer2 (index: 1) anymore!
[mpLayer switchToAndReleaseMe:0];

CCColorLayer

1
2
3
// Set background color to magenta. The most unobtrusive color imaginable.
CCColorLayer* colorLayer = [CCColorLayer layerWithColor:ccc4(255, 0, 255, 255)];
[self addChild:colorLayer z:0];

Sprite Class

Spider.h

1
2
3
4
5
6
7
8
9
@interface Spider : NSObject
{
        CCSprite* spiderSprite;
}
 
+(id) spiderWithParentNode:(CCNode*)parentNode;
-(id) initWithParentNode:(CCNode*)parentNode;
 
@end

Spider.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#import "Spider.h"
 
@implementation Spider
 
// Static autorelease initializer, mimics cocos2d's memory allocation scheme.
+(id) spiderWithParentNode:(CCNode*)parentNode
{
        return [[[self alloc] initWithParentNode:parentNode] autorelease];
}
 
-(id) initWithParentNode:(CCNode*)parentNode
{
        if ((self = [super init]))
        {
                CGSize screenSize = [[CCDirector sharedDirector] winSize];
 
                spiderSprite = [CCSprite spriteWithFile:@"spider.png"];
                spiderSprite.position = CGPointMake(CCRANDOM_0_1() *  
                                  screenSize.width, CCRANDOM_0_1() * screenSize.height);
                [parentNode addChild:spiderSprite];
 
                // Manually schedule update via the undocumented CCScheduler class.
                [[CCScheduler sharedScheduler] scheduleUpdateForTarget:self priority:0  
                                                                             paused:NO];
        }
 
        return self;
}
 
-(void) dealloc
{
        // Must manually unschedule, it is not done automatically.
        [[CCScheduler sharedScheduler] unscheduleUpdateForTarget:self];
 
        [super dealloc];
}

通过注册CCScheduler的 scheduleUpdateForTarget:priority:方法
使Spider每帧调用update:方法

让Spider类实现CCTargetedTouchDelegate协议,使Spider类接收touch
@interface Spider : NSObject
{

}
修改后的Spider.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
-(id) initWithParentNode:(CCNode*)parentNode
{
        if ((self = [super init]))
        {
                …
                // Manually schedule update via the undocumented CCScheduler class.
                [[CCScheduler sharedScheduler] scheduleUpdateForTarget:self priority:0  
                                                                             paused:NO];
 
                // Manually add this class as receiver of targeted touch events.
                [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self  
                                                       priority:-1 swallowsTouches:YES];
        }
 
        return self;
}
 
-(void) dealloc
{
        // Must manually unschedule, it is not done automatically!
        [[CCScheduler sharedScheduler] unscheduleUpdateForTarget:self];
 
        // Must manually remove this class as touch input receiver!
        [[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
 
        [super dealloc];
}
 
// Extract common logic into a separate method accepting parameters.
-(void) moveAway:(float)duration position:(CGPoint)moveTo
{
        [spiderSprite stopAllActions];
        CCMoveBy* move = [CCMoveBy actionWithDuration:duration position:moveTo];
        [spiderSprite runAction:move];
}
 
-(void) update:(ccTime)delta
{
        numUpdates++;
        if (numUpdates > 50)
        {
                numUpdates = 0;
 
                // Move at regular speed.
                CGPoint moveTo = CGPointMake(CCRANDOM_0_1() * 200 - 100,  
                                                             CCRANDOM_0_1() * 100 - 50);
                [self moveAway:2 position:moveTo];
        }
}
 
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
        // Check if this touch is on the Spider's sprite.
        CGPoint touchLocation = [MultiLayerScene locationFromTouch:touch];
        BOOL isTouchHandled = CGRectContainsPoint([spiderSprite boundingBox],  
                                                                         touchLocation);
        if (isTouchHandled)
        {
               // Reset move counter.
                numUpdates = 0;
 
                // Move away from touch loation rapidly.
                CGPoint moveTo;
                float moveDistance = 60;
                float rand = CCRANDOM_0_1();
 
                // Randomly pick one of four corners to move away to.
                if (rand < 0.25f)
                        moveTo = CGPointMake(moveDistance, moveDistance);
                else if (rand < 0.5f)
                        moveTo = CGPointMake(-moveDistance, moveDistance);
                else if (rand < 0.75f)
                        moveTo = CGPointMake(moveDistance, -moveDistance);
                else
                        moveTo = CGPointMake(-moveDistance, -moveDistance);
                 
                // Move quickly:
                [self moveAway:0.1f position:moveTo];
        }
 
        return isTouchHandled;
}

CCProgressTimer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Progress timer is a sprite only partially displayed to visualize some kind of progress.
CCProgressTimer* timer = [CCProgressTimer progressWithFile:@"firething.png"];
timer.type = kCCProgressTimerTypeRadialCCW;
timer.percentage = 0;
[self addChild:timer z:1 tag:UILayerTagProgressTimer];
// The update is needed for the progress timer.
[self scheduleUpdate];

-(void) update:(ccTime)delta
{
    CCNode* node = [self getChildByTag:UILayerTagProgressTimer];
    NSAssert([node isKindOfClass:[CCProgressTimer class]], @"node is not a CCProgressTimer");
    // Updates the progress timer
    CCProgressTimer* timer = (CCProgressTimer*)node;
    timer.percentage += delta * 10;
    if (timer.percentage >= 100)
    {
        timer.percentage = 0;
    }
}

CCParallaxNode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Load the sprites for each parallax layer, from background to foreground.
CCSprite* para1 = [CCSprite spriteWithFile:@"parallax1.png"];
CCSprite* para2 = [CCSprite spriteWithFile:@"parallax2.png"];
CCSprite* para3 = [CCSprite spriteWithFile:@"parallax3.png"];
CCSprite* para4 = [CCSprite spriteWithFile:@"parallax4.png"];
// Set the correct offsets depending on the screen and image sizes.
para1.anchorPoint = CGPointMake(0, 1);
para2.anchorPoint = CGPointMake(0, 1);
para3.anchorPoint = CGPointMake(0, 0.6f);
para4.anchorPoint = CGPointMake(0, 0);
CGPoint topOffset = CGPointMake(0, screenSize.height);
CGPoint midOffset = CGPointMake(0, screenSize.height / 2);
CGPoint downOffset = CGPointZero;
// Create a parallax node and add the sprites to it.
CCParallaxNode* paraNode = [CCParallaxNode node];
[paraNode addChild:para1 z:1 parallaxRatio:CGPointMake(0.5f, 0) positionOffset:topOffset];
[paraNode addChild:para2 z:2 parallaxRatio:CGPointMake(1, 0) positionOffset:topOffset];
[paraNode addChild:para3 z:4 parallaxRatio:CGPointMake(2, 0) positionOffset:midOffset];
[paraNode addChild:para4 z:3 parallaxRatio:CGPointMake(3, 0) positionOffset:downOffset];
[self addChild:paraNode z:0 tag:ParallaxSceneTagParallaxNode];
// Move the parallax node to show the parallaxing effect.
CCMoveBy* move1 = [CCMoveBy actionWithDuration:5 position:CGPointMake(-160, 0)];
CCMoveBy* move2 = [CCMoveBy actionWithDuration:15 position:CGPointMake(160, 0)];
CCSequence* sequence = [CCSequence actions:move1, move2, nil];
CCRepeatForever* repeat = [CCRepeatForever actionWithAction:sequence];
[paraNode runAction:repeat];

CCRibbon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-(void) resetRibbon
{
    // Removes the ribbon and creates a new one.
    [self removeChildByTag:ParallaxSceneTagRibbon cleanup:YES];
    CCRibbon* ribbon = [CCRibbon ribbonWithWidth:32 image:@"spider.png" length:32 color:ccc4(255, 255, 255, 255) fade:0.5f];
    [self addChild:ribbon z:5 tag:ParallaxSceneTagRibbon];
}

-(CCRibbon*) getRibbon
{
    CCNode* node = [self getChildByTag:ParallaxSceneTagRibbon];
    NSAssert([node isKindOfClass:[CCRibbon class]], @"node is not a CCRibbon");
    return (CCRibbon*)node;
}

-(void) addRibbonPoint:(CGPoint)point
{
    CCRibbon* ribbon = [self getRibbon];
    [ribbon addPointAt:point width:32];
}

-(BOOL) ccTouchBegan:(UITouch*)touch withEvent:(UIEvent *)event
{
    [self addRibbonPoint:[MultiLayerScene locationFromTouch:touch]];
    return YES;
}

-(void) ccTouchMoved:(UITouch*)touch withEvent:(UIEvent *)event
{
    [self addRibbonPoint:[MultiLayerScene locationFromTouch:touch]];
}

-(void) ccTouchEnded:(UITouch*)touch withEvent:(UIEvent *)event
{
    [self resetRibbon];
}

CCMotionStreak
用来替换CCRibbon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-(void) resetMotionStreak
{
    // Removes the CCMotionStreak and creates a new one.
    [self removeChildByTag:ParallaxSceneTagRibbon cleanup:YES];
        CCMotionStreak* streak = [CCMotionStreak streakWithFade:0.7f minSeg:10
    image:@"spider.png" width:32 length:32 color:ccc4(255, 0, 255, 255)];
    [self addChild:streak z:5 tag:ParallaxSceneTagRibbon];
}

-(void) addMotionStreakPoint:(CGPoint)point
{
    CCMotionStreak* streak = [self getMotionStreak];
    [streak.ribbon addPointAt:point width:32];
}

Cocos2D notes(2)

星期日, 六月 12th, 2011

获取真实texture大小
[player texture].contentSize.height

Odd??

1
2
3
4
5
6
7
8
9
-(void) accelerometer:(UIAccelerometer *)accelerometer
didAccelerate:(UIAcceleration *)acceleration
{
        // ERROR: lvalue required as left operand of assignment
        // player.position.x += acceleration.x * 10;
        CGPoint pos = player.position;
        pos.x += acceleration.x * 10;
        player.position = pos;
}

判断Sprite当前是否有执行Action
[spider numberOfRunningActions] == 0

简单的测试碰撞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-(void) checkForCollision
{
        // Assumption: both player and spider images are squares.
        float playerImageSize = [player texture].contentSize.width;
        float spiderImageSize = [[spiders lastObject] texture].contentSize.width;
        float playerCollisionRadius = playerImageSize * 0.4f;
        float spiderCollisionRadius = spiderImageSize * 0.4f;
        // This collision distance will roughly equal the image shapes.
        float maxCollisionDistance = playerCollisionRadius + spiderCollisionRadius;
        int numSpiders = [spiders count];
        for (int i = 0; i < numSpiders; i++)
        {
                CCSprite* spider = [spiders objectAtIndex:i];
                if ([spider numberOfRunningActions] == 0)
                {
                        // This spider isn't even moving so we can skip checking it.
                        continue;
                }
 
                // Get the distance between player and spider.
                float actualDistance = ccpDistance(player.position, spider.position);
 
                // Are the two objects closer than allowed?
                if (actualDistance < maxCollisionDistance)
                {
                        // No game over, just reset the spiders.
                        [self resetSpiders];
                }
        }
}

显示分数

1
2
3
4
5
6
7
8
// Update the Score (Timer) once per second.
totalTime += delta;
int currentTime = (int)totalTime;
if (score < currentTime)
{
        score = currentTime;
        [scoreLabel setString:[NSString stringWithFormat:@"%i", score]];
}

这个例子里使用度过时间的总和作为分数
如果用CCLabel每帧都改变其内容将会严重影响app运行速度
所以这里可以使用CCBitmapFontAtlas作为替代,只需要将创建CCLable的那一行修改

1
2
scoreLabel = [CCBitmapFontAtlas bitmapFontAtlasWithString:@"0"  
                                                             fntFile:@"bitmapfont.fnt"];

要生成fnt文件,用这个java app: http://slick.cokeandcode.com/demos/hiero.jnlp

播放音乐音效

1
2
3
4
5
#import "SimpleAudioEngine.h"
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"blues.mp3" loop:YES];
[[SimpleAudioEngine sharedEngine] playEffect:@"alien-sfx.caf"];
// preload
[[SimpleAudioEngine sharedEngine] preloadEffect:@"alien-sfx.caf"];

注意背景音乐(mp3)同一时间只能播放一个,虽然也有情况可以同时播多个mp3,但是cpu在解码mp3的时候是逐个处理的
所以音效最好使用caf格式
转换成caf格式的文件可能需要工具,这个坑爹的软件卖$15,应该有其他的免费替代品吧。。
http://dekorte.com/projects/shareware/SoundConverter/

遍历,停止所有action

1
2
3
4
CCARRAY_FOREACH([self children], node)
{
    [node stopAllActions];
}

Cocos2D notes (1)

星期五, 六月 10th, 2011

决定还是把cocos2d研究一下,虽然不打算在ios上主攻游戏开发,但2d引擎还是很多地方会用到。

安装cocos2d

google code下载最新的稳定版本,下载完成后解压,用shell cd到解压路径,执行
./install-templates.sh -f -u

因为无论是书还是网上的教程,基本都用的是cocos2d 0.99和xcode3,所以我不知道是为什么,这样安装后不能在xcode4中看到工程模板,可以参见在xcode4中添加cocos2d的工程模板 的解决办法。

CCDirector

首先接触到的是CCDirector类,顾名思义是2d动画的导演类,负责

  1. 创建或更改场景(scenes)
  2. 设置cocos2d的configuration细节
  3. 获取视图(OpenGL, UIView, UIWindow)
  4. 暂停,继续或结束游戏
  5. 转换UIKit和OpenGL坐标

获取CCDirector对象

1
CCDirector *director = [CCDirector sharedDirector];

设置游戏的设备方向

1
2
3
4
5
#if GAME_AUTOROTATION == kGameAutorotationUIViewController
    [director setDeviceOrientation:kCCDeviceOrientationPortrait];
#else
    [director setDeviceOrientation:kCCDeviceOrientationLandscapeLeft];
#endif

设置动画间隔

1
[director setAnimationInterval:1.0/60];

是否显示FPS数据

1
[director setDisplayFPS:YES];

启动场景

1
[[CCDirector sharedDirector] runWithScene: [HelloWorldScene node]];

获得所有可视区域的Size

1
CGSize winSize = [[CCDirector sharedDirector] winSize];

CCNode

CCNode是所有节点的父类包括(Layer, Sprite, Scene)

创建Node

1
CCNode* childNode = [CCNode node];

添加Node

1
[myNode addChild:childNode z:0 tag:123];

获取Node

1
CCNode* retrievedNode = [myNode getChildByTag:123];

删除Node(Tag方式)

1
[myNode removeChildByTag:123 cleanup:YES];

删除Node(指针方式)

1
[myNode removeChild:retrievedNode];

全部删除

1
[myNode removeAllChildrenWithCleanup:YES];

把自己从父节点删除

1
[myNode removeFromParentAndCleanup:YES];

CCAction

Node可以执行Action

声明并运行action

1
2
3
CCAction* action = [CCBlink actionWithDuration:10 blinks:20];
action.tag = 234;
[myNode runAction:action];

获取action

1
CCAction* retrievedAction = [myNode getActionByTag:234];

停止action

1
2
3
4
// by tag
[myNode stopActionByTag:234];
// by pointer
[myNode stopAction:action];

停止所有action

1
[myNode stopAllActions];

定时消息

scheduleUpdate方法会执行update:方法,每帧执行一次

或者指定定时方法

1
2
3
4
5
6
7
8
-(void) scheduleUpdates
{
[self schedule:@selector(updateTenTimesPerSecond:) interval:0.1f];
}
-(void) updateTenTimesPerSecond:(ccTime)delta
{
// this method is called according to its interval, ten times per second
}

停止所有定时方法

1
[self unscheduleAllSelectors];

停止指定定时方法

1
[self unschedule:@selector(updateTenTimesPerSecond:)];

trick — 用_cmd关键字代替当前执行的方法,也就是current method

1
2
3
4
5
6
7
8
9
-(void) scheduleUpdates
{
[self schedule:@selector(tenMinutesElapsed:) interval:600];
}
-(void) tenMinutesElapsed:(ccTime)delta
{
// unschedule the current method by using the _cmd keyword
[self unschedule:_cmd];
}

优先级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// in Node A
-(void) scheduleUpdates
{
[self scheduleUpdate];
}
// in Node B
-(void) scheduleUpdates
{
[self scheduleUpdateWithPriority:1];
}
// in Node C
-(void) scheduleUpdates
{
[self scheduleUpdateWithPriority:-1];
}

ABC3个节点的优先级为C>A>B

Scene

scene永远是一个场景上的根节点

1
2
3
4
5
6
7
+(id) scene
{
    CCScene *scene = [CCScene node];
    CCLayer* layer = [HelloWorld node];
    [scene addChild:layer];
    return scene;
}
1
2
3
4
// only use this to run the very first scene
[[CCDirector sharedDirector] runWithScene:[HelloWorld scene]];
// use replaceScene to change all subsequent scenes
[[CCDirector sharedDirector] replaceScene:[HelloWorld scene]];
1
2
[[CCDirector sharedDirector] pushScene:[Settings scene]];
[[CCDirector sharedDirector] popScene];

replaceScene和pushScene可以使用转换效果(popScene无法使用效果)

1
2
3
4
5
6
// initialize a transition scene with the scene we’d like to display next
CCFadeTransition* tran = [CCFadeTransition transitionWithDuration:1
scene:[HelloWorld scene]
withColor:ccWHITE];
// use the transition scene object instead of HelloWorld
[[CCDirector sharedDirector] replaceScene:tran];

有以下效果
CCFadeTransition: Fade to a specific color and back.
CCFadeTRTransition (three more variations): Tiles flip over to reveal
new scene.
CCJumpZoomTransition: Scene bounces and gets smaller, new scene
does the reverse.
CCMoveInLTransition (three more variations): Scene moves out, new
scene moves in at the same time, either from left, right, top or bottom.
CCOrientedTransitionScene (six more variations): A variety of
transitions flipping the whole scene around
CCPageTurnTransition: An effect like turning a page.
CCRotoZoomTransition: Scene rotates and gets smaller, new scene
does reverse.
CCShrinkGrowTransition: Current scene shrinks, new scene grows
over it.
CCSlideInLTransition (three more variations): New scene slides over
the current scene, either from left, right, top or bottom.
CCSplitColsTransition (one variation): Columns of scene move up or
down to reveal new scene.
CCTurnOffTilesTransition: Tiles randomly replaced by tiles of new scene.

CCLayer

一个场景(Scene)上可能有多个Layer

1
2
3
4
5
6
7
8
9
10
11
+(id) scene
{
    CCScene* scene = [CCScene node];
    CCLayer* backgroundLayer = [HelloWorldBackground node];
    [scene addChild: backgroundLayer];
    CCLayer* layer = [HelloWorld node];
    [scene addChild:layer];
    CCLayer* userInterfaceLayer = [HelloWorldUserInterface node];
    [scene addChild: userInterfaceLayer];
    return scene;
}

* 和Scene一样,Layer没有宽高,只是一群可视对象的概念集合

获取Touch 输入

1
self.isTouchEnabled = YES;

用户Touch动作跟以下方法相关
Called when a finger just begins touching the screen:
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
Called whenever the finger moves on the screen:
-(void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
Called when a finger is lifted off the screen:
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
Called to cancel a touch:
-(void) ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

获取Touch坐标

1
2
3
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView: [touch view]];
return [[CCDirector sharedDirector] convertToGL:touchLocation];

如果要指定层消耗触摸

1
2
3
4
5
-(void) registerWithTouchDispatcher
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self
priority:INT_MIN+1 swallowsTouches:YES];
}

如果此方法内容什么都不写,则不会接收任何触摸事件
这种情况下要用以下方法获取触摸事件
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {}
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {}
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {}
-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event {}
和上面的方法略有不同,并且,注意ccTouchBegan:withEvent:返回是BOOL类型,如果返回YES,则触摸事件不会冒泡到优先级较低的Layer

接收accelerometer事件

1
2
3
4
5
didAccelerate:(UIAcceleration *)acceleration
{
CCLOG(@"acceleration: x:%f / y:%f / z:%f",
acceleration.x, acceleration.y, acceleration.z);
}

CCSprite

1
2
CCSprite* sprite = [CCSprite spriteWithFile:@”Default.png”];
[self addChild:sprite];

锚点

1
2
3
CCSprite* sprite = [CCSprite spriteWithFile:@”Default.png”];
sprite.anchorPoint = CGPointMake(0, 0);
[self addChild:sprite];

CCLabel

1
2
CCLabel* label = [CCLabel labelWithString:@"text" fontName:@"AppleGothic" fontSize:32];
[self addChild:label];

对齐

1
2
3
4
5
6
7
8
9
10
11
12
13
// align label to the right
label.anchorPoint = CGPointMake(1, 0.5f);
// align label to the left
label.anchorPoint = CGPointMake(0, 0.5f);
// align label to the top
label.anchorPoint = CGPointMake(0.5f, 1);
// align label to the bottom
label.anchorPoint = CGPointMake(0.5f, 0);
// use case: place label at top-right corner of the screen
// the label’s text extends to the left and down and is always completely on screen
CGSize size = [[CCDirector sharedDirector] winSize];
label.position = CGPointMake(size.width, size.height);
label.anchorPoint = CGPointMake(1, 1);

CCMenu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
CGSize size = [[CCDirector sharedDirector] winSize];
 
// set CCMenuItemFont default properties
[CCMenuItemFont setFontName:@"Helvetica-BoldOblique"];
[CCMenuItemFont setFontSize:26];
 
// create a few labels with text and selector
CCMenuItemFont* item1 = [CCMenuItemFont itemFromString:@"Go Back!" target:self  
                                                               
selector:@selector(menuItem1Touched:)];
// create a menu item using existing sprites
CCSprite* normal = [CCSprite spriteWithFile:@"Icon.png"];
normal.color = ccRED;
CCSprite* selected = [CCSprite spriteWithFile:@"Icon.png"];
selected.color = ccGREEN;
CCMenuItemSprite* item2 = [CCMenuItemSprite itemFromNormalSprite:normal  
             selectedSprite:selected target:self selector:@selector(menuItem2Touched:)];
 
// create a toggle item using two other menu items (toggle works with images, too)
[CCMenuItemFont setFontName:@"STHeitiJ-Light"];
[CCMenuItemFont setFontSize:18];
CCMenuItemFont* toggleOn = [CCMenuItemFont itemFromString:@"I'm ON!"];
CCMenuItemFont* toggleOff = [CCMenuItemFont itemFromString:@"I'm OFF!"];
CCMenuItemToggle* item3 = [CCMenuItemToggle itemWithTarget:self  
                  selector:@selector(menuItem3Touched:) items:toggleOn, toggleOff, nil];
 
// create the menu using the items
CCMenu* menu = [CCMenu menuWithItems:item1, item2, item3, nil];
menu.position = CGPointMake(size.width / 2, size.height / 2);
[self addChild:menu];
 
// aligning is important, so the menu items don’t occupy the same location
[menu alignItemsVerticallyWithPadding:40];

Actions

1
2
3
// I want myNode to move to 100, 200 and arrive there in 3 seconds
CCMoveTo* move = [CCMoveTo actionWithDuration:3 position:CGPointMake(100, 200)];
[myNode runAction:move];

Repeating Actions

1
2
3
CCRotateBy* rotateBy = [CCRotateBy actionWithDuration:2 angle:360];
CCRepeatForever* repeat = [CCRepeatForever actionWithAction:rotateBy];
[myNode runAction:repeat];

Ease Actions(缓动)

1
2
3
4
5
// I want myNode to move to 100, 200 and arrive there in 3 seconds
CCMoveTo* move = [CCMoveTo actionWithDuration:3 position:CGPointMake(100, 200)];
// this time the node should slowly speed up and then slow down as it moves
CCEaseInOut* ease = [CCEaseInOut actionWithAction:move rate:4];
[myNode runAction:ease];

有以下缓动方式
CCEaseBackIn, CCEaseBackInOut, CCEaseBackOut
CCEaseBounceIn, CCEaseBounceInOut, CCEaseBounceOut
CCEaseElasticIn, CCEaseElasticInOut, CCEaseElasticOut
CCEaseExponentialIn, CCEaseExponentialInOut,
CCEaseExponentialOut
CCEaseIn, CCEaseInOut, CCEaseOut
CCEaseSineIn, CCEaseSineInOut, CCEaseSineOut

Action Sequences(序列)

1
2
3
4
5
CCTintTo* tint1 = [CCTintTo actionWithDuration:4 red:255 green:0 blue:0];
CCTintTo* tint2 = [CCTintTo actionWithDuration:4 red:0 green:0 blue:255];
CCTintTo* tint3 = [CCTintTo actionWithDuration:4 red:0 green:255 blue:0];
CCSequence* sequence = [CCSequence actions:tint1, tint2, tint3, nil];
[label runAction:sequence];

1
2
3
CCSequence* sequence = [CCSequence actions:tint1, tint2, tint3, nil];
CCRepeatForever* repeat = [CCRepeatForever actionWithAction:sequence];
[label runAction:repeat];

Instant Actions(即时)
有3种形式

1
2
3
4
5
6
7
8
CCCallFunc* func = [CCCallFunc actionWithTarget:self selector:@selector(onCallFunc)];
CCCallFuncN* funcN = [CCCallFuncN actionWithTarget:self
                                                   selector:@selector(onCallFuncN:)];
CCCallFuncND* funcND = [CCCallFuncND actionWithTarget:self  
                            selector:@selector(onCallFuncND:data:) data:(void*)self];

CCSequence* seq = [CCSequence actions:tint1, func, tint2, funcN, tint3, funcND, nil];
[label runAction:seq];

对应以下3个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-(void) onCallFunc
{
        CCLOG(@"end of tint1!");
}
 
-(void) onCallFuncN:(id)sender
{
        CCLOG(@"end of tint2! sender: %@", sender);
}
 
-(void) onCallFuncND:(id)sender data:(void*)data
{
        // be careful when casting pointers like this!
        // you have to be 100% sure the object is of this type!
        CCSprite* sprite = (CCSprite*)data;
        CCLOG(@"end of sequence! sender: %@ - data: %@", sender, sprite);
}

在xcode4中添加cocos2d的工程模板

星期三, 六月 8th, 2011

http://www.cocos2d-iphone.org/archives/1412

用第一种方法可行

How To Make A Space Shooter iPhone Game

星期一, 五月 30th, 2011

How To Make A Space Shooter iPhone Game

source: SpaceGame