cocos2dx3.1从零学习(⼀)——⼊门篇(⼀天学会打飞机)
我们有C++基础,学习引擎总是急于求成,想⽴马做出⼀款简单的游戏给朋友玩。但是我们往往看了很多
资料却⼀直不知道如何下⼿去写,有时候只要能⾛出第⼀步我们就会游刃有余,但是眼⾼⼿低的我们不是⼤
神,需要有⼈指引⼀下。这⾥我就写⼀下我是如何⼊门学习cocos2dx3.1的,给⼤家参考⼀下。
如果你想第⼀天就写出打飞机,请耐⼼去阅读。我也是⼀个菜鸟,博客难免粗糙和出错,请⼤家谅
解。加油吧!
我们创建⼯程后总会⾃带⼀个HelloWorld类,短短的⼏⾏代码就出来了⼀个游戏的雏形,请问我们真的理解
它了吗?如果我们能早⼀点弄明⽩这⼏⾏代码,我们或许会⽐现在⾛得更远。
理解HelloWorld类
HelloWorld去掉退出按钮只有下⾯三个函数。
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(HelloWorld);//⼀定要⾃⼰看源码
这三个⽅法⼀定要做到透彻理解和重写。因为所有的游戏场景都需要这三个函数。
创建游戏HelloWorld场景的时候,只需要在AppDelegate写⼀句: Helloworld::createScene();
请看它的实现
// 'scene' is an autorelease object
auto scene = Scene::create();
// 'layer' is an autorelease object
auto layer = HelloWorld::create();
/
/ add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
⾸先新建⼀个新场景,然后create⼀个HelloWorld层并添加到新创建的场景,最后返回这个场景给AppDelegate。
⼀个场景的创建和展⽰就是这么简单,但是你有没有发现HelloWorld中没有create呢?并且没有调⽤init()
⽅法呢?我们在HelloWorld中没有在任何地⽅看到调⽤init初始化,那么这是在哪⾥调⽤的呢?
请看CREATE_FUNC的宏定义:
#defineCREATE_FUNC(__TYPE__) \
static __TYPE__*create() \
{ \
__TYPE__ *pRet = new __TYPE__(); \
if (pRet && pRet->init()) \
{ \
pRet->autorelease(); \
return pRet; \
} \
else \
{ \
delete pRet; \
pRet = NULL; \
return NULL; \
} \
}
\表⽰换⾏,因为宏定义代码如果放同⼀⾏阅读不⽅便。
可以看到CREATE_FUNC宏其实是定义了⼀个静态成员函数,这个函数可以new⼀个新的对象,并对其进⾏init()操作。
这样整个Helloworld的逻辑就清晰了:
外部类调⽤static cocos2d::Scene* createScene();来创建⼀个新的场景。  CREATE_FUNC(HelloWorld);是定义⼀个静态的create类成员函数,并在这个函数中调⽤virtual bool init();初始化这个场景。
以后每当我们新建⼀个场景的时候,按照这个格式即可。好了,下⾯该动⼿了。
现在请删除Helloworld新建⼀个叫MainScene的场景,完善该类,修改AppDelegate.cpp中的场景创建
为auto scene = MainScene::createScene();运⾏展⽰新的场景。(这个时候整个屏幕还是⿊暗的,,因为没有任何元素显⽰)。
注意:如果是新⼿,我建议你再看⼀下Helloworld⾥⾯这三句代码的定义。
⽐如:static⽅法⾥⾯ auto layer =***(这⾥是当前类名,不是Layer)::create();
init()⽅法最好加virtual修饰,也可以这样写, virtual bool init() override;(override表⽰继承来的,对它重载)。在init⾥⾯⼀定先初始化⽗类Layer::init()。
到这⾥应该会⾃⼰重写⼀个空⽩的场景了,如果你以前不明⽩这⾥,并且刚才没有动⼿写的话,那么再请你删除CREATE_FUNC这句,⾃⼰重写⼀个create函数吧。
动⼿重写CREATE_FUNC宏定义
MainScene *MainScene::create1(){
auto mainS = new MainScene;
if ( mainS && mainS->init())
{
mainS->autorelease();
return mainS;
}
else
{
delete mainS;
mainS = NULL;
return nullptr;
xcode入门}
}
修改AppDegate.cpp⾥⾯的HelloWorld::createScene();改为    auto scene = MainScene::createScene();
再使⽤⼀下⾃⼰写的create1:  auto layer=MainScene::create1();
这样我们就完全掌握了游戏场景的创建和它的原理,其实更重要的是我们认识到了应该去怎么
学,cocos2dx引擎使⽤了⼤量的宏定义,我们⼀定不能只追求表⾯的⽤法,⽽应该深⼊下去学习宏实现了那些东西。特别是到后⾯的内存管理更是如此。
总结⼀下,也再重复⼀遍,所有游戏场景的基础都是这三句代码:
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(HelloWorld);
我在后⾯每⼀天新加场景都要⼿写⼀遍,⼀定要搞把这三句代码以及实现牢记于⼼。后⾯的学习中还会遇到⼤量的宏,我们要学会跟踪宏定义,仔细阅读每⼀段代码,这样我们才能学懂⽽不是简单的学会。
好,后⾯接下来完善我们的第⼀个游戏场景。现在新建的场景是空⽩的,什么都没有。我们要尝试往场景中添加各种展⽰元素。
这⾥有三个定义要搞清楚,场景(scene)、层(layer)、精灵(sprite)。(原谅我表达能⼒有限,⼤家最好先阅读⼀本cocos2dx的书籍,把⼀些理论知识弄明⽩)。
场景可以包含多个层,层可以包含多个精灵。精灵可以是我们在游戏看到的所有的元素,⽐如按钮、⾎量条、⼈
物等。⽐如酷跑中,可以看到近处的背景精灵移动快,远处的背景精灵移动慢,我们可以添加两个层到场景中,⼀
个层中循环快速的精灵背景,⼀个循环慢速的精灵背景。这样就容易理解它们的概念了吧。
进⼊正题
Sprite精灵创建
如果你是新⼿,⼀定要把下⾯的代码⾃⼰敲⼀下。磨⼑不误砍柴⼯,现在担⼼敲代码耽误时间,以后只会
耽误更多的时间。
⾸先在resource⽂件夹下放⼀张图⽚00191880.jpg,
然后创建精灵,展⽰这张图⽚。
三句,很简单,添加到init()函数中吧!
auto sprite =Sprite::create("00191880.jpg");//auto是C++11的⾃动推断变量类型
sprite->setPosition(200, 200);//设置这个精灵在屏幕的位置
this->addChild(sprite);//把这个精灵添加到当前层中。
我没学过OC,我以前做windows客户端,感觉这种写法很不习惯,⼊乡随俗吧,如果你连this也不知道是
什么意思的话,建议你看⼀下C++Primer,这本书很重要。
下⾯是我练习的代码,⾃⼰尝试⼀下吧!
auto spriteA =Sprite::create("1.png");
auto spriteB =Sprite::create("2.png");
auto spriteC =Sprite::create("3.png");
this->addChild(spriteA);
this->addChild(spriteB);
this->addChild(spriteC);
Size visibleSize =Director::getInstance()->getVisibleSize();
spriteA->setPosition(visibleSize.width /4-30, visibleSize.height / 2);
spriteB->setPosition(visibleSize.width /4 * 3+32, visibleSize.height / 2);
spriteC->setPosition(visibleSize.width /2, visibleSize.height / 8 * 7+50);
spriteA->setScale(1.5);
spriteB->setScale(1.5);
spriteC->setScale(1.5);
如果你阅读过cocos2dx的书籍或者百度⼀下的话,相信你上⾯的代码⼀定看得懂。Director::getInstance()是
⼀个单例,获取整个游戏的导演类,后⾯的getVisibleSize()、getWinSize()还有setScale()是什么意思的,快⾃⼰动
⼿去查⼀下吧。
下⾯写⼀点⼩菜吧,添加的精灵不会动是不是很没意思?下⾯就让图⽚动起来:
在init⾥⾯添加
this->schedule(schedule_selector(Second::myupdate));
这是⼀个定时器,每隔⼀段时间会执⾏myupdate函数。
myupdate的定义如下(我不会告诉你schedule后⾯还可以再加⼀个参数表⽰隔多久执⾏⼀次的,再去查⼀
下呗):
voidSecond::myupdate(float f){//注意有⼀个float f 形参
auto sp = this->getChildren();//获取这个层中所有的孩⼦,也就是所有的精灵,看不懂?别逗我了,点进去看源码吧,注意它的返回值类型。    for (auto a: sp)
{
a->setPosition(a->getPosition().x, a->getPosition().y - 2);
}
}
上⾯这段代码就是移动刚才你添加的所有的精灵。
for (auto  a: sp)看不懂?好吧,这个类似于迭代器的遍历,你可以改成f or(auto a=
sp.begin();a!=sp.end();a++){}(原谅我⼿打代码,没有在编译器写,因为我是重新整理的)。
再运⾏⼀下你的程序让它们动起来吧,如果你够厉害的吧,肯定会有办法让它们在屏幕中怎么都停不下来!
补充:Sprite->setTexture()这个可以修改精灵的材质。
*****************************************************************************************
到这⾥,你是不是已经猜到打飞机飞机是怎么移动的了?只要再加⼀个碰撞检测就我们就可以实现了。碰撞检测?咱们不整这么⾼端了,其实就是for循环获取这⼦弹和飞机的位置,查看⼦弹精灵是否在飞机精灵的位置啦。
咱们不急,下⾯学⼀下标签(Label),它让我们可以展⽰打飞机的分数等。
Label的创建有很多种⽅法,下⾯简单介绍三种
Label::create
Label::createWithTTF
Label::createWithBMFont
学过了Sprite肯定能看懂下⾯代码。再说⼀遍,⼀定要⾃⼰去尝试⼀下。
std::string words = "三翻四复";//windows下是不⽀持中⽂的,xcode⽀持。
auto label = Label::create(words,"STHeiti", 30);//⿊体,三⼗号
this->addChild(label);
TTFConfigconfig("f", 25);//TTF字体
auto labelTTF =Label::createWithTTF(config, "Hello ");
labelTTF->setPosition(333,333);
labelTTF->setColor(Color3B(255, 0, 0));
this->addChild(labelTTF);
auto labelTTF1 =Label::createWithTTF("ABCDEFG", "f", 44);
labelTTF1->setTextColor(Color4B(255, 0,0, 255));
labelTTF1->setPosition(444, 444);
labelTTF1->enableShadow(Color4B::BLUE,Size(10,-10));
labelTTF1->enableOutline(Color4B::GREEN,3);
labelTTF1->enableGlow(Color4B::BLACK);
this->addChild(labelTTF1);
//下⾯的代码是我学习了前⾯的时候⾃⼰去写的,多加尝试
std::vector<std::string> names ={"AAAAAAAA","BBBBBBBBBB","CCCCCCCCCCC","DDDDDDDDDDDD"};
for (auto str : names){
auto tmpLabel = Label::create(str,"STHeiti", 40);
tmpLabel->setPosition(visibleSize.width / 2,visibleSize.height-i*45);
tmpLabel->setColor(Color3B(rand() /255, rand() / 255, rand() / 255));
tmpLabel->setRotation(rand()/180);
this->addChild(tmpLabel);
}
还有⼀个Label::createWithBMFont("fonts/futura-48.fnt","只能是英⽂或者数字"),futura-48.fnt字体很漂亮,有需要的我再上传吧。
上⾯的代码还有很多我没提过的知识点,因为这些都是我学习的笔记,最近我时间不是很多,所以不会写得太细,有忽略掉的就⾃⼰动⼿去查吧!相信你的⾃学能⼒不会⽐我差。
setColor是设置颜⾊。enableShadow加阴影。enableOutline加描边。还有很多特性,⼤家有兴趣都⾃⼰尝试⼀下。
*********************************************************************************************
开始打飞机
到这⾥⼀个场景典型的元素就介绍完了,Sprite和lable,下⾯来个硬菜吧!打飞机!
利⽤打飞机的素材,实现打飞机的基本功能。
有⼏个⼩细节说⼀下:
1:使⽤⿏标拖动飞机。
因为还没学习事件响应,这⾥提前学习⼀下单点触控。
在init添加下⾯这段代码,屏幕就可以响应单击事件了。
auto listen =EventListenerTouchOneByOne::create();
listen->onTouchBegan =CC_CALLBACK_2(GameScene::onTouchBegan, this);
listen->onTouchMoved =CC_CALLBACK_2(GameScene::onTouchMoved, this);
listen->setSwallowTouches(true);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listen,this);
CC_CALLBACK_2表⽰回调函数接收两个参数。
onTouchBegan、onTouchMoved等在头⽂件声明,这⼏个函数是继承⾃基类Layer的。如果你不确定跟基类的函数名是否⼀致,请在声明的时候加override,表⽰重载。注意onTouchBegan是返回值类型是bool,其他的是void。
bool onTouchBegan (Touch*, Event*)override;
void onTouchMoved(Touch*, Event*)override;
voidGameScene::onTouchMoved(Touch* pTouch, Event* pEvent){
Point touch =pTouch->getLocation();//返回点击的位置
Rect rectPlayer =spPlayer->getBoundingBox();//看返回值类型,应该知道这个是飞机所占矩形区域的⼤⼩
ainsPoint(touch)){//如果点击的点在这个矩形区域内就可以对飞机进⾏拖动
Point temp = pTouch->getDelta();
spPlayer->setPosition(spPlayer->getPosition() + temp);
}
}
getBoundingBox()是获取精灵所占的矩形⼤⼩,containsPoint()查看点是否在矩形内。知道了如果响应单点触控,这样就可以完全实现飞机的拖拽了。
2.如何判断⼦弹是否命中飞机?
我前⾯提到过定时器,每帧执⾏回调函数。可以把敌机存到⼀个数组⾥,每次遍历敌机数组,判断⼦弹的点是否在敌机中。如果是的话,就表⽰命中,然后在数组中删除敌机元素,在层中删除敌机精灵。
对精灵执⾏  sp->removeFromParentAndCleanup(true);可以在层中消除⾃⾝。
到这⾥应该可以写出简单的打飞机了。
PS:如果你使⽤vector,然后erase在迭代器中删除。那请注意正确使⽤STL遍历时的erase。见我的博客: