cocos2dx 3.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;
     }
}

修改AppDegate.cpp里面的HelloWorld::createScene();改为   
auto scene = MainScene::createScene();

再使用一下自己写的create1   auto scene =MainScene::create1();

这样我们就完全掌握了游戏场景的创建和它的原理,其实更重要的是我们认识到了应该去怎么学,cocos2dx引擎使用了大量的宏定义,我们一定不能只追求表面的用法,而应该深入下去学习宏实现了那些东西。特别是到后面的内存管理更是如此。

总结一下,也再重复一遍,所有游戏场景的基础都是这三句代码:

    static cocos2d::Scene* createScene();
    virtual bool init();
    CREATE_FUNC(HelloWorld);

我在后面每一天新加场景都要手写一遍,一定要搞把这三句代码以及实现牢记于心。后面的学习中还会遇到大量的宏,我们要学会跟踪宏定义,仔细阅读每一段代码,这样我们才能学懂而不是简单的学会。

好,后面接下来完善我们的第一个游戏场景。现在新建的场景是空白的,什么都没有。我们要尝试往场景中添加各种展示元素。

这里有三个定义要搞清楚,场景(scene)、层(layer)、精灵(sprite)。(原谅我表达能力有限,大家最好先阅读一本cocos2dx的书籍,把一些理论知识弄明白)。

场景可以包含多个层,层可以包含多个精灵。 精灵可以是我们在游戏看到的所有的元素,比如按钮、血量条、人物等。比如酷跑中,可以看到近处的背景精灵移动快,远处的背景精灵移动慢,我们可以添加两个层到场景中,一个层中循环快速的精灵背景,一个循环慢速的精灵背景。这样就容易理解它们的概念了吧。

进入正题

Sprite精灵创建

如果你是新手,一定要把下面的代码自己敲一下。磨刀不误砍柴工,现在担心敲代码耽误时间,以后只会耽误更多的时间。

首先在resource文件夹下放一张图片00191880.jpg(华为入职时的照片,工号191880,从来没改过^_^),

然后创建精灵,展示这张图片。

三句,很简单,添加到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)看不懂?好吧,这个类似于迭代器的遍历,你可以改成for(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("fonts/barnacle.ttf", 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", "fonts/16.ttf", 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();//看返回值类型,应该知道这个是飞机所占矩形区域的大小

    if(rectPlayer.containsPoint(touch)){//如果点击的点在这个矩形区域内就可以对飞机进行拖动
        Point temp = pTouch->getDelta();
       spPlayer->setPosition(spPlayer->getPosition() + temp);
    }

}
 

getBoundingBox()是获取精灵所占的矩形大小,containsPoint()查看点是否在矩形内。知道了如果响应单点触控,这样就可以完全实现飞机的拖拽了。

2.如何判断子弹是否命中飞机?

我前面提到过定时器,每帧执行回调函数。可以把敌机存到一个数组里,每次遍历敌机数组,判断子弹的点是否在敌机中。如果是的话,就表示命中,然后在数组中删除敌机元素,在层中删除敌机精灵。

对精灵执行  sp->removeFromParentAndCleanup(true);可以在层中消除自身。

到这里应该可以写出简单的打飞机了。

PS:如果你使用vector,然后erase在迭代器中删除。那请注意正确使用STL遍历时的erase。见我的博客:STL各种容器如何正确的erase

cocos2dx 3.1从零学习(一)——入门篇(一天学会打飞机),布布扣,bubuko.com

时间: 2024-12-21 03:49:12

cocos2dx 3.1从零学习(一)——入门篇(一天学会打飞机)的相关文章

cocos2dx 3.1从零学习(三)——Touch事件(回调,反向传值)

第三讲 Touch 前面两篇我们学习的内容,足够我们做一款简单的小游戏.也可以说,我们已经入门了,可以蹒跚的走路了. 本篇将讲解cocos2dx中很重要的touch回调机制.你肯定记得第一章做定时器时间的时候用过CC_CALLBACK_1宏定义,它让我们回调一个只有一个形参的函数来执行定时操作. 回调函数的实现(Lambda表达式) 学习本篇前请仔细学习一下C++11的特性,std::function和lambda表达式.C++11还引入了很多boost库的优秀代码,使我们在使用的时候不必再加b

cocos2dx 3.1从零学习(二)——菜单、场景切换、场景传值

回顾一下上一篇的内容,我们已经学会了创建一个新的场景scene,添加sprite和label到层中,掌握了定时事件schedule.我们可以顺利的写出打飞机的主场景框架. 上一篇的内容我练习了七个新场景,每一个场景都展示不同的东西,像背景定时切换.各种字体的随机颜色和位置等.每次要切换一个场景都要修改AppDelegate中的调用代码,非常的不方便查看,这一篇我们写场景的切换.每当我们创建一个新的场景的时候只要添加对应按钮到主界面,点击即可以切换过去查看对应的效果.这个有点类似官方提供的cppt

cocos2dx 3.1从零学习(五)——动画

动画是游戏中最重要的表现部分,本篇只是初步学习大部分动画的使用方法,没有什么原理性的东西,但是例子有很多,如果有不熟的地方要练一下. 特别是Spawn和Sequence的组合使用,什么时候使用Spawn,什么使用使用Sequence,怎么在它们之间相互嵌套对方.用光你的脑细胞尽力的去折磨这几个函数吧!做出尽可能多的绚丽的组合! 动画方法 动画的方法都在Node中,它的子类包括Layer.Sprite.MenuItem等. runAction 启动动画 stopAction 停止动画 stopAc

cocos2dx 3.1从零学习(六)——CocosStudio(VS2013工程导入及环境设置)

导入libCocosStudio.libExtensions.libGUI 新建的工程如下图: 添加现有项目 右键解决方案,如下操作: 分别添加以下三个项目: (工程路径)\cocos2d\cocos\ui\proj.win32\libGUI.vcxproj (工程路径)\cocos2d\cocos\editor-support\cocostudio\proj.win32\libCocosStudio.vcxproj (工程路径)\cocos2d\extensions\proj.win32\li

cocos2dx 3.1从零学习(四)——内存管理(错误案例分析)

本篇内容文字比较较多,但是这些都是建立在前面三章写代码特别是传值的时候崩溃的基础上的.可能表达的跟正确的机制有出入,还请指正. 如果有不理解的可以联系我,大家可以讨论一下,共同学习. 首先明确一个事实,retain和release是一一对应的,跟new和delete一样. 1.引用计数retain release 这里请参考一下引用计数的书籍,肯定说的比我讲的详细. 简单一点理解就是,对new的指针加一个计数器,每引用一次这块内存,计数就加1.在析构的时候减1,如果等于0的时候就delete这个

Vue学习笔记入门篇——组件的使用

本文为转载,原文:Vue学习笔记入门篇--组件的使用 组件定义 组件 (Component) 是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能.在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展. 组件使用 注册 注册一个全局组件,你可以使用 Vue.component(tagName, options).组件在注册之后,便可以在父实例的模块中以自定义元素 的形式使用.

Vue学习笔记入门篇——组件的内容分发(slot)

本文为转载,原文:Vue学习笔记入门篇--组件的内容分发(slot) 介绍 为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板.这个过程被称为 内容分发 (或 "transclusion" 如果你熟悉 Angular).Vue.js 实现了一个内容分发 API,使用特殊的 'slot' 元素作为原始内容的插槽. 编译作用域 在深入内容分发 API 之前,我们先明确内容在哪个作用域里编译.假定模板为: <child-component> {{ messa

Vue学习笔记入门篇——组件的通讯

本文为转载,原文:Vue学习笔记入门篇--组件的通讯 组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B.它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将它内部发生的事情告知给父组件.然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的.这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性.在 Vue 中,父子组件的关系可以总结为 props down, events up.父组件通过 props 向下传递

storm学习之入门篇(一)

海量数据处理使用的大多是鼎鼎大名的hadoop或者hive,作为一个批处理系统,hadoop以其吞吐量大.自动容错等优点,在海量数据处理上得到了广泛的使用.但是,hadoop不擅长实时计算,因为它天然就是为批处理而生的,这也是业界一致的共识.否则最近这两年也不会有s4,storm,puma这些实时计算系统如雨后春笋般冒出来.先抛开s4,storm,puma这些系统不谈,我们首先来看一下,如果让我们自己设计一个实时计算系统,我们要解决哪些问题: 1.低延迟.都说了是实时计算系统了,延迟是一定要低的