【英文论坛获奖教程】来抓我啊!(c++)

来抓我啊!(C++)

原作者:http://discuss.cocos2d-x.org/t/cocos3-0-tutorial-game-catchme/14258#Torelli95
英文论坛原帖:http://discuss.cocos2d-x.org/t/cocos3-0-tutorial-game-catchme/14258

在本片教程中我们要制作一个简单的游戏,来抓我啊!Catch Me.

这游戏使用点击的方式来捕获一个在屏幕上移动的简单的动态标签

这款游戏将会在Linux上编译,在此仅说明一下。
这款游戏包括:
-自定义排版
-动作
-时间
-动画
-使用用户默认(保存玩家分数)
开始编码吧~

第一章

设置项目
创建一个新的cocos项目:
在终端里输入:
cocos new -l cpp -d /path/to/project CatchMe
替换/path/to/project为你要保存项目的位置
在你的编译环境中打开项目的时候,必须有4个类
HelloWorld.cpp
HelloWorld.h
AppDelegate.cpp
AppDelegate.h
添加一个空的类名为CatchMe.cpp还有对应的CatchMe.h。这个类会包含这个游戏最重要的元素,标签和处理程序。(监听器,动画等)
现在我们的HelloWorld看起来像默认的cocos HelloWorld:

建立我们的
CatchMe
标签
第一步:到网址http://www.dafont.com/给我们的标签下载一个新的字体,我下载了字体:NextGames.tff;
将文件保存到/path/to/CatchMe/Resources/fonts里。
你可以创建自己的tff字体也可以在其他地方下载。

第二步,我们要在CatchMe.h中声明下一个变量和程序:

///include cocos to the new class

#include "cocos2d.h"


///Extends LabelTTF because CatchMe will have _eventDispatcher for this must be a Node(Father of LabelTTF)

class CatchMe : cocos2d::LabelTTF
{
public:

   ///Constructor and Destructor of CatchMe class

    CatchMe();
    ~CatchMe();

    ///Gives LabelCatchMe to others

    cocos2d::LabelTTF* getLabel();

protected:
    cocos2d::LabelTTF* LabelCatchMe;

};

```

在这之后,在CatchMe.cpp中声明constructor, destructor 和 getLabel方法。
CatchMe::CatchMe()
{

    LabelCatchMe = LabelTTF::create("CatchMe", "fonts/NextGames.ttf", 72);
}

CatchMe::~CatchMe()
{
}

LabelTTF *CatchMe::getLabel()
{
    return LabelCatchMe;
}

```

现在我们在HelloWorld中把HelloWold标签替换成新标签;为此我们首先需要在HelloWorld.cpp中的类CatchMe里创建一个新的实例。
然后在HelloWorld.h的类HelloWorld里声明CatchMe实例的属性。
protected:
CatchMe* Game; 
cocos2d::LabelTTF* LabelCatchMe;
可能你已经注意到了其他的生命的变量,LabelCatchMe,这将被用来在HelloWorld里保持向后复制我们的标签。
并替换:
 // add a label shows "Hello World"
    // create and initialize a label

    auto label = LabelTTF::create("Hello World", "Arial", 24);

    // position the label on the center of the screen
    label->setPosition(Point(origin.x + visibleSize.width/2,
                            origin.y + visibleSize.height - label->getContentSize().height));

    // add the label as a child to this layer
    this->addChild(label, 1);

to:
 // add a label shows "CatchMe"

    Game = new CatchMe();

    LabelCatchMe = Game->getLabel();


    // position the label on the center of the screen
    LabelCatchMe->setPosition(Point(origin.x + visibleSize.width/2,
                            origin.y + visibleSize.height - LabelCatchMe->getContentSize().height));

    // add the label as a child to this layer
    this->addChild(LabelCatchMe, 1);


```

别忘了在HelloWorld.h中添加 #include "CatchMe.h"
如果你现在运行项目,结果应该是这样的:
  


第二章

增加一些颜色
这些标签效果看起来不错,但如果换换颜色效果会更好。为此我们需要使用Glubyte类的变量。
在CatchMe.h中声明下一个变量:
GLubyte r,g,b;
r,g,b对应着RGB颜色模型,这些变量的值范围在0~255之间。
现在我们到CatchMe.cpp 里的tchMe结构体中添加下面几行代码:
CatchMe::CatchMe()
{
    ///Initializes the Label CatchMe
    LabelCatchMe = LabelTTF::create("CatchMe", "fonts/NextGames.ttf", 72);

    srand(time(nullptr));
    r = rand()%255;
    g = rand()%255;
    b = rand()%255;
    LabelCatchMe->setColor( Color3B(r,g,b) );
}

```

因为这个标签会以随机颜色的方式出现,但我们现在就要改变颜色。下一步我们就要处理这个问题:
到CatchMe.h里声明一下方法:
protected:
    ///return a small value to change the colors gradually
    GLubyte newColor();
public:
    ///Change color label
    void changeColor();

```

在CatchMe.cpp里:
GLubyte CatchMe::newColor()
{
    GLubyte nuevoColor = rand() % 5;
    if(rand() % 3 == 0)
    nuevoColor = -nuevoColor;
    return nuevoColor;
}


void CatchMe::changeColor()
{
    r += newColor();
    g += newColor();
    b += newColor();
    LabelCatchMe->setColor( Color3B(r,g,b) ); 
}

```

下一步就是更新游戏场景并引用我们在上面声明的方法。
在HelloWorld.h中
protected: void update(float df);
在HelloWorld.cpp中

void HelloWorld::update(
float df)
{
 Game->changeColor();
 LabelCatchMe = Game->getLabel();
}
在HelloWorld中引用:
schedule (schedule_selector(HelloWorld::
update));
注意到方法update需要一个schedule.
如果你运行了程序的话会得到如下场景:
  
标签的颜色不断地变化。


第三章

事件
在这个区域里当玩家碰触的时候标签能够做出反应。
为此我们将在CatchMe.h上声明下一个方法:

protected:
    ///Manage Events and Actions
    
void 
setEventHandlers();

在CatchMe.cpp里:
void CatchMe::setEventHandlers()
{
        //Create a "one by one" touch event listener (processes one touch at a time)
        auto listener = EventListenerTouchOneByOne::create();
        // When "swallow touches" is true, then returning 'true' from the onTouchBegan method will "swallow" the touch event, preventing other listeners from using it.
        listener->setSwallowTouches(true);
        // Example of using a lambda expression to implement onTouchBegan event callback function
        listener->onTouchBegan = &](Touch* touch, Event* event){
            // event->getCurrentTarget() returns the *listener's* sceneGraphPriority node.
            auto target = static_cast(event->getCurrentTarget());

            //Get the position of the current point relative to the button
            Point locationInNode = target->convertToNodeSpace(touch->getLocation());
            Size s = target->getContentSize();
            Rect rect = Rect(0, 0, s.width, s.height);
            //Check the click area
            if (rect.containsPoint(locationInNode))
            {
                ///The action that we want to run if the Label get touched
                auto hide = Hide::create();
                target->runAction(hide);
                return true;
            }
            return false;
        };
        //Add listener
            _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, LabelCatchMe);
}

```

我们也需要在CatchMe结构体中引用这个方法:
CatchMe::CatchMe()
{
    ///Initializes the Label CatchMe
    LabelCatchMe = LabelTTF::create("CatchMe", "fonts/NextGames.ttf", 72);
    srand(time(nullptr));
    r = rand()%255;
    g = rand()%255;
    b = rand()%255;
    LabelCatchMe->setColor( Color3B(r,g,b) );

    setEventHandlers();

}

```

现在如果你运行项目并点击标签,你就会看到神器的事情发生了!

移动和动画
现在我们要给游戏增加点难度,难道不是这样吗?
为了能够移动标签我们需要在屏幕上产生一个随机点,考虑到标签的大小,所以我们需要以下的方法:
In CatchMe.h:

在CatchMe.h中:

protected:
///Generate a random point on the screen, taking 
into account the size of the label

    cocos2d::Point 
generatedRandomPoint(cocos2d::Node* node);

在CatchMe.cpp中:
Point CatchMe::generatedRandomPoint(Node* node)
{
   srand(time(nullptr));
   int randx;
   int randy;
   int contentSizeHeight = node->getContentSize().height;
   int contentSizeWidht = node->getContentSize().width;
   int visibleSizeHeight = Director::getInstance()->getVisibleSize().height - (contentSizeHeight * 0.5);
   int visibleSizeWidth = Director::getInstance()->getVisibleSize().width - (contentSizeWidht * 0.5);
   randx = rand() % visibleSizeWidth;
   randy = rand() % visibleSizeHeight;
      while((randx > (contentSizeWidht * 0.5) &&  randx < visibleSizeWidth && randy > (contentSizeHeight *0.5) && randy < visibleSizeHeight) == false ){
          randx = rand() % visibleSizeWidth;
          randy = rand() % visibleSizeHeight;}
   return Point(randx,randy);

}

```

现在我们用以下的方法添加移动和动画。
在CatchMe.h中:
protected:
void animateGameTitle();
在CatchMe.cpp中:
void CatchMe::animateGameTitle()
 {
    ///get the label size
     int nodeWidth = LabelCatchMe->getContentSize().width;
     int  nodeHeight = LabelCatchMe->getContentSize().height;
     ///Animatios to use
     auto moveTo = MoveTo::create(0.2f + rand() % 40 / 150.0f, generatedRandomPoint(LabelCatchMe));
     auto rotateTo = RotateTo::create(0.5f + rand() % 40 / 150.0f, rand() % 360);
     auto scaleTo = ScaleTo::create(0.2f + rand() % 40 / 150.0f,rand() % nodeWidth * 0.5/60,rand()%nodeHeight*0.5 /60);
    ///The animations will repeated many times,calls himself
     auto callFunc = CallFunc::create( this, callfunc_selector(CatchMe::animateGameTitle) );
     auto sequence = Sequence::create(moveTo,callFunc, nullptr);
     ///this Actions will run in parallel to MoveTo sequence.
     LabelCatchMe->runAction(sequence);
     LabelCatchMe->runAction(rotateTo);
     LabelCatchMe->runAction(scaleTo);
 }

```

Cocos2d-x有着很多其他可用的动画,这些能够在文档中找到。
最后在结构体重animateGameTitle引用:
CatchMe::CatchMe()
{
    ///Initializes the Label CatchMe
    LabelCatchMe = LabelTTF::create("CatchMe", "fonts/NextGames.ttf", 72);

    srand(time(nullptr));
    r = rand()%255;
    g = rand()%255;
    b = rand()%255;
    LabelCatchMe->setColor( Color3B(r,g,b) );
    setEventHandlers();
    animateGameTitle();

}

```

如果你运行程序的话,应该会得到如下结果:
  
伴随着标签在全屏幕的移动,旋转和尺寸更改。
但我们现在还有一个问题,如果点击了标签,他就不再出现了。
为了修复这个问题,我们到setEventHandlers里:
///The action that we want to run if the Label get touched
                auto hide = Hide::create();
                target->runAction(hide);
                return true;
we raplace for:
    ///The action that we want to run if the Label get touched
                auto hide = Hide::create();
                auto show = Show::create();
                auto delay = DelayTime::create(0.75f);
   ///make a sequence that hide and show a little delay to the label
   ///keep moving to another part of the screen without the player sees
                 auto sequenceTouch = Sequence::create(hide,delay,show, nullptr);
                target->runAction(sequenceTouch);
                return true;

```


第四章

分数!
一款游戏没有得分?如果不能超过最高分以及知道自己的得分那这还有玩的意义么?
我们需要声明3种新的变量,由于时间的关系我将它添加进类HelloWorld中,这可能不那么美观但它至少能运作。
HelloWorld.h:
在HelloWorld.h中:
public
 cocos2d::LabelTTF* currentScore;
 cocos2d::LabelTTF* bestScore;
接下来在CatchMe.h中:
public: int currentPoints;
HelloWorld.cpp: In HelloWorld::init;
在HelloWorld.cpp的HelloWorld::init中
currentScore = LabelTTF::create("0000","fonts/NextGames.ttf",25);
    currentScore->setPosition(Point(origin.x + visibleSize.width-currentScore->getContentSize().width,
                                    origin.y + visibleSize.height-currentScore->getContentSize().height));
    addChild(currentScore,-1);


```

然后在CatchMe.cpp中:
///The action that we want to run if the Label get touched
                auto hide = Hide::create();
                auto show = Show::create();
                auto delay = DelayTime::create(0.75f);
                ///make a sequence that hide and show a little delay to the label
                ///keep moving to another part of the screen without the player sees
                 auto sequenceTouch = Sequence::create(hide,delay,show, nullptr);
                target->runAction(sequenceTouch);


```


添加:
currentPoints +=5; This will increment currentPoints value if the label get touched.
回到HelloWorld.cpp中在HelloWorld::update里添加:
 
char buffer;
 sprintf(buffer, "%04lli", Game->currentPoints);
 currentScore->setString(std::string(buffer));
这会获取’currentPoints’的现有值并将它转换为字符。

如果你先在运行项目现在就会有分数显示了。
  

但最高分呢?
为了完成这一步我们在HelloWorld.h里声明方法bestScoreUpdated。
void bestScoreUpdated();
and in the HelloWorld.cpp:
在HelloWorld.cpp里:
void HelloWorld::bestScoreUpdated()
{
    int best = UserDefault::getInstance()->getIntegerForKey("Best_Score");

    if(best < Game->currentPoints){
        best = Game->currentPoints;
        UserDefault::getInstance()->setIntegerForKey("Best_Score",best);
        char buffer;
        sprintf(buffer, "%04lli", best);
        bestScore->setString("Best: " + std::string(buffer));
        UserDefault::getInstance()->flush();
    }
    else{
        char buffer;
        sprintf(buffer, "%04lli", best);
        bestScore->setString("Best: " + std::string(buffer));
}
}

```


现在我们引用HelloWorld::update里的方法:
void HelloWorld::update(float df)
{
 Game->changeColor();
 LabelCatchMe = Game->getLabel();
 char buffer;
 sprintf(buffer, "%04lli", Game->currentPoints);
 currentScore->setString(std::string(buffer));
 bestScoreUpdated();
}

```

最后我们在HelloWorld::init里把标签添加到场景:
bestScore = LabelTTF::create("Best: ","fonts/NextGames.ttf",25);
    bestScore->setPosition(Point(origin.x + currentScore->getContentSize().width * 2.0f,
                                    origin.y + visibleSize.height-currentScore->getContentSize().height));
    addChild(bestScore,-1);

```

现在你得到了一个好玩的游戏了!
  

希望你喜欢这篇教程!
一些额外内容来改进游戏:
添加一个计时器、有趣的音乐。突破你的想象吧~

非常不错的文章。顶!!!

咦,这个获奖系列 弄成一个专题 不错~~

sprintf(buffer, “%04lli”, Game->currentPoints); …%04lli是什么啊:10:

类CatchMe拥有个LabelTTF, 为什么还要继承自LabelTTF呢?