Danh Mục Học Tập BK

Cocos2dX

[Tutorial Series] Use the Cocos2d-x-3.0 game engine. Write a Tile Map Game.



1.[Tutorial Series] Use the Cocos2d-x-3.0 game engine. Write a Tile Map Game.(part01)


The topic of the game design idea is always making people exciting. In my opinion, an apprentice

of the game design or developers independently should take a small classic case to analyze than 
read one hundred article on the pure theory. It will be more practical. In this series, I am going 
to show you how to use the Cocos2d-x-3.0 game engine to write a simple Tile Map Game, small Pokemon.
When I say Tile Map Game, probably you already know some of these classic games, or you just simply couldn’t recognize them. These pictures will help your memory. Both of them are the Tile Map Game.

 Battle City and Pokemon
 
       In this issue, I going to show you how to use the Tiled Map editor to build and upload the map.

Manage the different layers and the objects. Upload the character. And make it move. Upload your 
enemy and make them move, too. Also give you the way to defend yourself.


1.Make arrangement:
Game Engine: Cocos2d-x-3.0: (http://www.cocos2d-x.org/download)
Tile Map Editor-9.1: (http://sourceforge.net/projects/tiled/files/)
VS2012: (http://www.visualstudio.com/zh-cn/visual-studio-homepage-vs.aspx)
    First thing first, we need a game engine ---Cocos2d-x-3.0, written in C++ and OpenGL ES 1.1/2.0,
runs on iOS, Android, BlackBerry, Bada, Marmalade, Windows, Windows Phone, Linux and more.
    Then we still need a edit tool to do the coding and show me the result. (I chose the tool VS2012).
    The Tile Map Editor: the version is 9.1. It can create the map you want.
    The Tile Map picture materials: the pictures that show the effect and create the map.
    The Character/Enemy Sprite: the pictures (use PNG graphics) that represent the character and the
enemy.
    The Music: including the background music and the different effect sounds.


 And if everything is done, those music and photographs resources should be put into the file of Resources.

2.Use Tile Map tool to create a map

The cocos2d-x-3.0 supports the map (use TMX format) which creates by the Tile Map editor.

    After install the tool. Double-click the shortcut on the desktop. Click the File/New. Input the
parameters as shown. Then click on the ‘map’ menu and choose the ‘New Tileset’, you will see:


Use the ‘browser’ button to choose the sample pictures that we already have. And set the
‘Margin’ and ‘Spacing’ to 1. The ‘Margin’ will cut off the picture’s edge. And the ‘Spacing’
will cut off the pixels every pixel between two tiles you had set. For example:


Set Margin: 1 & Spacing : 1


Set margin: 200

    
Set the Spacing: 50


 Once we have done the drawing. Change the layer’s name. Rename it ‘Background’. It will be
convenient for us to manage those layers. (Don’t mind about other layers and objects in the picture I have shown you. We will do it later.) And click on File/Save button. Save it and name it ’01.tmx’ (or
whatever you like.)
Rename the layer



2.[Tutorial Series] Use the Cocos2d-x-3.0 game engine. Write a Tile Map Game.(part02)


1.Upload the map
    Like the Object-C language, The C++ has the head files and the implementation files (I learn Object-C first.). We now register the files which will be used in the head file.
Open the ‘HelloWorldScene.h’ , add some member variables:


class HelloWorld : public cocos2d::Layer
{
private:
 cocos2d::TMXTiledMap *_tileMap;
 cocos2d::TMXLayer *_background;
};



    Then open the ‘HelloWorldScene.cpp’, make some changes. (You can delete the old code in ’bool HelloWorld::init()’.)


bool HelloWorld::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    std::string file = "01.tmx";
auto str = String::createWithContentsOfFile (FileUtils::getInstance()->fullPathForFilename(file.c_str()).c_str());
    _tileMap = TMXTiledMap::createWithXML(str->getCString(),"");
    _background = _tileMap->layerNamed("Background");

    addChild(_tileMap, -1);

return true;
}

Here, we have called the class of the TMXTileLayer to upload our tile map. Now run the VS2012 debug function. We will get the map, or more preciously, part of the map. ( Because the map size which we have created is too big and the window size is not large enough to hold the whole scene. The default setting only shows the lower left corner of the tile map. ) So if there is no problem happens when VS is debugging, everything is OK. And then, we will create the sprite, our character, and let the window set the view on the character. (It will solve the problem above, or you can build your map on the lower left corner.)

Part of the map

4. Character and the View
 Right-click on the ‘Layer Section’ of the Tiled Map editor. Select the ‘Object layer’ to create a new one. Then choose the ‘rectangle’ button. And draw a rectangle on the map where you want your character shows up. Right-click on this rectangle and setting some properties for it. (Only change the name.)
Character position & Properties

   

Return to the VS. In the ‘HelloWorldScene.h’, we now do some coding:

class HelloWorld : public cocos2d::Layer
{
private:
 cocos2d::Sprite *_player;
};
   


In the ‘HelloWorldScene.cpp’:
bool HelloWorld::init()
{
 TMXObjectGroup *objects = _tileMap->getObjectGroup("Object-Player");
 CCASSERT(NULL != objects, "'Object-Player' object group not found");
 auto playerShowUpPoint = objects->getObject("PlayerShowUpPoint");
 CCASSERT(!playerShowUpPoint.empty(), "PlayerShowUpPoint object not found");

 int x = playerShowUpPoint["x"].asInt();
 int y = playerShowUpPoint["y"].asInt();

 _player = Sprite::create("029.png");
 _player->setPosition(x + _tileMap->getTileSize().width / 2, y + _tileMap->getTileSize().height / 2);
 _player->setScale(0.5);

 addChild(_player);
 setViewPointCenter(_player->getPosition());
}
 
We add the judgment statement to decide whether the object has been uploaded. By using the ‘getObjectGroup’ function, we get the object layer. And we get object’s position from the map and set the position coordinate to our sprite. Next, we add a new function, ‘setViewPointCenter’.
In the ‘HelloWorldScene.h’:
:
class HelloWorld : public cocos2d::Layer
{
public:
 void setViewPointCenter(cocos2d::Point position);
}
    In the ‘HelloWorldScene.cpp’:
void HelloWorld::setViewPointCenter(Point position) {
    auto winSize = Director::getInstance()->getWinSize();

    int x = MAX(position.x, winSize.width / 2);
    int y = MAX(position.y, winSize.height / 2);
    x = MIN(x, (_tileMap->getMapSize().width * this->_tileMap->getTileSize().width) - winSize.width / 2);
    y = MIN(y, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) - winSize.height / 2);
    auto actualPosition = Point(x, y);

    auto centerOfView = Point(winSize.width / 2, winSize.height / 2);
    auto viewPoint = centerOfView - actualPosition;
    this->setPosition(viewPoint);
}

View correct & Character show up

Now, the view has been corrected, and the character has been showed up. But the character can’t move. And the screen also freezes.
  1. The Movement:
bool HelloWorld::init(){
......
 auto listener = EventListenerTouchOneByOne::create();
 listener->onTouchBegan = [&](Touch *touch, Event *unused_event)->bool {return true;};
 listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
 this->_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
......
}
And:
void HelloWorld::onTouchEnded(Touch *touch, Event *unused_event)
{
 auto actionTo1 = RotateTo::create(0, 0, 180);
 auto actionTo2 = RotateTo::create(0, 0, 0);
 auto touchLocation = touch->getLocation();

 touchLocation = this->convertToNodeSpace(touchLocation);

 auto playerPos = _player->getPosition();
 auto diff = touchLocation - playerPos;
 if (abs(diff.x) > abs(diff.y)) {
  if (diff.x > 0) {
   playerPos.x += _tileMap->getTileSize().width / 2;
   _player->runAction(actionTo2);
  }
  else {
   playerPos.x -= _tileMap->getTileSize().width / 2;
   _player->runAction(actionTo1);
  }
 }
 else {
  if (diff.y > 0) {
     playerPos.y += _tileMap->getTileSize().height / 2;
  }
  else {
   playerPos.y -= _tileMap->getTileSize().height / 2;
  }
 }

 if (playerPos.x <= (_tileMap->getMapSize().width * _tileMap->getMapSize().width) &&
  playerPos.y <= (_tileMap->getMapSize().height * _tileMap->getMapSize().height) &&
  playerPos.y >= 0 &&
  playerPos.x >= 0)
 {
  this->setPlayerPosition(playerPos);

 }

 this->setViewPointCenter(_player->getPosition());
}
void HelloWorld::setPlayerPosition(Point position)
{
    _player->setPosition(position);
}
And don’t forget the ‘HelloWorldScene.h’:
class HelloWorld : public cocos2d::Layer
{
public:
void onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *unused_event);
void setViewPointCenter(cocos2d::Point position);
void setPlayerPosition(cocos2d::Point position);
}

Sprite & View move


To be continue...


3.[Tutorial Series] Use the Cocos2d-x-3.0 game engine. Write a Tile Map Game.(part03)


6.Collision detected
    Since our sprite can move everywhere, you must be very exciting. There is also something strange. Probably you already notice that. The sprite we had made can through everywhere. So we will make it more realistic. 
Through the house

    Go back to the Tiled Map editor. Press the ‘New Tileset’ menu and open the new material we are going to use. Right-click the red one, and set the new property for it. (Pay attention to the upper and lower case. It may do some trickery.)
Now create a new layer, and name it ‘blockage01’. (Or whatever you like, just remember the name.) Paint those red rectangle on the map to mark the area where our sprite can not move through.

    Now, you probably already know what we are going to do! When we are coding we can make the program detect this red rectangle tile and know its property—the ‘Blockage’ is true. (Be wary about whether it’s ‘true’ or ‘True’, it will matter.) Make these area can be through or not.
    Return back to the VC. This time I sure you know where to put these exactly.
    In the ‘HelloWorldScene.h’:
cocos2d::TMXLayer *_blockage;
    In the ‘HelloWorldScene.cpp’:
_blockage = _tileMap->layerNamed("Blockage01");
    But this time we will set these red tiles to be invisible.
_blockage->setVisible(false);
    Add a new method to calculate the tiles’ coordinate 
class HelloWorld : public cocos2d::Layer
{
public:
......
cocos2d::Point tileCoordForPosition(cocos2d::Point position);
......
}
    In the ‘HelloWorldScene.cpp’:
Point HelloWorld::tileCoordForPosition(Point position)
{
 int x = position.x / _tileMap->getTileSize().width;
 int y = ((_tileMap->getMapSize().height * _tileMap->getTileSize().height) - position.y) / _tileMap->getTileSize().height;
 return Point(x, y);
}
    The original coordinate is on the upper-left corner. So you probably will be confused about the mathematical formula.


The original coordinate
    We replace the code in the ‘void HelloWorldPlayerPosition(Point position)’
void HelloWorld::setPlayerPosition(Point position)
{
    Point tileCoord = this->tileCoordForPosition(position);
    int tileGid = _blockage->getTileGIDAt(tileCoord);
    if (tileGid) {
        auto properties = _tileMap->getPropertiesForGID(tileGid).asValueMap();
        if (!properties.empty()) {
            auto collision = properties["Blockage”].asString();
            if ("True" == collision) {
                return;
            }
        }
    }
    _player->setPosition(position);
}
    We convert our sprite’s coordinate into the tiles’ coordinate. Then use the method ‘getTileGIDAt’ to get the specified position’s GID. Then use the GID to check the tiles’ property and decide whether it can be go through. (We will add more function in the ‘void HelloWorld::setPlayerPosition(Point position)’ later.)
    Try your game. This time the sprite won’t go through the area that you already blockade. And the red tiles won’t be seen since we already set them invisible.

Blockage
    Open up a new tile map material and create a new layer in the Tile Map editor. (I name the new layer ‘Foreground01’) And do some farming…on the ‘Foreground01’ layer. Use the green tiles we already met and paint them verdant on the ‘Blockage01’ layer (Yes, the ‘Blockage01’, not the ‘Foreground01’ we created before.)
    
       Create a new layer
Set the new property for the green tiles like we did to the red ones.
New property
   In the ‘HelloWorldScene.h’:
cocos2d::TMXLayer *_foreground;
    In the ‘HelloWorldScene.cpp’:
_foreground = _tileMap->getLayer("Foreground01");
    Add these code to the ‘void HelloWorld::setPlayerPosition(Point position)’
auto collectable = properties["Collectable"].asString();
if ("True" == collectable) {
    _blockage->removeTileAt(tileCoord);
_foreground->removeTileAt(tileCoord);
_player->setPosition(position);  
}
Our sprite is truly an herbivore.

Herbivore

4.[Tutorial Series] Use the Cocos2d-x-3.0 game engine. Write a Tile Map Game.(part04)
7.Score indicator:
    Type these codes before ‘HelloWorld’ class declaration in the ‘HelloWorldScene.h’, we are going to make a score indicator.
class HelloWorldHud : public cocos2d::Layer
{
public:
    void numCollectedChanged(int numCollected);
    virtual bool init();
    CREATE_FUNC(HelloWorldHud);

    cocos2d::LabelTTF *label;
};
    Type these inside the ‘HelloWorld’ class declaration.
......
Private:
......
int _numCollected;
static HelloWorldHud *_hud;
......
    In the ‘HelloWorldScene.cpp’,
    Type these at the top of file:
HelloWorldHud *HelloWorld::_hud = NULL;
    Add to the HelloWorld::createScene() method, right before the return
......
auto hud = HelloWorldHud::create();
_hud = hud;

scene->addChild(hud);
......
Add inside setPlayerPosition, in the case where a tile is collectable
......
this->_numCollected++;
this->_hud->numCollectedChanged(_numCollected);
......
    Now add those method:
bool HelloWorldHud::init()
{
    if (!Layer::init())
    {
        return false;
    }
    auto visibleSize = Director::getInstance()->getVisibleSize();
    label = LabelTTF::create("0", "fonts/Marker Felt.ttf", 18.0f, Size(50, 20), TextHAlignment::RIGHT);
    label->setColor(Color3B(0, 0, 0));
    int margin = 10;
    label->setPosition(visibleSize.width - (label->getDimensions().width / 2) - margin,
        label->getDimensions().height / 2 + margin);
    this->addChild(label);

    return true;
}

void HelloWorldHud::numCollectedChanged(int numCollected)
{
    char showStr[20];
    sprintf(showStr, "%d", numCollected);
    label->setString(showStr);
}
    It is easy to understand that we make a ‘LabelTTF’ to create the number we want. (We set the value ‘0’, the font, the size, the color, and the position.)
When the vegetable has been eaten by our sprite, the number will be plus 1.

8.Music & Sounds:
There is no doubt that it will be very boring if there is no music at all. So we are going to make some funny.
    Add these two lines in the ‘HelloWorldScene. cpp’ (When something involves with the music, we should have the ‘SimpleAudioEngine.h’, and the CocosDenshion is the music function in the Cocos2d engine.)
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;

bool HelloWorld::init()
{
......
SimpleAudioEngine::getInstance()->preloadEffect("error.mp3");
SimpleAudioEngine::getInstance()->preloadEffect("item.mp3");
SimpleAudioEngine::getInstance()->preloadEffect("step.mp3");

// I make this effect ’wade.mp3’ for fun. You can try to do it yourself, or simply delete them.
SimpleAudioEngine::getInstance()->preloadEffect("wade.mp3");
SimpleAudioEngine::getInstance()->playBackgroundMusic("background.mp3");
SimpleAudioEngine::getInstance()->setBackgroundMusicVolume(0.1);
......
}

(Notice that it is ‘playBackgroundMusic’, not ‘preloadBackgoundMusic’.)

// In case for collidable tile
SimpleAudioEngine::getInstance()->playEffect("error.mp3");

// In case of collectable tile
SimpleAudioEngine::getInstance()->playEffect("item.mp3");

// Right before setting player position
SimpleAudioEngine::getInstance()->playEffect("step.mp3");

// I make this effect for fun. You can try to do it yourself, or simply delete them.
SimpleAudioEngine::getInstance()->playEffect("wade.mp3");
Oh~ It’s hard to show you this result by using picture.
9.Add Enemy:
    Maybe you still remember how to create our sprite, the character. But we will make it some different to build multiple enemies. We still do it on the ‘Object-Player’ layer. Set the values as the picture shown (The name, the property name, and the property value. You can have different value, if you are going to have different type of enemy.):

// Type these code after where we create the player
bool HelloWorld::init()
{
......
 for (auto& eSpawnPoint: objects->getObjects()){
  ValueMap& dict = eSpawnPoint.asValueMap();
  if(dict["Enemy"].asInt() == 1){
   x = dict["x"].asInt();
   y = dict["y"].asInt();
   this->addEnemyAtPos(Point(x, y));
  }
 }......
}
    In the ‘HelloWorldScene.cpp’
void HelloWorld::addEnemyAtPos(Point pos)
{
 auto enemy = Sprite::create("030.png");
 enemy->setPosition(pos);
 enemy->setScale(0.5);
 this->animateEnemy(enemy);
 this->addChild(enemy);

 _enemies.pushBack(enemy);
}
10.Make them move:
    The enemy will chase down our player. Because the player will move at our will, we will make those enemies chase the player automatically. I make them move 10 pixels every 0.3 second. 
void HelloWorld::enemyMoveFinished(Object *pSender)
{
    Sprite *enemy = (Sprite *)pSender;
    this->animateEnemy(enemy);
}
void HelloWorld::animateEnemy(Sprite *enemy)
{
    float actualDuration = 0.3f;
    auto position = (_player->getPosition() - enemy->getPosition()).normalize()*10;
    auto actionMove = MoveBy::create(actualDuration, position);
    auto actionMoveDone = CallFuncN::create(CC_CALLBACK_1(HelloWorld::enemyMoveFinished, this));
    enemy->runAction(Sequence::create(actionMove, actionMoveDone, NULL));
}
    The function ‘animateEnemy’ create two ‘action’. The first one makes enemies move 10 pixels every 0.3 second. You can change the value if you like. The other one will call the function ’enemyMoveFinished’. I use the ‘Sequence::create’ to bind them. When the first function finished, the second one be call and start working.  
But we want the enemy want the enemies can turn around and face the player.
    Add these to ’void HelloWorld::animateEnemy(Sprite *enemy)’
void HelloWorld::animateEnemy(Sprite *enemy)
{
 auto actionTo1 = RotateTo::create(0, 0, 180);
 auto actionTo2 = RotateTo::create(0, 0, 0);
 auto diff = ccpSub(_player->getPosition(), enemy->getPosition());

    if (diff.x < 0) {
  enemy->runAction(actionTo2);
    }
 if (diff.x > 0) {
  enemy->runAction(actionTo1);
 }
......
float actualDuration = 0.3f;
......
}
Let us see the result:
Enemy movement 1 Enemy movement2
To be continue...

5.[Tutorial Series] Use the Cocos2d-x-3.0 game engine. Write a Tile Map Game.(part05)


11.The Shout
    The shout, probably you can call it the ability to kill. Like the Elder Scroll V, the dragonborn. We now add a new button to switch the mode between movement and shouting.
    Add those code to delete the shout that have already out of the tile map.
In the ‘HelloWorldScene.h’:
void projectileMoveFinished(cocos2d::Object *pSender);
In the ‘HelloWorldScene.cpp’:
void HelloWorld::projectileMoveFinished(Object *pSender)
{
    Sprite *sprite = (Sprite *)pSender;
    this->removeChild(sprite);
}
void HelloWorld::onTouchEnded(Touch *touch, Event *unused_event)
{
if (_mode == 0) {
......
// the original code we have typed 
......
} else {
  auto touchLocation = touch->getLocation();
  touchLocation = this->convertToNodeSpace(touchLocation);
  auto projectile = Sprite::create("bullet.png");
  projectile->setPosition(_player->getPosition());
  projectile->setScale(0.25);
  this->addChild(projectile);

  int realX;

  auto diff = touchLocation - _player->getPosition();
  if (diff.x > 0)
  {
   realX = (_tileMap->getMapSize().width * _tileMap->getTileSize().width) + 
    (projectile->getContentSize().width / 2);
  }
  else {
   realX = -(_tileMap->getMapSize().width * _tileMap->getTileSize().width) -
    (projectile->getContentSize().width / 2);
  }
  float ratio = (float)diff.y / (float)diff.x;
  int realY = ((realX - projectile->getPosition().x) * ratio) +projectile->getPosition().y;
  auto realDest = Point(realX, realY);

  int offRealX = realX - projectile->getPosition().x;
  int offRealY = realY - projectile->getPosition().y;
  float length = sqrtf((offRealX*offRealX) + (offRealY*offRealY));
  float velocity = 480 / 1; // 480pixels/1sec
  float realMoveDuration = length / velocity;

  auto actionMoveDone =CallFuncN::create(CC_CALLBACK_1(HelloWorld::projectileMoveFinished, this));
    projectile->runAction(Sequence::create(MoveTo::create(realMoveDuration,realDest), actionMoveDone, NULL))

  _projectiles.pushBack(projectile);
 }
}
    We first get the position where the finger touched. And we create the shout icon sprite. Then, we determine where it should go, how much time it will take.
    Now we got what we want: the switch button and the shout. But we can see the problem that even the shout and the enemy meet each other they are still there.
The button & Shout


12.Collision detect

In the ‘HelloWorldScene.h’:

cocos2d::Vector _enemies;
cocos2d::Vector _projectiles;
In the ‘HelloWorldScene.cpp’:
Add those codes at the end of the launch projectiles section of ‘onTouchEnded’:
_projectiles.pushBack(projectile);
Add those codes at the end of the ‘projectileMoveFinished’:
_projectiles.eraseObject(sprite);
Add those codes at the end of the ‘addEnemyAtPos’:
_enemies.pushBack(enemy);
void HelloWorld::testCollisions(float dt)
{
    Vector projectilesToDelete;

    for (Sprite *projectile : _projectiles) {
        auto projectileRect = Rect(
            projectile->getPositionX() - projectile->getContentSize().width / 2,
            projectile->getPositionY() - projectile->getContentSize().height / 2,
            projectile->getContentSize().width,
            projectile->getContentSize().height);

        Vector targetsToDelete;

        for (Sprite *target : _enemies) {
            auto targetRect = Rect(
                target->getPositionX() - target->getContentSize().width / 2,
                target->getPositionY() - target->getContentSize().height / 2,
                target->getContentSize().width,
                target->getContentSize().height);

            if (projectileRect.intersectsRect(targetRect)) {
                targetsToDelete.pushBack(target);
            }
        }


        for (Sprite *target : targetsToDelete) {
            _enemies.eraseObject(target);
            this->removeChild(target);
        }

        if (targetsToDelete.size() > 0) {
            // add the projectile to the list of ones to remove
            projectilesToDelete.pushBack(projectile);
        }
        targetsToDelete.clear();
    }


    for (Sprite *projectile : projectilesToDelete) {
        _projectiles.eraseObject(projectile);
        this->removeChild(projectile);
    }
    projectilesToDelete.clear();
}
Finally, we type these code in the
bool HelloWorld::init()
{
......
this->schedule(schedule_selector(HelloWorld::testCollisions));
return true;
}
    We first get the picture size of the shout and the size of the enemy, then we check whether those pictures touch each other. If they did, we add those ones into the arrays and delete the objects in these arrays.
    Now it should working like this:
Only one left


About Me

Mọi thắc mắc vui lòng liên hệ Nguyễn Hoàng Thiên Phước. Số điện thoại:0122-871-3493