Cocos2D Comparison

You love Cocos2D? We too!

In fact, our development team has worked with different versions of cocos2d in the past: When cocos2d-iphone came out, it was a joy to work with and the best open source game engine available for 2D games. With Android rising up as the biggest smartphone platform, the need to write a game only once for both iOS & Android got bigger and bigger, to prevent porting and duplicating code from Obj-C to Java. As a common ground, C++ can be used as a language both platforms work with and still have maximum performance compared to HTML5-based technology. This was the origin of cocos2d-x.

Unfortunately, working with C++ for game logic requires a multitude of development time and source code compared with scripting languages. In the Full Demo Game Comparison below the difference is even bigger than 60% what can be saved when using JavaScript and QML instead of C++. The game is still running at solid 60 fps on mobile devices, because only your game logic is written in JavaScript, whereas the core engine parts of V-Play are written in C++.

Additionally, to work with the Android NDK is a real pain and much boilerplate code is required to make native Java SDKs or 3rd party plugins work with a common C++ source. This is why V-Play chose to simplify these cross-platform pains by providing a high level API on top of cocos2d-x, which is used as the rendering engine internally. Plugins like GameCenter, Facebook or Flurry are available from JavaScript as ready-made components, without the need to reinvent the wheel with every game.

As we were using cocos2d a lot in the past as well, we believe these benefits V-Play can offer you as an existing cocos2d developer:

Build Server

... (see Target Group Benefits.docx)

Gaming Services

... (see Target Group Benefits.docx)

API Comparison

Because V-Play shares many of the concepts of cocos2d, it is straightforward to try V-Play as a cocos2d developer. To make it even easier, in the following sections cocos2d classes are compared with the V-Play counterparts. Small source code snippets explain the difference in code and allow you to get started quickly.

Scenes, Layers and Nodes

V-Play also uses the concept of a scene graph: when a parent node moves, its children move along with it. In V-Play, parent-child-relationships are defined by defining children items within the parent items. CCNode, CCLayer, CCLayerColor, CCScene and CCMenu classes can all be created with the Item, Rectangle and Scene element in QML.

 import VPlay 2.0
 import QtQuick 2.0

 Scene {
   Rectangle {
     color: "red"
     anchors.fill: parent
   }

   Item {
     anchors.right: parent.right
     y: 15
     width: 30
     height: 20

     Rectangle {
       anchors.fill: parent
       anchors.rightMargin: 5
       color: Qt.rgba(0.5, 0.5, 0, 1)
     }
   }
 }
 #include "cocos2d.h"

 // MyScene is another class, where we put the contents into
 CCScene *scene = MyScene::scene();

 CCLayerColor *layerColor = CCLayerColor::layerWithColorWidthHeight(
       ccc4(255, 0, 0, 255),
       scene->getContentSize().width,
       scene->getContentSize().height);
 scene->addChild(layerColor);

 CCNode *node = new CCNode::node();
 node->setContentSize(CCSize(30, 20));
 node->setPositionX( node->getParent()->getContentSize().width - node->getContentSize().width);
 node->setPositionY(15);
 layerColor->addChild(node);

 CCLayerColor *layerColor2 = CCLayerColor::layerWithColorWidthHeight(
       ccc4(255*0.5, 255*0.5, 0, 255),
       node->getContentSize().width - 5,
       node->getContentSize().height);
 node->addChild(layerColor2);

Images & Sprites

CCSprite and CCSpriteBatchNode have their V-Play counterparts in the Image, MultiResolutionImage Sprite, SpriteSequence and SpriteBatchContainer elements. Reading from sprite sheets generated from tools like Texture Packer is possible with the SingleSpriteFromFile component.

V-Play supports scaling to multiple resolutions out-of-the box. Please refer to the guide about How to create mobile games for different screen sizes and resolutions for more information about this topic.

The following example shows how to display an image that was saved in 3 different sizes to match common device categories best. It also demonstrates how to play a SpriteSheet animation: the jump animation is played 3 times, and then it is switched to the looping walking animation.

 import VPlay 2.0
 import QtQuick 2.0

 Scene {

   MultiResolutionImage {
     source: "staticImage.png" // the file path gets modified automatically based on the contentScaleFactor
   }

   SpriteSequence {
     x: 200
     y: 100
     spriteSheetSource: "animated-character-spritesheet.png"

     // the first sprite gets played automatically
     Sprite {
       name: "jump"
       frameWidth: 32
       frameHeight: 32
       frameCount: 4
       startFrameColumn: 5
       frameRate: 10
       // this animation is played 4 times, and then switched to walk
       loop: false
       repeatCount: 4
       to: "walk"
     }

     Sprite {
       name: "walk"
       frameWidth: 32
       frameHeight: 32
       frameCount: 4
       startFrameColumn: 1
       frameRate: 20
     }
   }// SpriteSequence

 }

Multi-Touch

Detection of touches is supported with the MouseArea element. It also supports easy drag and drop of elements. For multiple touches, you can use the MultiTouchArea which has built-in support for gestures like pinching or rotate-pinching. In cocos2d, dragging would need to be detected manually and gestures are not detected by default.

In the following source code, the red element is draggable in the scene. The blue one responds to the pinch gesture and changes by changing the scale of the Rectangle, and gets rotated by turning 2 fingers.

 import VPlay 2.0
 import QtQuick 2.0

 Scene {
   id: scene

   Rectangle {
     color: "red"
     width: 60
     height: 40

     MultiTouchArea {
       anchors.fill: parent
       multiTouch.target: parent // the red rectangle gets draggable
     }
   }

   Rectangle {
     color: "blue"
     width: 60
     height: 40
     x: scene.width/2

     MultiTouchArea {
       anchors.fill: parent

       multiTouch.target: parent
       multiTouch.rotationMode: MultiTouch.Infinity // allow rotating when 2 fingers are twisted

       // limit the scale of the blue rectangle between 0.5 and 3
       multiTouch.minimumScale: 0.5
       multiTouch.maximumScale: 3
     }
   }

 }

Labels

The CCLabelTTF equivalent is the QML Text element. Custom fonts can be loaded with the FontLoader element. In this example, the default font is used with a red color.

 Text {
   text: "Hello V-Play"
   color: "black"
   pos: Qt.point(20, 30)
   font.pixelSize: 32
 }
 CCLabelTTF *label = CCLabelTTF::create("Goodbye Cocos2d", 32);
 label->retain();
 label->setColor( ccc3(0, 0, 0));
 label->setPosition( ccp(20, 30));
 this->addChild(label);

Actions & Transitions

CCAction in cocos2d has its equivalent in the Animation element in QML. They both support the same features: interpolate properties to a desired target value within a set duration and an optional easing type. Transitions between scenes or any QML Item can also be done with the Animation and State elements very easily. Multiple animations can be connected with the SequentialAnimation and ParallelAnimation elements.

The MovementAnimation additionally allows to modify any property of an object infinitely with a velocity and acceleration factor. This could be used for example to scroll the world in a side-scroller game.

The following example shows how to change the opacity of a rectangle from 1 to 0.5, and afterwards move it 50 px to the right.

 import VPlay 2.0
 import QtQuick 2.0

 Scene {

   Rectangle {
     id: rectangle
     width: 100
     height: 60
     color: "red"

     SequentialAnimation {

       // makes the rectangle semi-transparent, and after it is finishedstarts to move it right
       NumberAnimation {
         target: rectangle
         property: "opacity"
         duration: 1000
         to: 0.5
       }

       // moves the rectangle 80px to the right in 1.5 seconds with an easing curve
       NumberAnimation {
         target: rectangle
         property: "x"
         duration: 1500
         to: 80
         easing: Easing.InBounce
       }

     }// SequentialAnimation
   }// Rectangle
 }// Scene

Particle Effects

The V-Play Particle element can be used instead of the CCParticleSystem class of cocos2d. V-Play comes with an own Particle Editor which is available across all platforms. It supports creation of new particle effects and modifying them at runtime. As the same rendering engine is used like in your game, you can see performance impact of tweaking the effects properties immediately. Thus you can rely on the performance measurement to be the same as in your game. This is even possible on mobile devices, which makes it great for performance testing of different effects.

The Particle Editor is available with full source code and contains many popular particle types like fire and smoke. Feel free to use them in your game, or take them as a starting point to create a new type.

V-Play supports the very same particle properties as cocos2d, which makes porting the particle effects very easy. See the source code comparison below:

 import VPlay 2.0
 import QtQuick 2.0

 ParticleVPlay {
   // Particle location properties
   x: 40; y: 50

   // Particle configuration properties
   maxParticles: 38
   particleLifespan: 0.9
   particleLifespanVariance: 0.2
   startParticleSize: 7
   startParticleSizeVariance: 2
   finishParticleSize: 45
   finishParticleSizeVariance: 10

   // Emitter behavior
   emitterType: Particle.Gravity
   duration: Particle.Infinite
   positionType: Particle.Relative

   // Gravity mode settings
   speed: 85
   speedVariance: 2

   // Appearance
   startColor: Qt.rgba(0.76, 0.25, 0.12, 1)
   finishColor: Qt.rgba(0, 0, 0, 1)
   textureFileName: "particleFire.png"

   Component.onCompleted: {
     // start when finished loading
     start()
   }

 }
 #include "cocos2d.h"

 CCParticleSystem *emitter = new CCParticleSystemQuad();
 emitter->setPosition( 40, 50);

 // Particle Configuration properties
 emitter->initWithTotalParticles(38);
 emitter->setLife(0.9);
 emitter->setLifeVar(0.2);
 emitter->setStartSize(7);
 emitter->setStartSizeVar(2);
 emitter->setEndSize(45);
 emitter->setEndSizeVar(10);

 // Emitter behavior
 emitter->setEmitterMode(kCCParticleModeGravity);
 emitter->setDuration(kCCParticleDurationInfinity);
 emitter->setPositionType(kCCPositionTypeRelative);

 // Gravity mode settings
 emitter->setSpeed(85);
 emitter->setSpeedVar(2);

 // Appearance
 emitter->setStartColor(ccc4(0.76*255, 0.25*255, 0.12*255, 255));
 emitter->setFinishColor(ccc4(0, 0, 0, 255));
 emitter->setTexture(CCTextureCache::sharedTextureCache()->addImage("particleFire.png"));

 this->addChild(emitter);

Physics

V-Play comes with full support of the latest Box2D version available from QML and JavaScript. However, internally the C++ version of Box2D is used for maximum performance. You can use Box2D either for calculating your entity positions, or just for collision detection if you want to move entities with animations for example.

The following example adds a ground rectangle and a ball-shaped image with physics colliders. The ball falls down to the ground due to gravity.

 import VPlay 2.0

 import QtQuick 2.0

 Scene {
   id: scene

   PhysicsWorld {
     gravity.y: 10
   }

   Image {
     x: scene.width/2 // position it at the center in the beginning
     source: "ball.png"

     CircleCollider {
       radius: parent.width/2
       restitution: 0.5 // make the ball bounce on the ground
     }
   }

   // the ground, which does not move
   Rectangle {
     width: scene.width
     anchors.bottom: parent.bottom
     height: 40
     color: "grey"

     BoxCollider {
       anchors.fill: parent
       bodyType: Body.Static
     }
   }

 }

Audio

The Sound and BackgroundMusic elements are similar to the API from SimpleAudioEngine in CocosDenshion. The following sample code shows how to play a sound when the scene is clicked. A looping background music is started when the application starts, and gets paused when the app is brought to the back automatically on mobile devices.

 import VPlay 2.0
 import QtQuick 2.0

 Scene {

   // gets started once the application starts
   BackgroundMusic {
     source: "bgMusic.mp3"
   }

   SoundEffectVPlay {
     id: soundEffect
     source: "soundEffect.wav"
   }

   MouseArea {
     anchors.fill: parent
     onClicked: soundEffect.play()
   }
 }

Parallax

The CCParallaxNode equivalent in V-Play is ParallaxItem. Additionally, you can use the ParallaxScrollingBackground element for side-scroller games that have an endlessly scrolling background. It is a nice convenience component which simplifies this task.

The following sample shows a slower scrolling background image with mountains and hills in the front.

 import VPlay 2.0
 import QtQuick 2.0

 Scene {
   id: scene
   objectName: "sceneName"
   width: 480
   height: 320

   // The levelItem will be moved into the right direction to demonstrate the parallax effect.
   Item {
     id: levelItem

     // move the level item with constant speed to demonstrate the parallax effect
     MovementAnimation {
       target: levelItem
       property: "x"
       velocity: 10
       running: true
     }

     // First background image consists of an image moving slower than the level item
     ParallaxItem {
       ratio: Qt.point(0.8,1.0)
       Image {
         source: "img/background-mountains.png"
       }
     }

     // Second background image consists of 2 different images put next to each other, moving with the same speed as the level item
     ParallaxItem {
       ratio: Qt.point(1.0,1.0)
       Image {
         x: -width
         source: "img/background-hills.png"
       }

       Image {
         id: hill2
         source: "img/background-hills2.png"
       }
     }
   }
 }

For a full list of the gaming components, visit the API reference. Get started with a step by step introduction to V-Play here.

Full Demo Game Comparison

As developers, we prefer source code instead of lengthy texts. So in addition to explain the benefits and API Comparison above, we show the differences & benefits in a real-game example: The popular cocos2d tutorial How To Make A Simple iPhone Game with Cocos2D is implemented both with cocos2d-x (right column) and V-Play (left column).

In the game you play a ninja and throw stars against invading monsters. There are 2 different scenes: one GameOver scene which shows if you won or lost, and the Game scene. In the V-Play version, Box2D was used for collision detection and a NumberAnimation for calculating the movement of the stars and monsters.

When comparing the source code, you will see that it is significantly faster to write the same game with V-Play and QML/JavaScript. In fact the same game is implemented with less than 40% of the original source code in C++, to achieve the same result! From 369 lines of code only 150 remain with V-Play. And that with just a simple game like this one - for more complex projects the difference gets even bigger!

In the full demo game comparison below, you can also see that far less characters are needed per loc in QML. So that's why V-Play will boost your productivity: less characters, less code, less errors, shorter time to develop. To be fair with the comparison, only real code lines are taken and comments and lines with empty spaces are left out.

For more sample games, see the V-Play documentation and its open source gaming demos, which are part of the V-Play SDK. A full list of available components including support for GameCenter, Facebook and Flurry Analytics can be found in the API reference.

 /*
  In this demo you play a ninja and throw stars against invading monsters.
  This game is written in a single QML file with just 150 lines of code.

  It contains following features:
   * multi-resolution images
   * switching between 2 scenes
   * physics for collision detection
   * audio
   * animations for movement of the monsters and stars
 */
 import VPlay 2.0
 import QtQuick 2.0

 GameWindow {
  // change the GameWindow size at runtime by pressing 1-7
  // the content of the logical scene size (480x320 by default)
  // gets scaled to the window size based on the scaleMode
  screenWidth: 960
  screenHeight: 640

  // gets set from Monster, after it reached the player
  property bool gameWon

  property int monstersDestroyed
  onMonstersDestroyedChanged: {
   if(monstersDestroyed > 5) {
    // you won the game, shot at 5 monsters
    changeToGameOverScene(true)
   }
  }

  // for creating monsters and projectiles at runtime dynamically
  EntityManager {
   id: entityManager
   entityContainer: scene
  }

  BackgroundMusic {
   source: "snd/background-music-aac.wav"
  }

  // make a white background for the window, default is black
  Rectangle {
   anchors.fill: parent
   color: "white"
  }

  Scene {
   id: scene
   // the "logical size", gets auto-scaled to GameWindow size
   width: 480
   height: 320

   // for collision detection between monsters and projectiles
   PhysicsWorld {}

   MultiResolutionImage {
    id: player
    anchors.verticalCenter: parent.verticalCenter
    // correct image version is used based on GameWindow size
    source: "img/Player-sd.png"
   }

   // components get not created immediately, but in addTarget()
   // could also be put into an own qml file (e.g. Monster.qml)
   Component {
    id: monster

    EntityBase {
     // required for removing all of them when the game is lost
     entityType: "monster"

     MultiResolutionImage {
      id: monsterImage
      source: "img/Target-sd.png"
     }

     y: utils.generateRandomValueBetween(0, scene.height)

     NumberAnimation on x {
      // start at the right side
      from: scene.width
      // move the monster to the left side of the screen
      to: -monsterImage.width
      // vary animation duration between 2-4 seconds
      duration: utils.generateRandomValueBetween(2000, 4000)
      onCompleted: {
       console.debug("monster reached base")
       changeToGameOverScene(false)
      }
     }

     BoxCollider {
      // make the collider as big as the image
      anchors.fill: monsterImage
      // use Box2D only for collision detection
      // move the entity with the NumberAnimation above
      collisionTestingOnlyMode: true

      fixture.onBeginContact: {
       // if the collided type was a projectile
       // both can be destroyed and the player gets a point
       var collidedEntity = other.parent.parent.parent

       if(collidedEntity.entityType === "projectile") {
        monstersDestroyed++
        // remove the projectile entity
        collidedEntity.removeEntity()
        // remove the monster
        removeEntity()
       }
      }
     }// BoxCollider
    }// EntityBase
   }// Component

   Component {
    id: projectile

    EntityBase {
     entityType: "projectile"

     MultiResolutionImage {
      id: monsterImage
      source: "img/Projectile-sd.png"
     }

     // set when a new projectile is created
     // in the MouseArea below
     property alias to: moveTo.to
     property alias duration: moveTo.duration

     PropertyAnimation on pos {
      id: moveTo
      from: player.pos
     }

     BoxCollider {
      anchors.fill: monsterImage
      collisionTestingOnlyMode: true
     }
    }// EntityBase
   }// Component

   SoundEffect {
    id: projectileCreationSound
    // gets played when a projectile is created below
    source: "snd/pew-pew-lei.wav"
   }

   MouseArea {
    anchors.fill: parent
    onReleased: {

     // see Ray Wenderlich tut for an explanation of this code

     // Determine offset of player to touch location
     var offset = Qt.point(
        mouseX - player.x,
        mouseY - player.y
        );

     // Bail out if we are shooting down or backwards
     if(offset.x <= 0)
      return;

     // Determine where we wish to shoot the projectile to
     var realX = scene.width
     var ratio = offset.y / offset.x
     var realY = (realX * ratio) + player.y
     var destination = Qt.point(realX, realY)

     // Determine the length of how far we are shooting
     var offReal = Qt.point(realX - player.x, realY - player.y)
     var length = Math.sqrt(offReal.x*offReal.x + offReal.y*offReal.y)
     // speed of the projectile should be 480pt per second
     var velocity = 480
     // seconds to milliseconds
     var realMoveDuration = length / velocity * 1000

     entityManager.createEntityFromComponentWithProperties(
           projectile, {"to": destination, "duration": realMoveDuration})

     projectileCreationSound.play()
    }
   }// onReleased
  }// MouseArea

  // switch to this scene, after the game was lost or won
  // it switches back to the gameScene after 3 seconds
  Scene {
   id: gameOverScene
   visible: false
   Text {
    anchors.centerIn: parent
    text: gameWon ? "You won :)" : "You lost"
   }

   onVisibleChanged: {
    if(visible) {
     // make the scene invisible after 3 seconds
     returnToGameSceneTimer.start()
    }
   }

   Timer {
    id: returnToGameSceneTimer
    interval: 3000
    onTriggered: {
     scene.visible = true
     gameOverScene.visible = false
    }
   }
  }// GameOverScene

  Timer {
   // only enable the creation timer when gameScene is visible
   running: scene.visible == true
   repeat: true
   // a new target(=monster) is spawned every second
   interval: 1000
   onTriggered: addTarget()
  }

  function addTarget() {
   console.debug("create a new monster")
   entityManager.createEntityFromComponent(monster)
  }

  function changeToGameOverScene(won) {

   gameWon = won

   gameOverScene.visible = true
   scene.visible = false

   // reset the game variables and
   // remove all projectiles and monsters
   monstersDestroyed = 0
   entityManager.removeEntitiesByFilter(["projectile", "monster"])
  }
 }

AppDelegate.h

 #ifndef  _APP_DELEGATE_H_
 #define  _APP_DELEGATE_H_

 #include "cocos2d.h"

 /**
 @brief    The cocos2d Application.

 The reason for implement as private inheritance is to hide some interface call by CCDirector.
 */
 class  AppDelegate : private cocos2d::CCApplication
 {
 public:
   AppDelegate();
   virtual ~AppDelegate();

   /**
     @brief    Implement CCDirector and CCScene init code here.
     @return true    Initialize success, app continue.
     @return false   Initialize failed, app terminate.
     */
   virtual bool applicationDidFinishLaunching();

   /**
     @brief  The function be called when the application enter background
     @param  the pointer of the application
     */
   virtual void applicationDidEnterBackground();

   /**
     @brief  The function be called when the application enter foreground
     @param  the pointer of the application
     */
   virtual void applicationWillEnterForeground();
 };

 #endif // _APP_DELEGATE_H_

AppDelegate.cpp

 #include "AppDelegate.h"
 #include "HelloWorldScene.h"
 #include "SimpleAudioEngine.h"

 USING_NS_CC;

 AppDelegate::AppDelegate() {

 }

 AppDelegate::~AppDelegate()
 {
 }

 bool AppDelegate::applicationDidFinishLaunching() {
   // initialize director
   CCDirector *pDirector = CCDirector::sharedDirector();

   pDirector->setOpenGLView(CCEGLView::sharedOpenGLView());

   CCSize screenSize = CCEGLView::sharedOpenGLView()->getFrameSize();
   CCSize designSize = CCSizeMake(480, 320);
   std::vector<std::string> searchPaths;

   if (screenSize.height > 320)
   {
     searchPaths.push_back("hd");
     searchPaths.push_back("sd");
     pDirector->setContentScaleFactor(640.0f/designSize.height);
   }
   else
   {
     searchPaths.push_back("sd");
     pDirector->setContentScaleFactor(320.0f/designSize.height);
   }

   CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths);

   CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width, designSize.height, kResolutionNoBorder);

   // turn on display FPS
   pDirector->setDisplayStats(true);

   // set FPS. the default value is 1.0/60 if you don't call this
   pDirector->setAnimationInterval(1.0 / 60);

   // create a scene. it's an autorelease object
   CCScene *pScene = HelloWorld::scene();

   // run
   pDirector->runWithScene(pScene);

   return true;
 }

 // This function will be called when the app is inactive. When comes a phone call,it's be invoked too
 void AppDelegate::applicationDidEnterBackground() {
   CCDirector::sharedDirector()->stopAnimation();

   // if you use SimpleAudioEngine, it must be pause
   CocosDenshion::SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
 }

 // this function will be called when the app is active again
 void AppDelegate::applicationWillEnterForeground() {
   CCDirector::sharedDirector()->startAnimation();

   // if you use SimpleAudioEngine, it must resume here
   CocosDenshion::SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
 }

HelloWorldScene.h

 #ifndef __HELLOWORLD_SCENE_H__
 #define __HELLOWORLD_SCENE_H__

 #include "cocos2d.h"

 #include "SimpleAudioEngine.h"

 class HelloWorld : public cocos2d::CCLayerColor
 {
 public:
   HelloWorld();
   ~HelloWorld();

   // Here's a difference. Method 'init' in cocos2d-x returns bool,
   // instead of returning 'id' in cocos2d-iphone
   virtual bool init();

   // there's no 'id' in cpp, so we recommand to return the exactly class pointer
   static cocos2d::CCScene* scene();

   // a selector callback
   virtual void menuCloseCallback(cocos2d::CCObject* pSender);

   // implement the "static node()" method manually
   CREATE_FUNC(HelloWorld);

   void spriteMoveFinished(cocos2d::CCNode* sender);

   void gameLogic(float dt);

   void updateGame(float dt);

   void registerWithTouchDispatcher();
   void ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event);

 protected:
   cocos2d::CCArray *_targets;
   cocos2d::CCArray *_projectiles;
   int _projectilesDestroyed;

   void addTarget();

 };

 #endif  // __HELLOWORLD_SCENE_H__

HelloWorldScene.cpp

 #include "HelloWorldScene.h"
 #include "GameOverScene.h"
 #include "SimpleAudioEngine.h"

 using namespace cocos2d;

 HelloWorld::~HelloWorld()
 {
   if (_targets)
   {
     _targets->release();
     _targets = NULL;
   }

   if (_projectiles)
   {
     _projectiles->release();
     _projectiles = NULL;
   }

   // cpp don't need to call super dealloc
   // virtual destructor will do this
 }

 HelloWorld::HelloWorld()
   :_targets(NULL)
   ,_projectiles(NULL)
   ,_projectilesDestroyed(0)
 {
 }

 CCScene* HelloWorld::scene()
 {
   CCScene * scene = NULL;
   do
   {
     // 'scene' is an autorelease object
     scene = CCScene::create();
     CC_BREAK_IF(! scene);

     // 'layer' is an autorelease object
     HelloWorld *layer = HelloWorld::create();
     CC_BREAK_IF(! layer);

     // add layer as a child to scene
     scene->addChild(layer);
   } while (0);

   // return the scene
   return scene;
 }

 // on "init" you need to initialize your instance
 bool HelloWorld::init()
 {
   bool bRet = false;
   do
   {
     //////////////////////////////////////////////////////////////////////////
     // super init first
     //////////////////////////////////////////////////////////////////////////

     CC_BREAK_IF(! CCLayerColor::initWithColor( ccc4(255,255,255,255) ) );

     //////////////////////////////////////////////////////////////////////////
     // add your codes below...
     //////////////////////////////////////////////////////////////////////////

     // 1. Add a menu item with "X" image, which is clicked to quit the program.

     // Create a "close" menu item with close icon, it's an auto release object.
     CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
           "CloseNormal.png",
           "CloseSelected.png",
           this,
           menu_selector(HelloWorld::menuCloseCallback));
     CC_BREAK_IF(! pCloseItem);

     // Place the menu item bottom-right conner.
     CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
     CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();

     pCloseItem->setPosition(ccp(origin.x + visibleSize.width - pCloseItem->getContentSize().width/2,
                                 origin.y + pCloseItem->getContentSize().height/2));

     // Create a menu with the "close" menu item, it's an auto release object.
     CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
     pMenu->setPosition(CCPointZero);
     CC_BREAK_IF(! pMenu);

     // Add the menu to HelloWorld layer as a child layer.
     this->addChild(pMenu, 1);

     /////////////////////////////
     // 2. add your codes below...
     CCSprite *player = CCSprite::create("Player.png", CCRectMake(0, 0, 27, 40) );

     player->setPosition( ccp(origin.x + player->getContentSize().width/2,
                              origin.y + visibleSize.height/2) );
     this->addChild(player);

     this->schedule( schedule_selector(HelloWorld::gameLogic), 1.0 );

     this->setTouchEnabled(true);

     _targets = new CCArray;
     _projectiles = new CCArray;

     // use updateGame instead of update, otherwise it will conflict with SelectorProtocol::update
     // see http://www.cocos2d-x.org/boards/6/topics/1478
     this->schedule( schedule_selector(HelloWorld::updateGame) );

     CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("background-music-aac.wav", true);

     bRet = true;
   } while (0);

   return bRet;
 }

 void HelloWorld::menuCloseCallback(CCObject* pSender)
 {
   // "close" menu item clicked
   CCDirector::sharedDirector()->end();
 }

 // cpp with cocos2d-x
 void HelloWorld::addTarget()
 {
   CCSprite *target = CCSprite::create("Target.png", CCRectMake(0,0,27,40) );

   // Determine where to spawn the target along the Y axis
   CCSize winSize = CCDirector::sharedDirector()->getVisibleSize();
   float minY = target->getContentSize().height/2;
   float maxY = winSize.height -  target->getContentSize().height/2;
   int rangeY = (int)(maxY - minY);
   // srand( TimGetTicks() );
   int actualY = ( rand() % rangeY ) + (int)minY;

   // Create the target slightly off-screen along the right edge,
   // and along a random position along the Y axis as calculated
   target->setPosition(
         ccp(winSize.width + (target->getContentSize().width/2),
             CCDirector::sharedDirector()->getVisibleOrigin().y + actualY) );
   this->addChild(target);

   // Determine speed of the target
   int minDuration = (int)2.0;
   int maxDuration = (int)4.0;
   int rangeDuration = maxDuration - minDuration;
   // srand( TimGetTicks() );
   int actualDuration = ( rand() % rangeDuration ) + minDuration;

   // Create the actions
   CCFiniteTimeAction* actionMove = CCMoveTo::create( (float)actualDuration,
                                                      ccp(0 - target->getContentSize().width/2, actualY) );
   CCFiniteTimeAction* actionMoveDone = CCCallFuncN::create( this,
                                                             callfuncN_selector(HelloWorld::spriteMoveFinished));
   target->runAction( CCSequence::create(actionMove, actionMoveDone, NULL) );

   // Add to targets array
   target->setTag(1);
   _targets->addObject(target);
 }

 void HelloWorld::spriteMoveFinished(CCNode* sender)
 {
   CCSprite *sprite = (CCSprite *)sender;
   this->removeChild(sprite, true);

   if (sprite->getTag() == 1)  // target
   {
     _targets->removeObject(sprite);

     GameOverScene *gameOverScene = GameOverScene::create();
     gameOverScene->getLayer()->getLabel()->setString("You Lose :[");
     CCDirector::sharedDirector()->replaceScene(gameOverScene);

   }
   else if (sprite->getTag() == 2) // projectile
   {
     _projectiles->removeObject(sprite);
   }
 }

 void HelloWorld::gameLogic(float dt)
 {
   this->addTarget();
 }

 // cpp with cocos2d-x
 void HelloWorld::ccTouchesEnded(CCSet* touches, CCEvent* event)
 {
   // Choose one of the touches to work with
   CCTouch* touch = (CCTouch*)( touches->anyObject() );
   CCPoint location = touch->getLocation();

   CCLog("++++++++after  x:%f, y:%f", location.x, location.y);

   // Set up initial location of projectile
   CCSize winSize = CCDirector::sharedDirector()->getVisibleSize();
   CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
   CCSprite *projectile = CCSprite::create("Projectile.png", CCRectMake(0, 0, 20, 20));
   projectile->setPosition( ccp(origin.x+20, origin.y+winSize.height/2) );

   // Determine offset of location to projectile
   float offX = location.x - projectile->getPosition().x;
   float offY = location.y - projectile->getPosition().y;

   // Bail out if we are shooting down or backwards
   if (offX <= 0) return;

   // Ok to add now - we've double checked position
   this->addChild(projectile);

   // Determine where we wish to shoot the projectile to
   float realX = origin.x+winSize.width + (projectile->getContentSize().width/2);
   float ratio = offY / offX;
   float realY = (realX * ratio) + projectile->getPosition().y;
   CCPoint realDest = ccp(realX, realY);

   // Determine the length of how far we're shooting
   float offRealX = realX - projectile->getPosition().x;
   float offRealY = realY - projectile->getPosition().y;
   float length = sqrtf((offRealX * offRealX) + (offRealY*offRealY));
   float velocity = 480/1; // 480pixels/1sec
   float realMoveDuration = length/velocity;

   // Move projectile to actual endpoint
   projectile->runAction( CCSequence::create(
                            CCMoveTo::create(realMoveDuration, realDest),
                            CCCallFuncN::create(this,
                                                callfuncN_selector(HelloWorld::spriteMoveFinished)),
                            NULL) );

   // Add to projectiles array
   projectile->setTag(2);
   _projectiles->addObject(projectile);

   CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("pew-pew-lei.wav");
 }

 void HelloWorld::updateGame(float dt)
 {
   CCArray *projectilesToDelete = new CCArray;
   CCObject* it = NULL;
   CCObject* jt = NULL;

   // for (it = _projectiles->begin(); it != _projectiles->end(); it++)
   CCARRAY_FOREACH(_projectiles, it)
   {
     CCSprite *projectile = dynamic_cast<CCSprite*>(it);
     CCRect projectileRect = CCRectMake(
           projectile->getPosition().x - (projectile->getContentSize().width/2),
           projectile->getPosition().y - (projectile->getContentSize().height/2),
           projectile->getContentSize().width,
           projectile->getContentSize().height);

     CCArray* targetsToDelete =new CCArray;

     // for (jt = _targets->begin(); jt != _targets->end(); jt++)
     CCARRAY_FOREACH(_targets, jt)
     {
       CCSprite *target = dynamic_cast<CCSprite*>(jt);
       CCRect targetRect = CCRectMake(
             target->getPosition().x - (target->getContentSize().width/2),
             target->getPosition().y - (target->getContentSize().height/2),
             target->getContentSize().width,
             target->getContentSize().height);

       // if (CCRect::CCRectIntersectsRect(projectileRect, targetRect))
       if (projectileRect.intersectsRect(targetRect))
       {
         targetsToDelete->addObject(target);
       }
     }

     // for (jt = targetsToDelete->begin(); jt != targetsToDelete->end(); jt++)
     CCARRAY_FOREACH(targetsToDelete, jt)
     {
       CCSprite *target = dynamic_cast<CCSprite*>(jt);
       _targets->removeObject(target);
       this->removeChild(target, true);

       _projectilesDestroyed++;
       if (_projectilesDestroyed >= 5)
       {
         GameOverScene *gameOverScene = GameOverScene::create();
         gameOverScene->getLayer()->getLabel()->setString("You Win!");
         CCDirector::sharedDirector()->replaceScene(gameOverScene);
       }
     }

     if (targetsToDelete->count() > 0)
     {
       projectilesToDelete->addObject(projectile);
     }
     targetsToDelete->release();
   }

   // for (it = projectilesToDelete->begin(); it != projectilesToDelete->end(); it++)
   CCARRAY_FOREACH(projectilesToDelete, it)
   {
     CCSprite* projectile = dynamic_cast<CCSprite*>(it);
     _projectiles->removeObject(projectile);
     this->removeChild(projectile, true);
   }
   projectilesToDelete->release();
 }

 void HelloWorld::registerWithTouchDispatcher()
 {
   // CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this,0,true);
   CCDirector::sharedDirector()->getTouchDispatcher()->addStandardDelegate(this,0);
 }

GameOverScene.h

 /****************************************************************************
  Copyright (c) 2010-2011 cocos2d-x.org
  Copyright (c) 2010      Ray Wenderlich

  http://www.cocos2d-x.org

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ****************************************************************************/

 #ifndef _GAME_OVER_SCENE_H_
 #define _GAME_OVER_SCENE_H_

 #include "cocos2d.h"

 class GameOverLayer : public cocos2d::CCLayerColor
 {
 public:
   GameOverLayer():_label(NULL) {};
   virtual ~GameOverLayer();
   bool init();
   CREATE_FUNC(GameOverLayer);

   void gameOverDone();

   CC_SYNTHESIZE_READONLY(cocos2d::CCLabelTTF*, _label, Label);
 };

 class GameOverScene : public cocos2d::CCScene
 {
 public:
   GameOverScene():_layer(NULL) {};
   ~GameOverScene();
   bool init();
   CREATE_FUNC(GameOverScene);

   CC_SYNTHESIZE_READONLY(GameOverLayer*, _layer, Layer);
 };

 #endif // _GAME_OVER_SCENE_H_

GameOverScene.cpp

 /****************************************************************************
  Copyright (c) 2010-2011 cocos2d-x.org
  Copyright (c) 2010      Ray Wenderlich

  http://www.cocos2d-x.org

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ****************************************************************************/

 #include "GameOverScene.h"
 #include "HelloWorldScene.h"

 using namespace cocos2d;

 bool GameOverScene::init()
 {
   if( CCScene::init() )
   {
     this->_layer = GameOverLayer::create();
     this->_layer->retain();
     this->addChild(_layer);

     return true;
   }
   else
   {
     return false;
   }
 }

 GameOverScene::~GameOverScene()
 {
   if (_layer)
   {
     _layer->release();
     _layer = NULL;
   }
 }

 bool GameOverLayer::init()
 {
   if ( CCLayerColor::initWithColor( ccc4(255,255,255,255) ) )
   {
     CCSize winSize = CCDirector::sharedDirector()->getWinSize();
     this->_label = CCLabelTTF::create("","Artial", 32);
     _label->retain();
     _label->setColor( ccc3(0, 0, 0) );
     _label->setPosition( ccp(winSize.width/2, winSize.height/2) );
     this->addChild(_label);

     this->runAction( CCSequence::create(
                        CCDelayTime::create(3),
                        CCCallFunc::create(this,
                                           callfunc_selector(GameOverLayer::gameOverDone)),
                        NULL));

     return true;
   }
   else
   {
     return false;
   }
 }

 void GameOverLayer::gameOverDone()
 {
   CCDirector::sharedDirector()->replaceScene( HelloWorld::scene() );
 }

 GameOverLayer::~GameOverLayer()
 {
   if (_label)
   {
     _label->release();
     _label = NULL;
   }
 }

Voted #1 for:

  • Easiest to learn
  • Most time saving
  • Best support

Develop Cross-Platform Apps and Games 50% Faster!

  • Voted the best supported, most time-saving and easiest to learn cross-platform development tool
  • Based on the Qt framework, with native performance and appearance on all platforms including iOS and Android
  • Offers a variety of plugins to monetize, analyze and engage users
FREE!
create apps
create games
cross platform
native performance
3rd party services
game network
multiplayer
level editor
easiest to learn
biggest time saving
best support