Cocos2DX Sample Application
This wiki guides you through the code for Ninja Shoot Cocos2dx demo game.
The tutorial itself is quite simple. The user first needs to click on the “Start Game”. Once started, the scene changes to the game play scene. Since its a 2 player demo, another user will also do the same steps and voila! they can shoot balls at each other by tapping on the screen and see them move in real-time.
Now lets understand how the game code works by examining the key code snippets. Entire source code is available and can be downloaded or viewed from our git repo.
First we specify the AppWarp constants that will be used in HelloWorldScene class,
#define APPWARP_APP_KEY "Your App Key" #define APPWARP_SECRET_KEY "Your Secret Key" #define ROOM_ID "Your Room Id"
You get your values by registering and creating an AppWarp type of application from AppHq (Shephertz developer dashboard). This page contains a step-by-step guide on how to register, creating the app and a game room. Once you’ve got these values, replace as indicated.
Next we simply add the required UI elements (local player, remote player etc.). For the real-time communication to happen, you need to use AppWarp source code. The latest version can be downloaded from our Git Repo here. Add this source code to your project. Next you need to initialize the AppWarp’s Client singleton and set up your listeners through which you will get events.
AppWarp::Client *warpClientRef; AppWarp::Client::initialize(APPWARP_APP_KEY,APPWARP_SECRET_KEY); warpClientRef = AppWarp::Client::getInstance(); warpClientRef->setConnectionRequestListener(this); warpClientRef->setNotificationListener(this); warpClientRef->setRoomRequestListener(this); warpClientRef->setZoneRequestListener(this);
The listener callbacks required for this sample are defined in HelloWorldScene.cpp file, so HelloWorldScene itself is added as a listener in the above code. ConnectionRequestListener defines the callbacks for connection establishment and tear-down. RoomRequestListener defines the callbacks for the game room’s join and subscribe operations. NotificationListener defines the callbacks for remote notifications such a player sending a chat or updating a rooms properties etc.
class HelloWorld : public cocos2d::CCLayerColor, public AppWarp::ConnectionRequestListener,public AppWarp::RoomRequestListener,public AppWarp::NotificationListener,public AppWarp::ZoneRequestListener
Now we are ready to connect to AppWarp cloud and start our game. Now each user that is concurrently connected to your game must have a unique username. In this demo we will simply use a random user name for this and connect. You can use other solutions such as a facebook id, email address etc. to guarantee uniqueness depending on your game.
std::string genRandom() { std::string charStr; srand (time(NULL)); for (int i = 0; i < 10; ++i) { charStr += (char)(65+(rand() % (26))); } return charStr; } userName = genRandom(); warpClientRef->connect(userName);
The game starts once the user has successfully connected to AppWarp and joined the game room.The listener callbacks as a result of connect, joinRoom and subscribeRoom API calls are defined as follows:
void HelloWorld::onConnectDone(int res) { if (res==0) { printf("\nonConnectDone .. SUCCESS\n"); AppWarp::Client *warpClientRef; warpClientRef = AppWarp::Client::getInstance(); warpClientRef->joinRoom(ROOM_ID); } else printf("\nonConnectDone .. FAILED\n"); } void HelloWorld::onJoinRoomDone(AppWarp::room revent) { if (revent.result==0) { printf("\nonJoinRoomDone .. SUCCESS\n"); AppWarp::Client *warpClientRef; warpClientRef = AppWarp::Client::getInstance(); warpClientRef->subscribeRoom(ROOM_ID); startGame(); removeStartGameLayer(); } else printf("\nonJoinRoomDone .. FAILED\n"); } void HelloWorld::onSubscribeRoomDone(AppWarp::room revent) { if (revent.result==0) { printf("\nonSubscribeRoomDone .. SUCCESS\n"); } else printf("\nonSubscribeRoomDone .. FAILED\n"); }
The joinRoom is called in the Listener onConnectDone and the game starts when onJoinRoomDone is called with a success response. Now the user can shoot in the opponents direction simply by a touch in the direction he want to shoot which is handled as follows:
void HelloWorld::ccTouchesEnded(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent) { CCSetIterator it = pTouches->begin(); CCTouch* touch = (CCTouch*)(*it); CCPoint location = touch->getLocation(); // Set up initial location of projectile CCSize winSize = CCDirector::sharedDirector()->getWinSize(); CCSprite *projectile = CCSprite::create("Bullet-red.png"); projectile->setPosition(ccp(player->getPosition().x+player->getContentSize().width/2, player->getPosition().y)); CCPoint projectilePos = projectile->getPosition(); // Determine offset of location to projectile int offX = location.x - projectilePos.x; int offY = location.y - projectilePos.y; // Bail out if we are shooting down or backwards if (offX <= 0) return; // Ok to add now - we've double checked position addChild(projectile,10); // Determine where we wish to shoot the projectile to int realX = winSize.width + (projectile->getContentSize().width/2); float ratio = (float) offY / (float) offX; int realY = (realX * ratio) + projectilePos.y; CCPoint realDest = ccp(realX, realY); // Determine the length of how far we're shooting int offRealX = realX - projectilePos.x; int offRealY = realY - projectilePos.y; float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY)); float velocity = 480/1; // 480pixels/1sec float realMoveDuration = length/velocity; sendData(winSize.width-realDest.x, realDest.y, realMoveDuration); // Move projectile to actual endpoint CCActionInterval* move = CCMoveTo::create(realMoveDuration, realDest); CCCallFuncN* moveFinished = CCCallFuncN::create(this, callfuncN_selector(HelloWorld::spriteMoveFinished)); CCSequence* seq = CCSequence::create(move,moveFinished, NULL); projectile->runAction(seq); // Add to projectiles array projectile->setTag(2); _projectiles->addObject(projectile); } void HelloWorld::sendData(float x, float y, float duration) { AppWarp::Client *warpClientRef; warpClientRef = AppWarp::Client::getInstance(); std::stringstream str; str <sendChat(str.str()); }
Here We are sending x and y values of the touch point according to the remote player’s frame of reference as well as the duration in which the projectile need to go beyond the screen. The data is a string where characters “x” and “d” are used as separators. This message is received by all players in the room and they can then update their UI in turn. When we receive remote messages we need to write some code to update the remote player’s position or projectile depending on the message received. Here is the code snippet which handles it in the HelloWorldScene.cpp file
void HelloWorld::onChatReceived(AppWarp::chat chatevent) { if(chatevent.sender != userName) { std::size_t loc = chatevent.chat.find('x'); std::string str1 = chatevent.chat.substr(0,loc); std::string str2 = chatevent.chat.substr(loc+1); loc = chatevent.chat.find('d'); std::string str3 = chatevent.chat.substr(loc+1); float x = (float)std::atof (str1.c_str()); float y = (float)std::atof(str2.c_str()); float dest = (float)std::atof(str3.c_str()); updateEnemyStatus(ccp(x,y), dest); } } void HelloWorld::updateEnemyStatus(CCPoint destination,float actualDuration) { enemy->setOpacity(255); isEnemyAdded = true; CCSprite *target = CCSprite::create("Bullet-blue.png"); target->setPosition(ccp(enemy->getPosition().x-enemy->getContentSize().width/2, enemy->getPosition().y)); addChild(target,10); // Move projectile to actual endpoint CCActionInterval* move = CCMoveTo::create(actualDuration, destination); CCCallFuncN* moveFinished = CCCallFuncN::create(this, callfuncN_selector(HelloWorld::spriteMoveFinished)); CCSequence* seq = CCSequence::create(move,moveFinished, NULL); target->runAction(seq); // Add to targets array target->setTag(3); _targets->addObject(target); }