一、前言

前面几节好像与我们一开始说的游戏不太相关,现在我们正式进入战斗场景的开发。

不过凡事不要心急,要循序渐进,我们先搭建一个能够让角色在上面行走的战斗场景吧。

二、正文

首先精简一下Role类,让他能够实现移动功能就好了。

顾名思义,role就是角色,在战斗场景出现的一个一个人都是一个role,废话不多说,直接贴上经过精简过得Role头文件

class Role : public Node{
public:Role();
/*create 之前,请先确认已经将文件添加到ArmatureManager*/static Role* create(const std::string& name,FlightLayer* layer);virtual bool init(const std::string& name,FlightLayer* layer);void setControlable(bool b);virtual Rect getBoundingBox();virtual inline void setArmOffsetX(int x){m_arm_offsetX = x;}virtual inline void setArmOffsetY(int y){m_arm_offsetY = y;}protected:
/*update相关的*/virtual void update(float delta);virtual void update_pos();/*从RoleProtocol中继承下来的方法*/
public:virtual bool onTouchBegan(Touch* touch,Event* event);virtual void onTouchMoved(Touch* touch,Event* event);virtual void onTouchEnded(Touch* touch,Event* event);protected:/*与显示相关的*/bool m_controlable;Armature* m_arm;int m_arm_offsetX;int m_arm_offsetY;bool m_armFaceTo;  //朝向,默认为true,向左public:/*外部调用接口*/virtual inline void setDesPoint(const Point& p){m_desPoint = p;}//IDvirtual inline int getId(){return m_id;}protected:/*与战斗相关的数据*/int m_id;  //idPoint m_desPoint;//目标位置int m_speed; //移动速度int m_initSpeed;};
#endif

经过精简后,Role类就剩下这些函数和变量了,现在的这个Role类只能简单的移动。不要心急,那我们就先实现role的移动吧。

好的,先撇开我们精简过得Role类,先看我们的GameScene

GameScene.h

class GameScene : public Scene{
public:CREATE_FUNC(GameScene);void setHeroTeam(const HeroMessage& h1,const HeroMessage& h2,const HeroMessage& h3);void setMonsterDeq(deque<MonsterMessage> deq);
private:virtual bool init();FlightLayer* layer;
};

GameScene的两个接口setHeroTeam和setMonsterDeq在上一节的选人界面已经使用过了。

void GameScene::setHeroTeam(const HeroMessage& h1,const HeroMessage& h2,const HeroMessage& h3){layer->initTeam(h1,h2,h3);
}void GameScene::setMonsterDeq(deque<MonsterMessage> deq){layer->initMonsterDeq(deq);
}

GameScene也只是简单地将信息传递给layer层罢了。

下面主要研究FlightLayer

class Role;typedef Role** Role_Ptr;
class FlightLayer : public Layer{
friend class Role;
/*外部提供接口*/
public:virtual bool init();CREATE_FUNC(FlightLayer);void addRole(Role* r);std::list<Role_Ptr> getRolesArray(){return m_rolesArray;}void initTeam(const HeroMessage& h1,const HeroMessage& h2,const HeroMessage& h3);void initMonsterDeq(deque<MonsterMessage> deq);/*内部使用函数*/
private:virtual void update(float delta);void initListener();bool comparePosY(Role_Ptr a,Role_Ptr b);/*更新layer中role的叠放次序*/void refreshLocalZOrder();/*保持m_cur_control的正确性*/void updateMyControl();virtual bool onTouchBegan(Touch* touch,Event* event);virtual void onTouchMoved(Touch* touch,Event* event);virtual void onTouchEnded(Touch* touch,Event* event);/*类成员变量*/
private:std::list<Role_Ptr> m_rolesArray;Role* m_cur_control;Role_Ptr m_cur_controlPtr;
};
#endif

当然啦,这个FlightLayer也是被我精简过得。

这里需要注意的是,我们将Role**这个二级指针typedef为Role_Ptr,并不是一个新的类啊,至于为啥要用二级指针(其实改为用智能指针也行),等下我会好好说的。

好的开始好好讲FlightLayer的实现

bool FlightLayer::init(){this->scheduleUpdate();initListener();m_cur_controlPtr = nullptr;m_cur_control = nullptr;Size visibleSize = Director::getInstance()->getVisibleSize();Sprite* BG = Sprite::create("flightBG.jpg");BG->setAnchorPoint(Point(0.5f,0.5f));BG->setPosition(visibleSize.width/2,visibleSize.height/2);this->addChild(BG);return true;
}

init函数除了启动update和调用initListener函数之外没有其他特别的。

void FlightLayer::initListener(){EventListenerTouchOneByOne* touchListener = EventListenerTouchOneByOne::create();touchListener->onTouchBegan = CC_CALLBACK_2(FlightLayer::onTouchBegan,this);touchListener->onTouchMoved = CC_CALLBACK_2(FlightLayer::onTouchMoved,this);touchListener->onTouchEnded = CC_CALLBACK_2(FlightLayer::onTouchEnded,this);Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(touchListener,this);
}

initListener函数也只是为layer添加触摸事件监听器而已。

bool FlightLayer::onTouchBegan(Touch* touch,Event* event){if(m_rolesArray.size() < 1){return false;}for(auto it = m_rolesArray.begin();it!=m_rolesArray.end();++it){if((**it)->onTouchBegan(touch,event)){m_cur_controlPtr = *it;m_cur_control = **it;return true;}}m_cur_controlPtr = nullptr;return false;
}

大致的意思是:当有点击事件发生时,onTouchBegan方法被触发,遍历m_rolesArray,看哪个角色被选中(能够接受触摸事件),如果有角色被选中,就把触摸事件交给那个角色处理。

m_roleArray是一个Role_Ptr(Role**)的list,用于保存现在存活的role

那么为什么要用二级指针呢?

考虑以下问题:假设现在有两个角色,如果用一级指针的话,定义如下:

Role* A = Role::create();

Role* B = Role::create();

好了,现在A要攻击,攻击目标设置为B(所谓攻击目标就是一直向目标走去,要时刻检测目标的当前位置)

如何设置攻击目标?一级指针的话不就是在Role类里面加一个Role*类型的成员变量(Role* attackTarget)代表攻击目标就可以了吗。

好了,前提条件说完了,现在出现这样一种情况,A的攻击目标设置为B(A->attackTarget = B),这时候A会时刻检测自己的attackTarget的位置,并且不断地走过去。

但是,假如这个时刻B被C打死了。。那么这个时候B从场景中清除掉(调用removeFromParent)。那么A怎么知道B已经死了,被清除掉了呢?

聪明的同学可能会想:你判断一下A->attackTarget 是不是等于null不就可以了吗?但是很抱歉,cocos2dx里面,调用玩removeFromParent之后,原来那块中间在这一帧结束之前,并不不会置为null。也就是说B 进行remove后,A->attackTarget不等于null,那么有人又问了,你这里的m_rolesArray数组检测每一个role*(假设m_roleArray里面放入的是Role*而不是Role**),如果死了(Hp==0)就把它赋值为空,不可以吗?这个就是很经典的C语言关于值传递和地址传递的问题。你把m_rolesArray里面的临时变量置为null有什么用?m_rolesArray和A里面的attackTarget指针指向都是同一块空间(B的实际空间),但是两个是完全不同的变量,并不能说你把m_rolesArray的指针置为null,A的attackTarget也会变成null,二者是互不影响的。

好的,讲了那么多,大家可能会不懂。因为我这里也绕了很久。不过最后还是觉得很有必要理清楚,不懂的同学可以看一下《Effective C++》里面介绍的RAII思想。

最后,补充一点是,最好用智能指针代替二级指针,这里我懒得修改了,不好意思哈。

貌似绕开了话题了,我们回到FlightLayer吧。

刚刚讲到有一个m_rolesArray的List负责保存在场景中还存活的角色。那么这个这些角色是什么时候放进去的呢?

void FlightLayer::initTeam(const HeroMessage& h1,const HeroMessage& h2,const HeroMessage& h3){Hero* hero1 = Hero::create(h1.r_name,this);hero1->setPosition(-100,380);hero1->setDesPoint(Point(200,380));hero1->initWithMessage(h1);this->addRole(hero1);Hero* hero2 = Hero::create(h2.r_name,this);hero2->setPosition(-100,260);hero2->setDesPoint(Point(400,260));hero2->initWithMessage(h2);this->addRole(hero2);Hero* hero3 = Hero::create(h3.r_name,this);hero3->setPosition(-100,140);hero3->setDesPoint(Point(200,140));hero3->initWithMessage(h3);this->addRole(hero3);}void FlightLayer::initMonsterDeq(deque<MonsterMessage> deq){this->m_monsterDeq = deq;
}void FlightLayer::addRole(Role* r){//r->setDesPoint(r->getPosition());this->addChild(r);Role_Ptr temp = (Role_Ptr)malloc(sizeof(Role*));*temp = r;m_rolesArray.push_back(temp);
}

对的,就是initTeam和initMonsterDeq这两个接口。这里的addRole是一个基本操作,负责在堆里面开一个区域存放Role的实体。

再回过头来看我们的三个触摸事件触发的回调函数。

bool FlightLayer::onTouchBegan(Touch* touch,Event* event){if(m_rolesArray.size() < 1){return false;}for(auto it = m_rolesArray.begin();it!=m_rolesArray.end();++it){if((**it)->onTouchBegan(touch,event)){m_cur_controlPtr = *it;m_cur_control = **it;return true;}}m_cur_controlPtr = nullptr;return false;
}void FlightLayer::onTouchMoved(Touch* touch,Event* event){if(m_cur_control){m_cur_control->onTouchMoved(touch,event);}else{return;}
}void FlightLayer::onTouchEnded(Touch* touch,Event* event){if(m_cur_control){m_cur_control->onTouchEnded(touch,event);Point tp = Director::getInstance()->convertToGL(touch->getLocationInView());if(!m_cur_control->getBoundingBox().containsPoint(tp)){m_cur_control->setDesPoint(m_cur_control->getEndPoint());}}
}

这三个简化后的函数应该不难看懂。大概流程是这样的

1.onTouchBegan接受到触摸事件之后,检测m_rolesArray,看是否有Role被点中了,如果有m_cur_controlPtr 和 m_cur_control就设置好,然后把触摸事件交给m_cur_control(Role)处理。

2.onTouchMoved,没什么,也是依赖于m_cur_control的处理。

3.onTouchEnded,就是用来设置m_cur_control的终点的。

是不是兜兜转转又回到去了Role类,关于精简过的Role类我们还是标上注释吧。

class Role : public Node{
public:Role();
/*create 之前,请先确认已经将文件添加到ArmatureManager*/static Role* create(const std::string& name,FlightLayer* layer);virtual bool init(const std::string& name,FlightLayer* layer);void setControlable(bool b);<span style="white-space:pre">  </span>//设置是不是可以控制的virtual Rect getBoundingBox();<span style="white-space:pre">  </span>//获取范围virtual inline void setArmOffsetX(int x){m_arm_offsetX = x;}<span style="white-space:pre"> </span>//设置x,y的偏移量virtual inline void setArmOffsetY(int y){m_arm_offsetY = y;}protected:
/*update相关的*/virtual void update(float delta);<span style="white-space:pre">   </span>//update函数virtual void update_pos();public:virtual bool onTouchBegan(Touch* touch,Event* event);virtual void onTouchMoved(Touch* touch,Event* event);virtual void onTouchEnded(Touch* touch,Event* event);protected:/*与显示相关的*/bool m_controlable;<span style="white-space:pre">   </span>//是否可触摸Armature* m_arm;<span style="white-space:pre"> </span>//骨骼动画int m_arm_offsetX;<span style="white-space:pre">    </span>//xy偏移量int m_arm_offsetY;bool m_armFaceTo; //朝向,默认为true,向左public:/*外部调用接口*/virtual inline void setDesPoint(const Point& p){m_desPoint = p;} //设置终点位置//IDvirtual inline int getId(){return m_id;}protected:/*与战斗相关的数据*/int m_id; //idPoint m_desPoint;//目标位置int m_speed; //移动速度int m_initSpeed;};
#endif

其实先跟大家说,Role的行走主要还是依赖于update函数

再解释上面可能大家不太清楚的几个名词

1、xy的偏移量,因为我们用cocostudio做出来的骨骼动画可能范围和描点都不是和我们现在这个Role类(Node)完全重合的。所以可能要或多或少地调整一下骨骼动画的位置。

2、朝向。就是我们的角色是面向哪里的,有左右之分(true/false)

三个触摸回调函数实现在没有讲控制效果之前也就没什么的。除了onTouchBegan要根据传过来的触摸点判断是不是被点中。如果是,就返回true不是就返回false。

负责角色移动的还是要看update函数

</pre>update调用update_pos<pre name="code" class="cpp">void Role::update_pos(){if(m_desPoint.x > this->getPosition().x && m_armFaceTo == true){  m_armFaceTo = false;}if(m_desPoint.x < this->getPosition().x && m_armFaceTo == false){m_armFaceTo = true;}if(m_armFaceTo){m_arm->setVisible(false);m_arm->setPosition(m_arm_offsetX,m_arm_offsetY);m_arm->setScaleX(1.0f);m_arm->setVisible(true);}else{m_arm->setVisible(false);m_arm->setScaleX(-1.0f);m_arm->setPosition(-m_arm_offsetX,m_arm_offsetY);m_arm->setVisible(true);}if(!Rect(m_desPoint.x-m_speed/2,m_desPoint.y-m_speed/2,m_speed,m_speed).containsPoint(getPosition())){this->move();float distance = ccpDistance(getPosition(),m_desPoint);float t = distance / m_speed;float speed_x = (m_desPoint.x - getPositionX()) / t;float speed_y = (m_desPoint.y - getPositionY()) / t;setPositionX(getPositionX() + speed_x);setPositionY(getPositionY() + speed_y);}else{this->stand();}}

讲一讲逻辑就可以了,就是不断更新Role的x和y,直到接近m_desPoint,要转身的时候就转身。

有两个函数 move()和stand() 其实是用来控制骨骼动画的。move负责播放跑步的动画,stand负责播放待机的动画

对了,好像Role的init函数还没有讲

bool Role::init(const std::string& name,FlightLayer* layer){m_layer = layer;if(!m_arm){m_arm = Armature::create(name);m_arm->setPosition(m_arm_offsetX,m_arm_offsetY);setContentSize(m_arm->getContentSize());this->addChild(m_arm,1);m_armFaceTo = true;scheduleUpdate();return true;}else{return false;}
}

很简单吧。。也没什么。

好吧,这节到此结束。

我的csdn地址:http://blog.csdn.net/hezijian22

邮箱地址:578690286@qq.com

如有问题或指教,欢迎与我交流,谢谢。

cocos2dx3.2开发 RPG《Flighting》(五)只能行走的战斗场景相关推荐

  1. 使用 CodeIgniter 框架快速开发 PHP 应用(五)

    原文:使用 CodeIgniter 框架快速开发 PHP 应用(五) 简化 HTML 页面和表格设计 这一章介绍了又一个节约你的时间而且使你的代码更具安全性和逻辑性的领域. 第一,我们将会介绍创建视图 ...

  2. 列表怎么有限的初始化为零_《零基础学习Android开发》第五课 类与面向对象编程1-1...

    视频:<零基础学习Android开发>第五课 类与面向对象编程1-1 类的定义.成员变量.构造方法.成员方法 一.从数据与逻辑相互关系审视代码 通过前面的课程,我们不断接触Java语言的知 ...

  3. 开发指南专题五:JEECG微云快速开发平台代码生成器

    开发指南专题五:JEECG微云快速开发平台代码生成器 1.1. Maven开发环境搭建 在搭建jeecg的maven开发环境之前,需要先配置好本机的maven环境,并在eclipse中安装好m2ecl ...

  4. Android开发笔记(五十三)远程接口调用AIDL

    AIDL概述 AIDL全称是"Android Interface Definition Language",即Android的接口定义语言.AIDL用来协助开发者来处理进程间通信, ...

  5. Android开发笔记(五十二)通知推送Notification

    PendingIntent 准备工作复习一下PendingIntent,前面的博文< Android开发笔记(五十)定时器AlarmManager>已经提到了它.PendingIntent ...

  6. 使用Unity开发RPG游戏完整指南(全)

    使用Unity开发RPG游戏完整指南(全) - GameRes游资网 关注公众号 风色年代(itfantasycc) 200G Unity资料合集送上~ 本教程教大家如何使用Unity创建一个RPG游 ...

  7. 20189200余超 2018-2019-2 移动平台应用开发实践第五作业

    20189200余超 2018-2019-2 移动平台应用开发实践第五作业 输入/输出 输入输出可以说是计算机的基本功能.作为一种语言体系,java中主要按照流(stream)的模式来实现.其中数据的 ...

  8. 游戏开发入门(五)游戏动画系统

    视频链接:游戏开发入门(五)游戏动画系统(8节课 时常:约2小时30分钟) PPT比视频要块一些 第二节与PPT对不上 视频中杂音太大,如果带耳机还听不清的话,建议直接看内容即可 笔记与总结(请先学习 ...

  9. 【SQL Server】数据库开发指南(五)T-SQL 高级查询综合应用与实战

    本系列博文还在更新中,收录在专栏:#MS-SQL Server 专栏中. 本系列文章列表如下: [SQL Server] Linux 运维下对 SQL Server 进行安装.升级.回滚.卸载操作 [ ...

最新文章

  1. 3月最新!2021中国内地大学ESI排名出炉:333所高校入榜全球前1%
  2. 「AI初识境」被Hinton,DeepMind和斯坦福嫌弃的池化到底是什么?
  3. 【NLP】PET——文本分类的又一种妙解
  4. wxWidgets:wxNavigationEnabled< W >类模板的用法
  5. 业务库负载翻了百倍,我做了什么来拯救MySQL架构
  6. pytorch dataset_【小白学PyTorch】16.TF2读取图片的方法
  7. Docker学习总结(48)——Docker 四种网络模式温故
  8. 5 shell命令之tr
  9. 监控服务器说明文档,监控服务器说明文档
  10. hdu 二分图最大匹配问题 (hdu 1083)
  11. DSO(Direct Sparse Odometry)
  12. 【程序猿助手】Emacs,最强的编辑器,之间的不
  13. react18 学习(一)
  14. #10015. 「一本通 1.2 练习 2」扩散 二分+并查集或Floyd
  15. 王之泰201771010131《面向对象程序设计(java)》第十五周学习总结
  16. javax.crypto.BadPaddingException: Given final block not properly padded
  17. testflight怎么做版本更新_TestFlight 使用指南
  18. 苹果摆脱对中国制造的依赖?iPhone14的拆解结果显示恰恰相反,更离不开中国制造了...
  19. 如何做好一个中小型企业计算机网络管理员
  20. file_get_contents failed to open stream: HTTP request failed! HTTP/1.1 400 Bad Request

热门文章

  1. mysql查询数学成绩信息_【MySQL】:利用DQL查询表中的数据
  2. MACD背离指标公式
  3. 编辑器 · Babel 中文文档
  4. Android OpenCV(四十五):图像修复
  5. [综][PDPTW]A survey on pickup and delivery problems
  6. “数字资产”这个词可能是误导 | 正本清源谈区块链
  7. Visual Age for Java_VisualAge for Java使用技巧
  8. 困兽之斗--乐视2017暑期实习生笔试题(二)
  9. OneNote桌面版与UWP版避免自动切换字体的方案
  10. mysql _外键、实体关系与ER图