飞机游戏:屏幕滚动,飞机拖拽,边界设置,子弹跟随

来源:互联网 发布:网络商业计划书 编辑:程序博客网 时间:2024/06/06 15:36

昨天没有写博客,今天将内容一并补上。这几天都学的好辛苦,前所未有的辛苦。
之所以会这样,一是因为对API还很陌生,再就是对于C++类的运用有待进一步清晰。
还有一点,我是一个有阅读障碍的人,能够读的出来的东西我就很容易理解。但很多API的代码,我完全没法读,所以对于我还不理解的东西,我用的时候老觉得不踏实。总感觉像借来的,随时要还给人家的感觉。

屏幕滚动:

这一点跟之前做flabby bird游戏时,底板移动的逻辑是一样的,举例说明:
首先要获取到场景的尺寸和坐标

Size visibleSize = Director::getInstance()->getVisibleSize();Vec2 origin = Director::getInstance()->getVisibleOrigin();

假设场景的锚点在左下角(0,0),背景图片的锚点在正中间(0.5,0.5)。

那么第一张图片的初始位置就是(场景宽度/2,场景高度/2)sprite1->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2)); 那么第二张图片的初始位置就是(场景宽度/2,图片1的高度+图片2高度/2)sprite2->setPosition(Vec2(visibleSize.width / 2, sprite1->getContentSize().height + sprite2->getContentSize().height/2));

图片需要向下偏移,偏移的速度需要用到一个计时器,这里有一个API:

/*添加背景计时器,计时器有一个回调方法,后面的数字代表频率,单位为秒,数字类型为float型*/schedule(CC_SCHEDULE_SELECTOR(GameScene::autoremove), 0.002f);   /*在计时器的回调方法中实现屏幕的滚动*/void GameScene::autoremove(float dt){    Size visibleSize = Director::getInstance()->getVisibleSize();    auto b1 = this->getChildByTag(1);    //这是按标签获取子节点    auto b2 = this->getChildByTag(2);    b1->setPositionY(b1->getPositionY() -5);   //这是设置单位偏移量,每个计算单位沿Y轴向下偏移5个像素    b2->setPositionY(b2->getPositionY() -5);    /*滚动算法:当图片上沿移动至场景下沿是将其置于第二张图片的初始位置,依次循环*/    if (b1->getPositionY() + b1->getContentSize().height / 2 <= 0)    {        b1->setPosition(Vec2(visibleSize.width / 2, b2->getContentSize().height / 2 +   visibleSize.height));     }    if (b2->getPositionY() + b2->getContentSize().height / 2 <= 0)    {        b2->setPosition(Vec2(visibleSize.width / 2, b2->getContentSize().height / 2 + visibleSize.height));    }}

飞机拖拽:

1,采用单例模式创建飞机对象。在飞机类中声明一个静态的成员变量(静态的飞机类指针),还有一个静态的成员函数用于返回该即将创建的飞机对象。该模式下只会创建唯一的飞机对象,而且可以当做全局变量使用,确保在任意文件中都可以被获取到。

2,因为飞机所在的图层大小和它的父图层(Gamescene)图层的大小是一样的,而在点击飞机的时候会有一个穿透效应,就是说点击的其实是飞机所在的图层,而图层的大小又是和场景大小一样且完全重叠的,所以该情况下是无法完成拖拽的。解决方案有两种:
->1,设法直接获取到飞机精灵在图层上的节点位置。(当前使用的就是该方法)
->2,将飞机图层的尺寸设置成和飞机的尺寸大小一样,继而达到移动飞机所在图层便能移动飞机的效果。
3,所以在飞机的类中还要声明一个节点类型的成员变量,和一个用于返回该节点的成员函数。
4,飞机所有的动作都需要有一个函数来实现,所以还需要再定义一个成员函数。

代码如下:class Planee : public cocos2d::Layer{public:     static Planee* getInter();    Node* getPlaneSp();    void showInit();private:    static Planee *pl;   //一定要是静态的指针变量    Node *sprite1;      //后面创建飞机精灵时就必须用sprite1这个变量名,否则就没有意义了};

单例模式创建飞机对象的逻辑:首先检查有没有,如果没有就创建,有就直接返回

代码如下:Planee *Planee::pl = NULL;   //永远记得指针的初始化,用NULL把它栓起来Planee* Planee::getInter(){    if (NULL == pl)    {        pl = new Planee();    }    return pl;}

经过了以上这一步之后,不管之前有没有创建过pl这个飞机对象,现在它都已经存在了,那么接下来就是返回飞机的节点,为后面做准备:

Node* Planee::getPlaneSp(){    return sprite1;}

接下来就是实现飞机精灵的各种功能,首先在加载精灵的时候,精灵的变量名就直接用Sprite1,我们将它设计成一个可以全局通用的变量就是要在这里用的。所以此处的变量名前不能再加auto,不然会报错,会报错。

sprite1 = Sprite::create("player1.png");

精灵加载好,接下来就是给它添加点击事件。点击事件有按下,移动,抬起,撤销,但这里需要用到的只有按下,移动,抬起,这一段主要是API的使用:

    /*添加点击事件*/    auto listener1 = EventListenerTouchOneByOne::create();    listener1->setSwallowTouches(true);    /*鼠标按下*/    listener1->onTouchBegan = [](Touch* touch, Event* event)    {        auto target = static_cast<Sprite*>(event->getCurrentTarget());  //关键代码:获取当前对象        Vec2 locationInNode = target->convertToNodeSpace(touch->getLocation());        Size s = target->getContentSize();   //获取当前对象的尺寸        Rect rect = Rect(0, 0, s.width, s.height);   //设定点击响应范围        if (rect.containsPoint(locationInNode))    //判断点击是否有效        {            log("sprite began... x = %f, y = %f", locationInNode.x, locationInNode.y); //打印坐标            target->setOpacity(180);            return true;        }        return false;    };    /*鼠标移动*/    listener1->onTouchMoved = [](Touch* touch, Event* event)      {        Size visibleSize = Director::getInstance()->getVisibleSize();  //场景尺寸        auto target = static_cast<Sprite*>(event->getCurrentTarget());        target->setPosition(target->getPosition() + touch->getDelta());    /*鼠标抬起*/    listener1->onTouchEnded = [=](Touch* touch, Event* event)    {        auto target = static_cast<Sprite*>(event->getCurrentTarget());        log("sprite onTouchesEnded.. ");        };

到这里,飞机已经可以跟随鼠标移动。接下来就是设置边界,这个很好理解,飞机的锚点在正中心,保证其不出界只需要让锚点距离场景边界的距离始终>=飞机对应轴向尺寸的一半就可以。

X轴右边界为例,设置如下        if (target->getPositionX() + touch->getDelta().x >= visibleSize.width - target->getContentSize().width/2)        {            target->setPositionX(visibleSize.width - target->getContentSize().width / 2);

经过一番折腾,边界问题也解决了,最难的部分到了,怎样才能让子弹跟着飞机跑呢?

逻辑:
在加载子弹精灵时,将其初始化的坐标等于飞机坐标,这样就可以实现子弹与飞机的跟随效果。要实现这个效果,首先就得让子弹能够获取到飞机精灵在图层上的节点坐标。这就是为什么我们要在前面的飞机类中设计一个返回值为节点类型的成员函数的原因。那么最让我觉得绕的地方就在于,如何才能把飞机对象传给子弹,思路如下:

1,在子弹类的头文件中引用飞机类的.h文件

2,在子弹类中定义一个构造函数,参数为飞机类的一个指针

/*子弹类的结构*/class Bullet : public cocos2d::Layer{public:     Bullet(Planee*);   //构造函数,参数为一个飞机类的指针    void show();    void move(float dt);private:    Planee *planee;};

3,在GameScene中动态创建子弹对象时,传入飞机对象

/*动态创建子弹*/Bullet *p2 = new Bullet(planee);   //将单例模式下创建的planee对象作为参数传入构造函数this->addChild(p2, 1, 4);return tru

4,用子弹类的私有成员变量来接传入的飞机对象,此时子弹类的各成员函数中就可以获取到飞机的对象,再通过飞机对象的成员函数就可以获取到飞机的节点位置。也就是说从头到尾都只有一个飞机对象,只是中间辗转反侧换过几个别名而已。

/*子弹类的构造函数*/Bullet::Bullet(Planee *p)  //此句有疑问{    this->planee = p;    show();}

5,获取以后就可以将飞机的节点位置作为子弹的发射位置,子弹移动的目的坐标的X坐标也要做相应的修改

/*添加子弹*/auto sprite3 = Sprite::create("bullet_1.png");sprite3->setPosition(Vec2(this->planee->getPlaneSp()->getPositionX(),                         this->planee->getPlaneSp()->getPositionY())); //设置坐标/*this->planee->getPlaneSp()->getPositionX()  //这一步是关键,好好理解*/
0 0
原创粉丝点击