Cocos2dx – Jedi’s Light Saber

Lets try creating a stunning Jedi’s Light Saber in this tutorial.

Downloads and Source

Project File (ZIP)

Project @ Github: https://github.com/saiy2k/cocos2dx-lightsaber

1. Create new Cocos2DX Project

Refer to official wiki on creating a new cocos2dx project.

2. `LightSaber` Class

  1. Create a C++ class named `LightSaber` that inherits from `CCLayer`
    //LightSaber.h
    class LightSaber : public CCLayer {
    public:
    
        // MEMBER VARIABLES
        // ****************
        CCSprite                        *textureSprite;
        CCArray                         *saberArray;
    
        // OBJECT LIFE CYCLE
        // *****************
        virtual bool                    init();
        CREATE_FUNC(LightSaber);
        ~LightSaber();
    
    };

    `textureSprite` will hold the texture to be used by the particle system and `saberArray` will hold the lightSaber particle systems. All particle systems in this array will be exact copies of each other, but together they intensify the effect.

  2. The `init` function and the `destructor` allocates memory to the `saberArray` and releases it.
    bool LightSaber::init() {
        if ( !CCLayer::init() ) return false;
    
        saberArray                  =   CCArray::create();
        saberArray->retain();
        return                          true;
    }
    
    LightSaber::~LightSaber() {
        saberArray->release();
    }
  3. Create an Instance of `LightSaber` and it to the running scene in `AppDelegate` as follows:
        // in applicationDidFinishLaunching() of AppDelegate.cpp
        CCScene *pScene = CCScene::create();
        pScene->addChild(LightSaber::create());
        pDirector->runWithScene(pScene);

    Its standard Layer setup logic till now. Fancier things will follow.

3. Enable Touches via Touch Dispatcher

  1. The user should be able to move the Light Saber by dragging. So enable touches in the layer as follows
        // in LightSaber.h
        virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
        virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
        virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
        virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);
        void                            registerWithTouchDispatcher();
    
        // in LightSaber.cpp
        bool LightSaber::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) {}
        void LightSaber::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent) {}
        void LightSaber::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent) {}
        void LightSaber::ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent) {}
        void LightSaber::registerWithTouchDispatcher() {
            CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, INT_MIN, true);
        }
    
        // `init()` of LightSaber
        this->setTouchEnabled(true);
  2. Write a function `createSabers()` that will create a particleSystem, when touch is made.
        void LightSaber::createSabers() {
            CCParticleSystem                *saber;
    
            textureSprite               =   CCSprite::create("swipeParticle.png");
            textureSprite->retain();
    
            saber                      =   CCParticleSun::create();
            saber->setTexture(textureSprite->getTexture());
            saber->setAutoRemoveOnFinish(true);
            saber->setEmissionRate(2000);
            saber->setLife(0.06);
            saber->setLifeVar(0.01);
            saber->setSpeed(0.0);
            saber->setSpeedVar(0.0);
            saber->setStartColor(ccc4f(0.2, 0.2, 0.9, 0.9));
            saber->setEndColor(ccc4f(0.1, 0.1, 0.9, 0.5));
            saber->setStartSize(16.0);
            saber->setStartSizeVar(2.0);
            saber->setEndSize(8.0);
            saber->setEndSizeVar(2.0);
            saber->setBlendAdditive(true);
            saber->setPosition( ccp(2020, 2020) ); // this is to place the saber out of screen initially.
            saberArray->addObject(saber);
            this->addChild(saber);
    
            textureSprite->release();
        }
    
        // in `ccTouchBegan()`
        this->createSabers();
        return true;
  3. Also, write `updateSabers()` and `removeSabers()` functions as follows and call them from `ccTouchMoved()` and `ccTouchEnded()` respectively.
        void LightSaber::updateSabers(CCArray *systems, CCPoint startPoint, CCPoint endPoint) {
            CCParticleSystem                *saber;
            for (int i = 0; i < systems->count(); i++) {
                saber           =   (CCParticleSystem *)systems->objectAtIndex(i);
                saber->setPosition( startPoint );
            }
        }
        void LightSaber::removeSabers() {
            CCObject                        *obj;
            CCParticleSystem                *particleSys;
            CCARRAY_FOREACH(saberArray, obj) {
                particleSys             =   (CCParticleSystem *)obj;
                particleSys->stopSystem();
                particleSys->setAutoRemoveOnFinish(true);
            }
        }

    Now we will have a nice light dot that moves around your finger with a mini trail.

4. Transformation of the `DOT` into a Real Saber (some Math)

Instead of creating a number of particle system along the line of the Saber, I planned to use the `setPosVar()` to spread the particles along the length of the saber. But spreading the available particles across a bigger length reduces the intensity of the whole system. To compensate for this, I create multiple copies of the Particle system and overlays one over other. That is why, we have a `saberArray` instead of a single `CCParticleSystem`.

Light Saber Geomtery

In the above diagram, the touchPoint is P1, which is the holding end of the Saber and the other end of the Saber is P2, which is an offset by certain distance from P1 for now. `ccTouchMoved()` determines this 2 points P1 and P2 and passes it onto `updateSabers()`. Update `updateSabers()` with the following code, which is to determine the spread area and rotation of the saber based on P1 and P2.

    float                           dist;
    CCPoint                         velocity;
    CCPoint                         delta, offset, final;
    CCPoint                         centerPoint;
    CCParticleSystem                *saber;
    float                           ang;

    delta                       =   ccpSub(endPoint, startPoint);
    centerPoint                 =   ccpAdd(ccpMult(delta, 0.5), startPoint);
    ang                         =   atan2f(delta.y, delta.x) * 180 / 3.14;
    dist                        =   ccpDistance(startPoint, endPoint);

    for (int i = 0; i < systems->count(); i++) {
        saber           =   (CCParticleSystem *)systems->objectAtIndex(i);
        saber->setPosition( centerPoint );
        saber->setPosVar( ccp(0, dist * 0.5) );
        saber->setRotation(90 - ang);
    }

In the above code, `delta` represents the distance vector between `startPoint` and `endPoint`, which when multiplied by 0.5, gives half the distance between startPoint and endPoint. This half-distance is then added to the `startPoint` which gives `centerPoint`, where our System will be positioned.

But without proper call to `setPosVar()`, this will again look like a dot offset by some position from the touchPoint. Since the System is now placed exactly in between P1 and P2, whose distance is `dist`, the emissions should be from -dist / 2 to +dist / 2, which is set by the call to `setPosVar()`

Its not yet complete, since the light saber will stand vertically through the centerPoint and it need to be rotated to align to P1 and P2, which is done by the `setRotation()` call.

5. Strengthening the Effect

Since the particle system is stretched along the length of the saber, the saber doesn’t look intensive. So to increase the intensity, lets replicate the particle system and overlay one over the other.

Since we already have an array for the particle system and all our `ccTouchMoved()` and `ccTouchEnded()` functions deal with the array, we need just one more change to achieve this, which is to create more number of Particle Systems and add it to the array, by modifying the `createSabers()` function.

Full Code

void LightSaber::createSabers() {
    CCParticleSystem                *saber;
    textureSprite               =   CCSprite::create("swipeParticle.png");
    textureSprite->retain();
    for (int i = 0; i < strength; i++) {         saber                      =   CCParticleSun::create();         saber->setTexture(textureSprite->getTexture());
        saber->setAutoRemoveOnFinish(true);
        saber->setEmissionRate(2000);
        saber->setLife(0.06);
        saber->setLifeVar(0.01);
        saber->setSpeed(0.0);
        saber->setSpeedVar(0.0);
        saber->setStartColor(ccc4f(0.2, 0.2, 0.9, 0.9));
        saber->setEndColor(ccc4f(0.1, 0.1, 0.9, 0.5));
        saber->setStartSize(16.0);
        saber->setStartSizeVar(2.0);
        saber->setEndSize(8.0);
        saber->setEndSizeVar(2.0);
        saber->setBlendAdditive(true);
        saber->setPosition( ccp(2020, 2020) );
        saberArray->addObject(saber);
        this->addChild(saber);
    }
    textureSprite->release();
}

void LightSaber::updateSabers(CCArray *systems, CCPoint startPoint, CCPoint endPoint) {
    float                           dist;
    CCPoint                         velocity;
    CCPoint                         delta, offset, final;
    CCPoint                         centerPoint;
    CCParticleSystem                *saber;
    float                           ang;
    delta                       =   ccpSub(endPoint, startPoint);
    centerPoint                 =   ccpAdd(ccpMult(delta, 0.5), startPoint);
    ang                         =   atan2f(delta.y, delta.x) * 180 / 3.14;
    dist                        =   ccpDistance(startPoint, endPoint);
    for (int i = 0; i < systems->count(); i++) {
        saber           =   (CCParticleSystem *)systems->objectAtIndex(i);
        saber->setPosition( centerPoint );
        saber->setPosVar( ccp(0, dist * 0.5) );
        saber->setRotation(90 - ang);
    }
}

void LightSaber::removeSabers() {
    CCObject                        *obj;
    CCParticleSystem                *particleSys;
    CCARRAY_FOREACH(saberArray, obj) {
        particleSys             =   (CCParticleSystem *)obj;
        particleSys->stopSystem();
        particleSys->setAutoRemoveOnFinish(true);
    }
}

#pragma mark -
#pragma mark TOUCH OVERRIDES

bool LightSaber::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) {
    this->createSabers();
    return                          true;
}

void LightSaber::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent) {
    CCPoint                         point;
    point                       =   pTouch->getLocation();
    this->updateSabers(saberArray, point, ccpAdd(point, ccp(-40, 100)));
}

void LightSaber::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent) {
    this->removeSabers();
}

void LightSaber::ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent) {
}

void LightSaber::registerWithTouchDispatcher() {
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, INT_MIN, true);
}

#pragma mark -
#pragma mark OBJECT LIFE CYCLE

bool LightSaber::init() {
    if ( !CCLayer::init() ) {
        return                      false;
    }

    this->setTouchEnabled(true);
    strength                    =   8;
    saberArray                  =   CCArray::create();
    saberArray->retain();
    return                          true;
}

LightSaber::~LightSaber() {
    saberArray->release();
}

Cocos2dx – Spinning Globe by Masking technique

In this post, lets see how we can implement a spinning world in cocos2d-x using masking technique. See the video below for sample output:


Low FPS is because of my Mac Mini; In device, it runs at 60 FPS

Downloads

1. Full Project

1. Create a Cocos2d-x Project

I am not going to discuss about how to setup a cocos2d-x project for iOS here. For info on how to setup cocos2d-x project, refer the official wiki.

All our code will go into `HelloWorld` class that comes with the template.

2. Masking Functionality

The hardest part in achieving our goal would be ‘Masking’ and we resort to Pavel Hancak’s tutorial on cocos2d-x masking. Please check the tutorial on how to mask a sprite. Based on that code, I rewrote the function as below:

CCSprite* HelloWorld::maskedSpriteWithSprite(CCSprite* pTextureSprite, CCSprite* pMaskSprite, float xoffset, float yoffset)
{
    // store the original positions of both sprites
    CCPoint textureSpriteOrigPosition(pTextureSprite->getPosition().x, pTextureSprite->getPosition().y);
    CCPoint maskSpriteOrigPosition(pMaskSprite->getPosition().x, pMaskSprite->getPosition().y);

    // convert the texture sprite position into mask sprite coordinate system
    pTextureSprite->setPosition(ccp(pTextureSprite->getContentSize().width/2 - pMaskSprite->getPosition().x + pMaskSprite->getContentSize().width/2 - xoffset, pTextureSprite->getContentSize().height/2 - pMaskSprite->getPosition().y + pMaskSprite->getContentSize().height/2 + yoffset));

    // position the mask sprite so that the bottom left corner lies on the (o,o) coordinates
    pMaskSprite->setPosition(ccp(pMaskSprite->getContentSize().width/2, pMaskSprite->getContentSize().height/2));

    CCRenderTexture* rt = CCRenderTexture::renderTextureWithWidthAndHeight((int)pMaskSprite->getContentSize().width, (int)pMaskSprite->getContentSize().height);

    ccBlendFunc bfMask = ccBlendFunc();
    bfMask.src = GL_ONE;
    bfMask.dst = GL_ZERO;
    pMaskSprite->setBlendFunc(bfMask);

    // turn off anti-aliasing around the mask sprite
    pMaskSprite->getTexture()->setAliasTexParameters();

    ccBlendFunc bfTexture = ccBlendFunc();
    bfTexture.src = GL_DST_ALPHA;
    bfTexture.dst = GL_ZERO;
    pTextureSprite->setBlendFunc(bfTexture);

    rt->begin();
    pMaskSprite->visit();
    pTextureSprite->visit();
    rt->end();

    // generate the resulting sprite
    CCSprite* pOutcome = CCSprite::spriteWithTexture(rt->getSprite()->getTexture());
    pOutcome->setFlipY(true);

    // restore the original sprite positions
    pTextureSprite->setPosition(textureSpriteOrigPosition);
    pMaskSprite->setPosition(maskSpriteOrigPosition);
    pOutcome->setPosition(maskSpriteOrigPosition);

    return pOutcome;
}

I added two parameters `xoffset` and `yoffset` to the original code. These parameters are to offset the position of underlying map. By varying this offset parameter in gameloop, we move the map linearly over a period of time.

3. Setup Sprites

Add two sprites `map` and `mask`, where

2d world map

`map` contains the full map. Only a part of will be seen at any time

When one edge of the scrolls into the keyhole (mask), we dont want the background to be visible, but the other edge to wrap and fit the gap. For this purpose, we duplicated the map horizontally, and when one edge is within keyhole, the duplicated part will fit in and when the edge crosses the keyhole completely, we reset the map to initial position. (Will see implementation details in subsequent sections).

mask for world map

`mask` picture is like a circular keyhole through which we sees part of a map.

Code to add these two sprites is as follows. Dont add the sprite as child of layer yet.

    
    // in init() function
    map = CCSprite::create("worldmap2d.jpg");
    map->setPosition( ccp(512, 768/2) );
    map->retain();

    mask = CCSprite::create("worldmapmask.png");
    mask->setPosition( ccp(512, 768/2) );
    mask->retain();

Apart from the `map` and `mask`, a star background is added behind.

4. Masking

With Pavel Hancak’s tutorial, masking is now very simple as following:

    // in init() function
    masked = maskedSpriteWithSprite(map, mask, 0, 100);
    addChild(masked);

Only the masked sprite should be added to the `layer`. I found the offsets on trial and error basis 😛 At this point, you should see circular part of the map and not the whole. We masked the map, but still it looks like a flattened map without any depth. To add some depth, add a `shade` picture, above all the sprites as follows

    // in init() function
    shade = CCSprite::create("worldmapshade.png");
    shade->setPosition( ccp(512, 768/2) );
    addChild(shade);
shade over world map

shade to be applied over the world map to create an illusion of depth

5. Spinning Animation

We dont have any real `3D` object here and so we achieve spinning animation by moving the map horizontally behind the keyhole (mask) infinitely. Add this final piece of code and we are done:

    // in init() function
    this->scheduleUpdate();

    void HelloWorld::update(float dt) {
        CCSprite *m;
        xoff += dt * 100;
        if (xoff > 1024) {
            xoff = 0;
        }
        m = maskedSpriteWithSprite(map, mask, xoff, 120);
        masked->setTexture(m->getTexture());
    }

In above code, `xoff` determines the amount of distance that the map should be moved. `Update` is the gamploop, where we increment `xoff` by small amounts in each frame, thus moving the map. When the `xoff` reaches its limit, we reset it. Changing `xoff` doen’t do anything magically. We create a new sprite based on offset position and apply the texture to our `masked` sprite.

Run the project and see the world spinning. Thats the end of the tutorial. Hope it helped :-)

Source can be browsed @ https://github.com/saiy2k/cocos2dx-masking 

HTML5 Orientation support (iPhone)

There is a great tutorial on html5rocks.com on how to use device orientation api in javascript. It has neat examples to demonstrate the functionality.

Problems with browser Orientation change:

But one issue that I faced is when the device(iPhone) is tilted, the browser’s orientation gets changed between landscape and portrait, which had a very negative impact on my project. I am currently working on a game named ‘HTML5 Space Maze'(in github), which is supposed to draw a continuous line based on device orientation. But when the device is tilted and the browser’s orientation gets changed, my game’s state gets messed up.Searched with a hope that there should be some API to lock the browser orientation. But there were none, though I got some work arounds(in stackoverflow) to mimic the orientation lock. But I am not satisfied with all this and postponed my R & D.

Phonegap, the savior:

Today morning I was trying out the phonegap framework for first time and my HTML5 Space Maze game integrated well with the phonegap and ran good in iPhone. Then I switched to the long-forgotten ‘orientation’ branch of my project and tried running the code through phonegap, locking the orientation of ‘CDVViewController’ to landscape left.Then tested the code in iPhone and it worked flawlessly. Since the ViewController is restricted to landscape left, no matter how you tilt the phone, the views wont rotate and thus making my game responding well.

I achieved my goal somehow today, but the target platform changed from web to iOS now. Not a big deal for me, I not yet decided or even thought on how and where to deploy the product once its complete.

C Program for 21 Sticks game

Here is a good brain refreshing puzzle that I solved recently. Thanks to my friend Raghul for sharing this good piece of puzzle with me. Its a turn based 2 player game (player and computer in our case). c

Now, I am to write a program for this game in such a way that however the player plays, he always picks the last one and thus the computer wins. Looked very simple at first and I came with the following program in 15 minutes and thought to myself (very proudly) that I solved it.

    
    int lineCount;
    int userPick;
    int aiPick;

    lineCount = 21;
    while (lineCount > 1) {
        printf("\n\n%d lines remaining. Pick 1 or 2 or 3 or 4: ", lineCount);
        scanf("%d", &userPick);
        lineCount -= userPick;

        if (lineCount == 1) {
            printf("\nyou win and AI lose\n");
            return;
        }
        if (lineCount <= 5) {
            aiPick = lineCount - 1;
        } else {
            aiPick = rand() % 4 + 1;
        }

        printf("you took %d lines", userPick);
        printf("\nAI took %d lines", aiPick);
        lineCount -= aiPick;
    }
    if (lineCount == 1) {
        printf("\nyou win and AI lose\n");
        return;
    } else {
        printf("\n1 line remains and u lose\n\n");
    }

The AI seems to be working good, but its not perfect. It fails occasionally, especially when the player gets a turn with the remaining number of sticks in the range 2 to 5. So I tried to fix this by playing in such a way that AI always gets its turn when the remaining number of sticks is in the range 2 to 5. But I couldn’t figure out how to do this inside my little mind. So I took a pen and paper and started writing the remaining number of lines, all possibilities of AI’s move and corresponding result, which resulted in a table such as this:

Possible States with results
Remaining Sticks AI Move Worst Case
2 1 Win
3 2 Win
4 3 Win
5 4 Win
6 1 Lose
2 Lose
3 Lose
4 Lose
7 1 Win
2 Lose
3 Lose
4 Lose
8 1 Lose
2 Win
3 Lose
4 Lose
9 1 Lose
2 Lose
3 Win
4 Lose
20 1 Lose
2 Lose
3 Lose
4 Win
21 1 Lose
2 Lose
3 Lose
4 Lose

Only after completely writing out the above table, I found that when there are some certain magic remaining sticks at your turn, regardless of your move, if ur opponent plays his best move, he can win. The magic numbers are found to be 21, 16, 11, 6 and of course 1.

Since the player begins the game with 21 remaining sticks (one of the magic number), as long as my AI plays its best move, it can win. The AI’s best move nothing but the move which makes the remaining number of lines to the next lowest magic number and as we continue when the player plays his move, he will have the remaining sticks as 16, 11, 6 and finally 1 and he loses. Here is the final working program with perfect AI:

#include <stdio.h>

int lineCount;

void main() {

    int userPick;
    int aiPick;

    lineCount = 21;
    while (lineCount > 1) {
        printf("\n\n%d lines remaining. Pick 1 or 2 or 3 or 4: ", lineCount);
        scanf("%d", &userPick);

        lineCount -= userPick;

        if (lineCount >= 17 && lineCount <= 20) {
            aiPick = lineCount - 16;
        } else if (lineCount >= 12 && lineCount <= 15) {
            aiPick = lineCount - 11;
        } else if (lineCount >= 7 && lineCount <= 10) {
            aiPick = lineCount - 6;
        } else {
            aiPick = lineCount - 1;
        }

        printf("you took %d lines", userPick);
        printf("\nAI took %d lines", aiPick);
        lineCount -= aiPick;
    }

    printf("\n1 line remains and u lose\n\n");
}

I am sure this is not the best way to solve this problem, I should have done some research on maths and find some elegant solution, but I guess this is fine for the first step and to achieve the goal. I will look into other possible elegant solutions and if I find any, I will post about it later.

Happy Programming !!!

Extended EPGLTransitionView

Get the project @ https://github.com/saiy2k/EPGLTransitionView

I was searching for some techniques to do flip screen transitions as in Flipboard iphone app and I found this awesome project, EPGLTransitionView.

EPGLTransitionView is an excellent project to do some cool UIViewController transitions. It basically works by taking screenshots of the two views you would like to switch and manipulating the screenshots with OpenGL commands for cool transition effects. Official URL for the project is http://www.memention.com/blog/2010/02/28/Bells-and-Whistles.html

The project comes with 3 basic transitions:

fall turn flip

Reverse Animation:

The class Demo2Transition in the ‘Demo Project’ provided is exactly what I wanted. But it lacked certain features like turning in reversed direction and turning from bottom up.

So I downloaded the project from github, tinkered with the Demo2Transition class and in a while found a way to reverse the animation. Actually reversing was not a big deal, the animation is controlled by a control variable f, which determines the turned position of view at any given time. I just reversed the initial and final values and it worked flawlessly.

Up Down Transition:

I am not much into OpenGL before, so pulling this’up down turn transition’ took me some time with lots of trial and errors. I did this by changing the order of vertices and texture co-ordinates in which the image is read.

Now both of reversal and up down transition and working great except for 1 frame flicker in ‘reverse animation’. Working on fixing it.

My fork:

I forked the project and committed my changes to the forked repo @ https://github.com/saiy2k/EPGLTransitionView

I also sent a pull request to the original repo, lets see if its accepted. I done a few open source projects before, but this is the first time that I contribute to some one else’s project.

Usage:

I changed the ‘init’ method in the original project into the following:

– (id)initWithView1:(UIView*)view1 andView2:(UIView*)view2 delegate:(id)_delegate fwdDirection:(BOOL)fwd

where `view1` is the UIView which is currently being active and `view2` is the UIView which need to be shown, `delegate` is the transition class that conforms to `EPGLTransitionViewDelegate` protocol, and `fwd` is a flag that determines the direction of transition.

The EPGL TransitionView can reverse the transition only if the Transition class supports it by implementing the `isVertical` property. If it isn’t then, the value of `fwd` doesn’t have any effect.

A sample use case is given below:

UIViewController *controller = [[[UIViewController alloc] init] autorelease];
controller.delegate = self;
controller.view.frame = CGRectMake(0, 20, 320, 460);
NSObject<EPGLTransitionViewDelegate> *transition;
EPGLTransitionView *glview;

transition = [[[Demo2Transition alloc] init] autorelease];
[transition setIsVertical:YES];
glview = [[EPGLTransitionView alloc]
initWithView1:self.view
andView2:controller.view
delegate:transition
fwdDirection:NO];

[glview startTransition];
[self presentModalViewController:controller animated:NO];

Happy flipping.