Cocos3.3横版游戏-Part2-加入主角及控制主角移动

Cocos3.3横版游戏-Part2-加入主角及控制主角移动,第1张

概述接上一篇 在这里我们将我们的主角导入,并通过虚拟摇杆控制他 在游戏的设计中,我们尽量做到逻辑代码和视觉表现的代码分离,否则设计到后期整个代码一团糟,无论是维护还是开发新内容都是困难重重. (我就被某教程的有限状态机(FSM)和动画载入混到一起,结果坑掉了自己的代码) Role是基础的角色类,包括了移动,攻击,受到攻击,死亡等等的动画及判断动画. 还加入了BodyBox和HitBox,这两个具体作用

接上一篇
在这里我们将我们的主角导入,并通过虚拟摇杆控制他
在游戏的设计中,我们尽量做到逻辑代码和视觉表现的代码分离,否则设计到后期整个代码一团糟,无论是维护还是开发新内容都是困难重重.
(我就被某教程的有限状态机(FSM)和动画载入混到一起,结果坑掉了自己的代码)

Role是基础的角色类,包括了移动,攻击,受到攻击,死亡等等的动画及判断动画.
还加入了BodyBox和HitBox,这两个具体作用后续再说
源码中你可以看到

/* 以下是攻击序列动画,需要用creatAttackAnimation创建,会在动作执行中间添加攻击判定和受伤飘字等*/    CC_SYNTHESIZE_RETAIN(Action*,m_pnomalattacka,NomalAttackA);   //角色普通攻击A时动画帧序列    CC_SYNTHESIZE_RETAIN(Action*,m_pnomalattackb,NomalAttackB);   //角色普通攻击B时动画帧序列    CC_SYNTHESIZE_RETAIN(Action*,m_pnomalattackc,NomalAttackC);   //角色普通攻击C时动画帧序列    CC_SYNTHESIZE_RETAIN(Action*,m_pnomalattackd,NomalAttackD);   //角色普通攻击D时动画帧序列    CC_SYNTHESIZE_RETAIN(Action*,m_pchange,Change);              //角色蓄力时的动画序列    CC_SYNTHESIZE_RETAIN(Action*,m_pchangeattack,ChangeAttack);  //角色蓄力攻击时的动画序列

等各种定义

具体的执行代码基本由这个组成

voID Role::runIDleAction(){       if(changeState(ACTION_STATE_IDLE))    {        this->runAction(m_pIDleaction);    }}

由此可以看到,主要功能就是播放一个动作

这个是从缓存中载入动画,为此我们需要在GameLayer.cpp的init中加入

SpriteFrameCache::getInstance()->addSpriteFramesWithfile("Boy.pList","Boy.pvr.ccz");

优先载入缓存,之后只需要调用就好了,我在这里写了好几个载入方式,这个是最基本的从0开始,只有3个参数,用于载入普通移动动画还有跳跃之类的.特点是中间不需要插入回调判断(受到敌人攻击或自己攻击判定回调),由于是从0开始的,一次可以将所有的动画读取完.回调函数没法插入,只能在后面加入.
稍微改变下,再多输入一个参数(如下),使得下方的i从framCountBegan开始,我们可以在连续squence中插入回调函数,更加精准进行攻击判定

Animation* Role::createAttackAnimation(const char* formatStr,int frameCountBegan,int frameCountEnd,int fps){    Vector<SpriteFrame*> pFrames;     for(int i = frameCountBegan; i < frameCountEnd; i++ )    {        const char* imgname = String::createWithFormat(formatStr,i)->getCString();        SpriteFrame *pFrame = SpriteFrameCache::getInstance()->getSpriteFrameByname(imgname);        pFrames.insert(i-frameCountBegan,pFrame);    }    return Animation::createWithSpriteFrames(pFrames,1.0f / fps);}

当然我们的角色可以改变状态必须是不和之前状态相同并且没有死.
如果死了那么就不要动了

bool Role::changeState(ActionState actionState){    if(currActionState == ACTION_STATE_DEAD || currActionState == actionState)    {        return false;    }    this->stopAllActions();    this->currActionState = actionState;    return true;}

我们应该给角色一个可以受到攻击的范围用于受击判定.最简单的方法是用一个BoundingBox,之后我们只要判断攻击框和受攻击的框有没有相交就好了.

BoundingBox Role::createBoundingBox(Vec2 origin,Size size){    BoundingBox boundingBox;    boundingBox.original.origin= origin;    boundingBox.original.size= size;    boundingBox.actual.origin = this->getposition() + boundingBox.original.origin;    boundingBox.actual.size= size;    return boundingBox;}voID Role::updateBoxes() {    bool isFlippedX = this->isFlippedX();    float x = 0.0f;    if(isFlippedX) {        x = this->getposition().x - m_hitBox.original.origin.x;    }else {        x = this->getposition().x + m_hitBox.original.origin.x;    }    m_hitBox.actual.origin = Vec2(x,this->getposition().y + m_hitBox.original.origin.y);    m_bodyBox.actual.origin = this->getposition() + m_bodyBox.original.origin;}voID Role::setposition(const Vec2 &position){    Sprite::setposition(position);    this->updateBoxes();}

这么写,我们的角色在更新定坐标时就可以同步更新body和hit这两个框了
基础的Role类其他都是重复的地方,看源码就可以理解了

接下来创建我们的Hero

class Hero :public Rolebool Hero::init(){    bool ret = false;    do     {        this->initWithSpriteFramename("boy_IDle_00.png");        this->setAnchorPoint(Vec2(0.48f,0.13f));        Animation *IDleAnim = this->createNomalAnimation("boy_IDle_%02d.png",3,4);        this->setIDleAction(RepeatForever::create(Animate::create(IDleAnim)));

Init中,我改变了这个精灵的锚点,我希望我的锚点可以直接对应角色的脚下,这样在直接判断移动坐标时比较省事.
上面是移动状态时应该载入的动作&动画

Animation *attackAnima1 = this->createAttackAnimation("boy_attack_00_%02d.png",0,5,21);        Animation *attackAnima2 = this->createAttackAnimation("boy_attack_00_%02d.png",8,15);        this->setNomalAttackA(Sequence::create(            Animate::create(attackAnima1),CallFuncN::create(CC_CALLBACK_1(Hero::attackCallBackAction,this)),Animate::create(attackAnima2),Role::createIDleCallbackFunc(),NulL));

这是我们角色攻击时应该是用的.就如我刚才说的一样,通过设定起点等方法创建一个animation然后通过sequence,并在中间插入CallFuncn更加的符合逻辑.不然只能等动画结束之后再判断,不符合状况.

this->m_bodyBox = this->createBoundingBox(Vec2(1,50),Size(30,40));        this->m_hitBox = this->createBoundingBox(Vec2(65,Size(50,90));

我们Hero的攻击范围和受击范围

voID Hero::attackCallBackAction(Node* pSender){}

攻击回调,先空着.

回到GameLayer,在init中加入

addHero();this->scheduleUpdate();

在下面写addHero和update

voID GameLayer::addHero(){    m_pHero = Hero::create();    m_pHero->setposition(_origin + Vec2(100,50));    m_pHero->runIDleAction();    m_pHero->setLocalZOrder(_visheight - m_pHero->getpositionY());    this->addChild(m_pHero);}voID GameLayer::update(float dt){    this->updateHero(dt);}

至此,运行一下,可以看到Hero站立在GameLayer中了.

接下来我们要实现如何通过不同层的摇杆来控制我们的Hero移动.
首先Hero.h中加入

voID onMove(Vec2 direction,float distance);    voID onStop();

实现方法是

voID Hero::onMove(Vec2 direction,float distance){    this->setFlippedX(direction.x < 0 ? true : false);    this->runWalkAction();    Vec2 veLocity = direction * (distance < 33 ? 1 : 3);    this->setVeLocity(veLocity);}voID Hero::onStop(){    this->runIDleAction();    this->setVeLocity(Vec2::ZERO);}

摇杆单位向量(方向)distance可以使我们的Hero反转,而direction控制Hero的移动速度.
至于这么写,我本是想通过摇杆实现走(walk)和跑(run)的切换,只需要移动稍微远点就好.不过找到的素材只有跑的画面,那就直接通过速度来感受吧

细心的人应该可以看到工程里面有一个Global类,我们通过它来进行多个层之间的控制Global.h中

#include "Singleton.h"

这是一个单类的头文件定义,保证只对应一个.感兴趣的可以看看
部分代码如下:

//需引入以下类,否则在这些类中访问单例对象会报错class Hero;class Role;class GameLayer;class OperateLayer;class StateLayer;class Enemy;//全局单例class Global :public Singleton<Global>{public:    Global(voID);    ~Global(voID);    //GameScene *gameScene;    GameLayer *gameLayer;           //游戏层    OperateLayer *operateLayer;     // *** 作层    StateLayer * stateLayer;        //状态层    Hero *hero;                     //英雄    __Array *enemIEs;               //敌人    TMXTiledMap *tileMap;           //地图...#define global Global::instance()

在Hero.cpp最上方是

Hero::Hero(voID){    global->hero = this;}

JoyStick的最上方是

Joystick::Joystick():    m_pJoystick(NulL),m_pJoystickBg(NulL){    m_pHero=global->hero;}

我们可以在JoyStick *** 作m_pHero来直接控制global的hero,也就是我们在GameLayer.cpp中创建的Hero
我们只需要在JoyStick.cpp的updateJoystick中添加:

m_pHero->onMove(direction,distance);

ontouchended中添加:

m_pHero->onStop();

编译一下,通过摇杆,我们可以控制角色左右朝向,放开摇杆Hero就可以停止了

不过在这个情况下,角色只会不同的改变方向,播放Run动画,而坐标不会移动.
我们需要在GameLayer中添加updataHero函数

voID GameLayer::updateHero(float dt){    if(m_pHero->getCurrActionState() == ACTION_STATE_WALK)    {        Vec2 currentP= m_pHero->getposition();//当前坐标        Vec2 expectP = currentP + m_pHero->getVeLocity();//期望坐标        Vec2 actualP = expectP;//实际坐标        m_pHero->setposition(actualP);        m_pHero->setLocalZOrder( _visheight - m_pHero->getpositionY());    }}

我们的Hero可以跑动了.

但是又出现问题了,我们的Hero居然可以跑到墙上和天上,还能跑出视界之外.得给hero限定一个范围,不要让他乱跑

重写updateHero

voID GameLayer::updateHero(float dt){    if(m_pHero->getCurrActionState() == ACTION_STATE_WALK)    {        Vec2 currentP= m_pHero->getposition();//当前坐标        Vec2 expectP = currentP + m_pHero->getVeLocity();//期望坐标        Vec2 actualP = expectP;//实际坐标        float mapWIDth = global->tileMap->getContentSize().wIDth;   //整张地图宽度        float herofat = m_pHero->getbodyBox().actual.size.wIDth/2;  //角色横向宽度,以受攻击的bodyBox为准        ////不能跑到墙上去        if(expectP.y<0 || !global->tileAllowMove(expectP))        {            actualP.y =currentP.y;        }        //不能跑出地图外面        if(expectP.x < herofat || expectP.x >= mapWIDth - herofat)        {            //if(!global->tileAllowMove(expectP))            actualP.x = currentP.x;        }        m_pHero->setposition(actualP);        m_pHero->setLocalZOrder( _visheight - m_pHero->getpositionY());    }}

其中global->tileAllowMove(expectP)是:
(本想放在Map中间调用的,想想还是直接扔在Global算了);

Point Global::tilePosFromLocation(Point MovePoint,TMXTiledMap *map) {//将当前坐标切换到地图坐标    Point point = MovePoint - map->getposition();    Point pointGID = Vec2::ZERO;    pointGID.x = (int) (point.x / map->getTileSize().wIDth);     pointGID.y = (int) ((map->getMapSize().height * map->getTileSize().height - point.y) / map->getTileSize().height);     return pointGID; }bool Global::tileAllowMove(Point MovePoint){//判断这个点是在地图的Floor上,可以移动过去    TMXLayer *floor = global->tileMap->getLayer("Floor");    Point tileGID = tilePosFromLocation(MovePoint,global->tileMap);    auto allowpoint =floor->getTileGIDAt(tileGID);    if(0 == allowpoint)    {        return false;    }    return true;}

当然也可以使用其他方法,比如通过getContentSize获得地图大小之类的.

但是问题又来了,我们的Hero还是可以跑到地图右边界外,要怎么做才能始终看到我们的Hero呢?
有2个办法,
第一个是让我们的视角随着Hero移动而移动,
第二个可以让地图随着Hero移动而滚动.

这里我用第一个方法.理解起来比较简单.第二个方法也在源代码的注释中写上了,取消注释可以看看.(方法二不仅仅可以监视X轴,在Y轴可以监视,是相当有效且标准的卷轴滚动算法,受限篇幅,可以自己找资料看看原理)

在GameLayer中加入

voID GameLayer::updateVIEw(float dt) //这个是移动窗口{    float halfWinWIDth = Director::getInstance()->getVisibleSize().wIDth/ 2;    float mapWIDth = global->tileMap->getContentSize().wIDth;    if(m_pHero->getpositionX() > halfWinWIDth && m_pHero->getpositionX() <= (mapWIDth - halfWinWIDth))    {        this->setpositionX(this->getpositionX() - m_pHero->getVeLocity().x);    }}

然后在update中

this->updateVIEw(dt);

Ok,大功告成.我们可以通过摇杆来控制Hero移动,同时镜头也会跟着Hero移动.

下一篇加入敌人,并给敌人一个简单的AI

总结

以上是内存溢出为你收集整理的Cocos3.3横版游戏-Part2-加入主角及控制主角移动全部内容,希望文章能够帮你解决Cocos3.3横版游戏-Part2-加入主角及控制主角移动所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1038823.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-24
下一篇 2022-05-24

发表评论

登录后才能评论

评论列表(0条)

保存