Cocos3.3横版游戏-Part2-加入主角及控制主角移动
接上一篇 Role是基础的角色类,包括了移动,攻击,受到攻击,死亡等等的动画及判断动画. /* 以下是攻击序列动画,需要用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开始的,一次可以将所有的动画读取完.回调函数没法插入,只能在后面加入. 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这两个框了 接下来创建我们的Hero class Hero :public Role
bool 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移动. 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的移动速度. 细心的人应该可以看到工程里面有一个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 m_pHero->onMove(direction,distance);
onTouchEnded中添加: m_pHero->onStop(); 编译一下,通过摇杆,我们可以控制角色左右朝向,放开摇杆Hero就可以停止了 不过在这个情况下,角色只会不同的改变方向,播放Run动画,而坐标不会移动. 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限定一个范围,不要让他乱跑 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)是: 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呢? 这里我用第一个方法.理解起来比较简单.第二个方法也在源代码的注释中写上了,取消注释可以看看.(方法二不仅仅可以监视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 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |