个人初做小游戏RunRunMagic(三) 玩家精灵的创建

过了一天了,继续干活奋斗

我习惯是把这样的精灵拿出来单写一个类,这样的类继承自Sprite类,再简单也要写成一个类(比如可能只是初始化时添加一张图片)。。可能有经验了之后会有别的想法吧。。。人物要是想有一个跑动的动作就需要起本身就有“动作”,我还不是太会骨骼动画,而且这样的小动画帧动画就足够了。

这次制作帧动画所需要的工具除了上一篇用到的像素绘以外,还有制作png和plist的软件Zwoptex。使用它就可以把帧动画的图片们合成一张图以及制作plist啦。网上教程还是不少的,随便查查应该都有。

总之用这个方法人物的png就是下面这一张:


还是那句能看就好了敲打其实真正动起来之后发现有点奇怪,人跑起来有种游泳的感觉(哭)

(哇这动图怎么比原来大这么多!我果然还是不太会用像素绘)

总之素材ok了就开始干活吧!

人物类中的构造函数:

Player::Player(){
    Sprite::initWithFile("magic02.png");
    isMove = false; //用来记录人物是否移动的bool变量
    isGo = false; //用来记录点击事件中手指的动作究竟是在移动还是点击的bool变量
}

至于isMoveisGo到底是个什么东西呢,后面会慢慢说。。。

其实isGo我总觉得好像不太应该写在人物的这个类里。。。可能还是直接写在游戏的界面会比较好。。。?

上面的构造函数中用四帧中的其中一帧作为人物初始化用的首帧图片,然后初始化了其他成员变量,只做了这么一点事。然后我们开始制作动图吧!

本来想让人物有着各种各样的动作,不过后来有点懒得画素材了所以作罢。。。(所以说不要随便挖大坑)所以在人物的动作函数中只有跑路一个姿势orz

void Player::PlayerMove(){   
    auto cache = SpriteFrameCache::getInstance();
    cache->addSpriteFramesWithFile("magicAll.plist", "magicAll.png");
    Vector<SpriteFrame*>vec;
    char name[20];
    for(int i=1;i<=4;i++){
        sprintf(name, "magic0%d.png",i);
        auto frame = cache->getSpriteFrameByName(name);
        vec.pushBack(frame);
    }
    
    auto animation = Animation::createWithSpriteFrames(vec,0.1f);
    auto animate = Animate::create(animation);
    auto seq = Sequence::create(animate, NULL);
    auto repeat = RepeatForever::create(seq);
    
    this->runAction(repeat);
}
只要Player的对象调用这个函数人物就可以跑(动)起来啦!这也是创建帧动画的过程,仔细看看并不难 (再不济背下来也好哇)

这样的话只要把人物放到合适的位置,和滚动的背景放在一起,人物就会真的像跑起来一样啦!耶!

咳咳继续干活。。。


然后我们要实现的目标还有什么呢?

1.跑道有三条,人物是要可以上下移动的。但是只能停在这三条跑道(说白了就是三条线)上

2.点击屏幕左边人物跳跃,点击右边人物发射火球

3.(制作途中发现的很重要的一点)人物在执行动作时会有执行的时间,在这个时间内是不能执行其他动作的!这也是上面出现isMove这个bool变量的原因。

暂时就是这些吧?

上面这三条都有一个很重要的共同点,就是都需要涉及到屏幕触摸的操作。因为这些触摸的都是我们的游戏界面,因此我们回到界面的那一类中。

要写的就是下面这几个函数,害怕写错的话可以跳进Layer类中寻找,复制粘贴的话就不怕错啦!

    virtual bool onTouchBegan(Touch *touch, Event *unused_event); //触碰到屏幕时触发的函数
    virtual void onTouchMoved(Touch *touch, Event *unused_event); //手势移动时出发的函数
    virtual void onTouchEnded(Touch *touch, Event *unused_event); //手离开屏幕时触发的函数
    virtual void onTouchCancelled(Touch *touch, Event *unused_event); //触摸取消(来电话啊、有弹出的消息框啊)时触发的函数
也就是说,如果在屏幕上划了一小段,触发的函数应该是touchBegan->touchMoved(移动时会触发多次)->touchEnded,而点击的话则是touchBegan->touchEnded,无论怎样touchBegan和touchEnded都是会触发到的!这也就是声明 isGo的原因(这也是我认为把它声明在Player这个类里不太对的原因。。。)
当然大家也都知道,只写了这几个函数的话什么也不会发生的!下面就是这个非常重要的触摸监听啦!

   //得到事件分发器
    auto dispatcher = Director::getInstance()->getEventDispatcher();
    //创建监听器 监听this
    auto listener = EventListenerTouchOneByOne::create();
    //给监听器添加调用函数
    listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan,this);
    listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
    listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this);
    listener->onTouchCancelled = CC_CALLBACK_2(HelloWorld::onTouchCancelled,this);
    dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
我对于这些行的态度和帧动画是一样的, 再不济背下来也好哇

写完触摸监听我们就可以开始编写触摸事件啦!

值得注意的是,touchBegan的返回值是bool类型的。如果想在执行touchBegan后继续执行touchMoved和touchEnded,就不要忘记在touchBegan中return true!

bool PlayLayer::onTouchBegan(Touch *touch, Event *unused_event){
    return true;
}
我这次在touchBegan中什么都没有写,可能有人要问了,点击的时候人物不是会跳跃或者攻击的吗?这个在后面会提到。

接下来是touchMoved:

void PlayLayer::onTouchMoved(Touch *touch, Event *unused_event){
    if(player->isMove == true)//如果人物移动中则返回
        return;
    
    player->isGo = true;
    if(touch->getStartLocation().y < touch->getLocation().y){//如果向上滑动
        if(player->getPosition().y >= 120){//如果人物在顶端则返回
            return;
        }else{
            player->isMove = true;
            auto move = MoveTo::create(0.5f, Vec2(50,player->getPosition().y+40));//向上移动100
            auto call = CallFunc::create(CC_CALLBACK_0(PlayLayer::stopMove, this));
            auto seq = Sequence::create(move,call, NULL);
            player->runAction(seq);
        }
    }
    
    if(touch->getStartLocation().y > touch->getLocation().y){//如果向下滑动
        if(player->getPosition().y <= 40){//如果人物在底端则返回
            return;
        }else{
            player->isMove = true;
            auto move = MoveTo::create(0.5f, Vec2(50,player->getPosition().y-40));//向上移动100
            auto call = CallFunc::create(CC_CALLBACK_0(PlayLayer::stopMove, this));
            auto seq = Sequence::create(move,call, NULL);
            player->runAction(seq);
        }
    }
}
void PlayLayer::stopMove(){
    player->isMove = false;
}

在这里之前提到的isMove和isGo在这里都出现了,接下来我们慢慢说

首先在函数的参数列表中,touch可以说是我们在屏幕上触碰到的那一点的一个变量。这次用到了他的两个方法:

<span style="white-space:pre">	</span>touch->getStartLocation()//得到触摸执行过程中起始触摸点的坐标
<pre name="code" class="cpp"><span style="white-space:pre">	</span>touch->getStartLocation()//得到当前触摸点的坐标

 

这里是非常有用的,笔记笔记

我设计的三条跑道的y坐标分别为40、80、120,因为怕后面忘记,先拿小本子记录一下吧得意

当移动的起始触摸点的y值小于后面移动到的点的y值,就证明不管是斜着划还是直着划,总是是在向上滑动的,因此人物要向上移动。反之亦然。不过值得一提的是,这样的判断因为太过灵敏(一个像素点的偏移就被认为是移动),在一些手机上可能会出现想要点击却上下移动的情况,因此可以把偏移量改大一些,这样其实用户体验也会好一些(小白就不要拿用户体验出来装B啦)

还有很重要的一点,人物在移动的时候是不能执行其他操作的,第一句如果isMove为true则直接返回就是在判断这一步。如果没有这样的判断的话,首先人物的操作会变得太过随意,降低游戏难度。同时也很有可能出现人物移动位置出现错乱的现象,所以这样的判断还是很有必要的。至于如何将isMove变回false,起初我是写在了时间调度中,随时判断人物是否移动到了指定位置。后来被人提醒这样的写法不好,才想出了现在的方法。写一个队列,在执行完对应动作后执行一个方法将isMove变回false,这样就没有问题了。能想出这种方法的我真是太天才了得意(不要臭美啊喂)

对了还有很重要的一点就是人物是只能在三条跑道上停留的,所以在顶端或底端时直接return,这个思想与isMove为true时直接返回的思想很想,因此不再多说。

最后的最后我差点忘掉了isGo!这里只是将其改为true,既然执行到了touchMoved这个函数就证明手势确实是在移动的。因此将其改为true。

说完touchMoved,让我们再来看一看touchEnded吧:

void PlayLayer::onTouchEnded(Touch *touch, Event *unused_event){
        if(player->isGo == true){//如果是Move则令其状态变回false,然后返回
            player->isGo = false;
            return;
        }
    
        while (touch->getLocation().x < 100) {//点击屏幕左侧 人物跳跃
            if (player->isMove == true) {
                return;
            }
            player->isMove = true;
            auto jump = JumpTo::create(0.5, player->getPosition(), 32, 1);
            auto call = CallFunc::create(CC_CALLBACK_0(PlayLayer::stopMove,this));
            auto seq = Sequence::create(jump,call, NULL);
            player->runAction(seq);
        }
        while (touch->getLocation().x > 140) {//点击屏幕右侧 放出火焰
            if (player->isMove == true) {
                return;
            }
            player->isMove = true;
            auto fire = new Fire();
            int y =player->getPosition().y;
            fire->setPosition(Vec2(60,y));
            this->addChild(fire,2);
            
            auto fireMove = MoveTo::create(2, Vec2(260,y));
            auto fireRotate = RotateTo::create(2, 360*8);
            auto spawn = Spawn::create(fireMove,fireRotate, NULL);
            auto call = CallFunc::create(CC_CALLBACK_0(PlayLayer::stopMove,this));
            auto seq = Sequence::create(DelayTime::create(0.5),call, NULL);
            fire->runAction(spawn);
            fire->runAction(seq);
            fireArray->addObject(fire);
            log("fire");
        }
}
粘到这里我发现自己忘记说fire相关的东西了。。。其实非常简单只是个图片的初始化

Fire::Fire(){
    Sprite::initWithFile("fire.png");
}

 ←这个是图片

 总之这里首先进行了一个判断,判断触摸事件是点击还是滑动。如果调用过touchMove,则在这个函数中将isGo改为false后直接返回。(这个方法是一个认识的大神帮我想出来的,致敬可怜

然后根据点击的点坐标的x值判断其点击的是屏幕的左边还是右边(我这里直接是左100右140,中间有40点击是没用哒哈哈哈哈。。)左边的话是跳跃,右边的话是发射火球,这个我们后面慢慢说。

首先是跳跃,这个和移动的思想非常像,不过就是让人物原地跳跃了一下,动作时间为5秒。定义一个队列,执行完跳跃后isMove改为false,这一点是非常简单的。

至于发射火球,就是让火球出现在人物的前面,然后让他直直的飞出去就好了。对了动作是移动的同时在旋转的,这样看起来更好玩一点,嘿嘿ww当然还有一个时间间隔,我这次设置的是0.5秒而并不是火焰的动作执行完后才可以的。当然看看也就明白了吧。

最后的最后还有一点。。。可能有人看到最后的fireArray了然后想这个是什么鬼??其实这个是我在之后才想起并添加上的一个__Array。

    fireArray = __Array::create();
    fireArray->retain();
这是为后面碰撞检测方便而设置的数组,当然这就是后话了,我们后面再慢慢谈。

总之现在人物就可以跑跑跳跳发火球啦!大家辛苦了奋斗

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值