原文

Cocos2d-x v3.6制作射箭游戏(二)

六 24, 2015by RENSHANin COCOS2D-X

上章我们创建并加载了游戏地图,接下来的两章我们将实现如下的效果。

在开始之前,先给大家道个歉,因为前几周忙,没有时间写教程,所以迟迟都没更新,让有些童鞋久等了,见谅哦!!

本章我们的主要任务是创建射箭的弓箭手(也就是游戏猪脚),并且让这个猪脚随着触摸点的改变不断的旋转手中的弓箭。

分析:

对于这个射箭的角色而言,它能不停的射出弓箭。当我们按住屏幕上某点时,会从该角色拿弓箭的手的位置“画”一条标注箭支运动轨迹的红线(看似抛物线);当在屏幕上滑动手指或鼠标时,这条红线会随着触摸点的位置不停的变换轨迹;当松开屏幕上的手指或鼠标时,会射出一支弓箭,这支弓箭会按最终的红线路径移动。另外,玩家手中的弓箭会随着屏幕上的手指或鼠标旋转。

Player 类

下面我们一起来创建这个 Player 猪脚类,其初步定义如下:

class Player: public Sprite { public: Player(); bool init(Vec2 playerPos); static Player* create(Vec2 playerPos); void createPlayer(); void createPlayerHpBar(); void rotateArrow(Point touchPoint); void createAndShootArrow( Point touchPoint); void shootArrow(); void finishRunAction(); void update(float dt); CC_SYNTHESIZE(int, playerHp, PlayerHp); // 玩家血量值 CC_SYNTHESIZE(bool, startDraw, StartDraw); // 是否开始画红色的路径线 CC_SYNTHESIZE(bool, isRunAction, IsRunAction); // 玩家是否正在执行射箭动画 private: Vec2 playerPos; // 角色在 tmx 地图上的位置 Size playerSize; // 角色尺寸 Size winSize; // 屏幕窗口尺寸 Sprite* playerbody; // 角色身体 Sprite* playerarrow; // 角色的弓箭,也就是会随触摸点旋转的弓和箭部分 Sprite* hPBgSprite; // 角色血条背景精灵 ProgressTimer* hpBar; // 角色血条 ccQuadBezierConfig bezier; // 路径贝赛尔 DrawNode* drawNode; // 这里表示我们的线条对象 };

以上的各方法都是我们这两章需要实现的,其他更多的方法我们将在后面需要的时候再扩充。
其中CC_SYNTHESIZE宏的作用是定义一个保护型的变量,并声明一个getfunName函数和setfunName函数,你可以用getfunName函数得到变量的值,用setfunName函数设置变量得值。如:CC_SYNTHESIZE(int, playerHp, PlayerHp);定义了一个整型的 playerHp 变量,同时还声明了 getPlayerHp() 和 setPlayerHp() 两个方法。
ccQuadBezierConfig是我们新定义的一个结构体,后面我们会详细的讲解。

下面我们就从上到下依次来看看以上的各方法。

创建角色

首先是 Player 的初始化(init)和创建(create),这里我们通过给定 Player 的位置来创建该角色,而这个传入的坐标位置应该是我们从 TiledMap 的对象层中读取到的位置(上章有讲)。具体代码如下:

Player * Player::create(Vec2 playerPos) { Player *pRet = new Player(); if (pRet && pRet->init(playerPos)) { pRet->autorelease(); return pRet; }else { delete pRet; pRet = NULL; return NULL; } } bool Player::init(Vec2 playerPos) { if (!Sprite::init()) { return false; } this->playerPos = playerPos; createPlayer(); // 创建角色 createPlayerHpBar(); // 创建角色血量条 scheduleUpdate(); return true; }

下面我们接着来看看 createPlayer 方法,该方法将初始化我们的 Player 角色,代码如下所示:

void Player::createPlayer() { playerbody = Sprite::createWithSpriteFrameName("playerbody.png"); playerSize = Size(playerbody->getContentSize().width/2, playerbody->getContentSize().height / 3*2); // 设置Player的尺寸,大小略小于playerbody的尺寸,这样利于我们后面更准确的进行碰撞设置。 playerbody->setAnchorPoint(Vec2(0.7f, 0.4f)); this->addChild(playerbody); this->setPosition(Vec2(playerPos.x+ GameManager::getInstance()->getObjectPosOffX(), playerPos.y + playerSize.height * 0.4f)); playerarrow = Sprite::createWithSpriteFrameName("playerarrow.png"); playerarrow->setPosition(Vec2(0, 0)); playerarrow->setAnchorPoint(Vec2(0.3f, 0.5f)); this->addChild(playerarrow); }

createPlayer 方法中我们将创建如下所示的一个游戏角色。

因为没有找到合适的游戏资源(原游戏中得到的资源都是零件,要使用需要把它们一帧一帧重组),所以我们的游戏一切从简,不整那些复杂的。
这里我们只把角色简单分成了两个部分,第一部分当然是玩家的身体playerbody,第二部分是随着触摸点/鼠标旋转的手和弓箭playerarrow。(PS:当然因为资源限制这个原因,可能会稍稍降低咱游戏的档次,应该不能怪我啰!O(∩_∩)O~)

设置playerbody位置时,你可能已经发现,我们并没有把角色身体设置在传入的playerPos处,而是对它稍微做了一定的调整。这是因为我们传入的位置它是紧贴本格瓦片底部的(我们制作tmx文件时,需要这样做。上章没说清楚,这章补起,要记住哦!)。如下图所示:

Y值坐标也不可太接近本格瓦片底部,也就是不要设为9.990,9.998这类太接近10的,因为 tmx 文件中存放的坐标值是整数,如果设为9.990,9.998,那么存放的值会是9.990 X 32 = 319.68 = 320,同理 9.998 X 32 也是 320。320 对于瓦片大小是32 X 32的地图来说是个特殊的数字,因为 320 /32 = 10。这样在程序中就会误以为9.990,9.998之类的点是坐标上的第10个点。

而且上章我们也说过,由于分辨率适配的原因,对象组中对象的位置与实际的位置是有一定的偏差的,所以我们在设置角色身体位置时,需要修正这些偏差。
以上代码中设置位置的原理图如下:

其中,对象组在 X 轴上的偏移值我们把它保存在了 GameManager 中,而 GameManager 是个单例类,后面章节我们会详细的讲解。当然如果你现在就想运行代码,那就先把GameManager::getInstance()->getObjectPosOffX()部分去掉吧。

创建好角色后,接下来我们需要创建角色的血量条,血量条可通过 Cocos2d-x 中封装好的进度条类 ProgressTimer 来创建。其代码段如下:

void Player::createPlayerHpBar() { // 创建血条底,即进度条的底背景  hPBgSprite = Sprite::createWithSpriteFrameName("hpbg.png"); hPBgSprite->setPosition(Vec2(playerbody->getContentSize().width / 2, playerbody->getContentSize().height)); playerbody->addChild(hPBgSprite); // 创建血条  hpBar = ProgressTimer::create(Sprite::createWithSpriteFrameName("hp1.png")); hpBar->setType(ProgressTimer::Type::BAR); // 设置进度条样式(条形或环形) hpBar->setMidpoint(Vec2(0, 0.5f)); // 设置进度条的起始点,(0,y)表示最左边,(1,y)表示最右边,(x,1)表示最上面,(x,0)表示最下面。 hpBar->setBarChangeRate(Vec2(1, 0)); // 设置进度条变化方向,(1,0)表示横方向,(0,1)表示纵方向。 hpBar->setPercentage(100); // 设置当前进度条的进度 hpBar->setPosition(Vec2(hPBgSprite->getContentSize().width / 2, hPBgSprite->getContentSize().height / 2 )); hPBgSprite->addChild(hpBar); hPBgSprite->setVisible(false); // 设置整个血条不可见,我们将在Player 遭受攻击的时候再显示血条。 }

旋转角色弓箭

接下来我们来让 Player 的弓箭部分跟随着触摸点/鼠标旋转。所以我们定义了如下的函数:

void Player::rotateArrow(Point touchPoint) { // 1  auto playerPos = this->getPosition(); auto pos = playerPos + playerarrow->getPosition(); // 2 Point vector = touchPoint - pos; auto rotateRadians = vector.getAngle(); auto rotateDegrees = CC_RADIANS_TO_DEGREES( -1 * rotateRadians); // 3 if (rotateDegrees >= -180 && rotateDegrees <= -90){ rotateDegrees = -90; } else if (rotateDegrees >= 90 && rotateDegrees <= 180){ rotateDegrees = 90; } // 4 auto speed = 0.5 / M_PI; auto rotateDuration = fabs(rotateRadians * speed); // 5 playerarrow->runAction( RotateTo::create(rotateDuration, rotateDegrees)); }

rotateArrow方法的参数为触摸点的位置。

  1. 获取角色弓箭在游戏场景中位置;
  2. 计算弓箭的旋转角度。
    这里利用三角正切函数来计算,原理如下图所示:

    vector(offX,offY) 是触摸点到弓箭之间的向量,通过 getAngle 方法,我们可以得到 vector 向量与X轴之间的弧度。
    再者,我们需要把弧度 rotateRadians 转化为角度,CC_RADIANS_TO_DEGREES就是能把弧度转化为角度的宏。转化时乘 -1 是因为Cocos2d-x中规定顺时针方向为正,这与我们计算出的角度方向相反,所以转化的时候需要把角度a变为-a。
  3. 控制旋转角度的范围,即只让它在角色右半边内旋转。
  4. 计算弓箭旋转时间。
    speed表示炮塔旋转的速度,0.5 / M_PI其实就是 1 / 2PI,它表示1秒钟旋转1个圆。
    rotateDuration表示旋转特定的角度需要的时间,计算它用弧度乘以速度。
  5. 让弓箭执行旋转动作。

触摸响应

好了,现在 Player 就初步定义好了。接下来,我们回到游戏场景把Player加入进去,并来测试下弓箭是否跟随触摸点旋转。

在 Cocos2d-x 3.x 引擎中,实现触摸响应的流程基本是一致的。所以在 3.6 中,其过程依旧是:

  1. 重载触摸回调函数;
  2. 创建并绑定触摸事件;
  3. 实现触摸回调函数。

所以我们要测试弓箭是否跟随触摸点旋转,第一步请先在 GameScene 中重写如下的触摸回调函数,并声明变量:

    virtual bool onTouchBegan(Touch *touch, Event *unused_event); // 开始触摸屏幕时响应 virtual void onTouchMoved(Touch *touch, Event *unused_event); // 触摸屏幕并在屏幕上滑动时响应 virtual void onTouchEnded(Touch *touch, Event *unused_event); // 触摸结束时响应 private: Point preTouchPoint; // 上一个触摸点 Point currTouchPoint; // 当前触摸点

接着,我们需要在 GameScene 的 init 初始化函数中创建并绑定触摸事件,并先随便创建一个 Player 对象,用于测试。如下:

SpriteFrameCache::getInstance()->addSpriteFramesWithFile("texture.plist", "texture.pvr.ccz"); player = Player::create(Vec2(winSize.width / 4, winSize.height/5)); this->addChild(player); // 获取事件分发器 auto dispatcher = Director::getInstance()->getEventDispatcher(); // 创建单点触摸监听器 auto listener = EventListenerTouchOneByOne::create(); // 让监听器绑定事件处理函数 listener->onTouchBegan = CC_CALLBACK_2(GameScene::onTouchBegan,this); listener->onTouchMoved = CC_CALLBACK_2(GameScene::onTouchMoved,this); listener->onTouchEnded = CC_CALLBACK_2(GameScene::onTouchEnded,this); // 将事件监听器添加到事件调度器 dispatcher->addEventListenerWithSceneGraphPriority(listener,this);

Player 的位置是固定的,我们当然不能随便设,这里只是为了测试。后面的章节中我们会创建一个类来专门管理从 TiledMap 中得到的对象,包括Player、敌人、道具,砖块等。

以上 plist 和 pvr.ccz文件是我们的打包资源,它们是用 Texturepacker 编辑器打包而来。更多详细内容请点此查看。

绑定好触摸事件后,最后我们需要实现它们,代码如下:

bool GameScene::onTouchBegan(Touch *touch, Event *unused_event) { currTouchPoint = touch->getLocation(); if( !currTouchPoint.equals(preTouchPoint)){ player->rotateArrow(currTouchPoint); } preTouchPoint = currTouchPoint; return true; } void GameScene::onTouchMoved(Touch *touch, Event *unused_event) { currTouchPoint = touch->getLocation(); if( !currTouchPoint.equals(preTouchPoint)){ player->rotateArrow(currTouchPoint); } preTouchPoint = currTouchPoint; } void GameScene::onTouchEnded(Touch *touch, Event *unused_event) { // 射箭,下章内容 }

在 onTouchBegan 和 onTouchMoved 函数中,处理方法是一样的。即当当前触摸点与之前的触摸点不一致时,就旋转 Player 的弓箭。
getLocation 方法将 touch 对象中保存的屏幕坐标转换成我们需要的 Cocos2d 坐标。 分不清屏幕坐标和Cocos2d 坐标的童鞋请参考Cocos2d-x 3.0坐标系详解一文。

当触摸结束时,Player 对象需要射出弓箭,这个我们暂时不写。

运行游戏,此时你就可以看到想要的效果了。关于本章资源,请点此下载。

今天的教程就到这里,下章我们将讲些有意思的,敬请期待!

另外,最近发现很多越俎代庖的开发者,所以这里打个版权,装下B。===》原创教程,转载请注明出至:http://shannn.com/archives/515

转载于:https://www.cnblogs.com/cocox/p/4607033.html

Cocos2d-x v3.6制作射箭游戏(二)相关推荐

  1. 使用SpringBoot及Construct2的WebSocket制作联机游戏(二)

    前情概要: 使用SpringBoot及Construct2的WebSocket制作联机游戏(一) 一.介绍: 1.SpringBoot服务端添加登录及注册接口,并进行Postman测试 2.Sprin ...

  2. python scratch unity_Unity3D研究院之2D游戏开发制作原理(二十一)

    经过了4个月不懈的努力,我和图灵教育合作的这本3D游戏开发书预计下个月就要出版了.这里MOMO先打一下广告,图灵的出版社编辑成员都非常给力,尤其是编辑小花为这本书付出了很大的努力,还有杨海玲老师,不然 ...

  3. 用JAVA制作小游戏——推箱子(二)

    本篇博客主要是推箱子游戏界面功能的代码讲解. 首先先给出这段代码的部分运行截图: 重难点: 游戏界面主要有五个重难点: 固定好地图的位置 地图的显示 构建菜单栏 读取地图数据 玩家操作功能实现 地图的 ...

  4. Python 制作迷宫游戏(二)——游戏窗口

    Python 制作迷宫游戏(二)--游戏窗口 上一节我们使用prime做了迷宫的底层数组,它的形式是一个二维数组. 这一节我们着手开始制作游戏窗口 使用模块 pygame 在这项迷宫游戏的制作当中需要 ...

  5. 如何用UE4制作2D游戏文档(二)——资源篇

    一.前言 首先感谢B站马克镇镇长的视频讲解清晰把很多细节都照顾到了,我本来自己做的时候没有视频说的那么细,参考他的全套系列视频之后完善了很多内容. 视频指路:https://www.bilibili. ...

  6. 单机游戏制作系列之二——基本框架

    单机游戏制作系列之二--基本框架 笔者个人的想法,是打算将这个系列的文章写成通用性的,不局限于某一种语言,也不局限于某一种引擎,但是水平有限,预计是达不到这个效果.以下仅以C++来举例,如果其他的语言 ...

  7. python游戏制作软件_python制作小游戏(二)

    下载W3Cschool手机App,0基础随时随地学编程导语 T_T突然发现N久以前我还做过一个系列??? 利用Python制作小游戏??? 好吧,既然做了,就有头有尾吧~~~ 本期我们将制作一个类似八 ...

  8. UE4无尽跑酷游戏制作杂记之二

    UE4无尽跑酷游戏制作杂记之二 在做游戏的过程中,我先后犯了两个很愚蠢的错误,这个在我观看官方的无尽的奔跑者之后我意识到了这个错误. 首先在我原来的设计当中,我使用了一个单例类,或者说一个管理者类(继 ...

  9. 【python】python制作 连连看 游戏脚本(二)

    [python]python制作 连连看 游戏脚本(一)_sunriver2000的博客-CSDN博客 [python]python制作 连连看 游戏脚本(二)_sunriver2000的博客-CSD ...

最新文章

  1. R使用lm构建多变量线性回归模型
  2. 文巾解题 1035. 不相交的线
  3. Vue.js-Day04-AM【父子组件通信(父传子、子传父)、动态组件、组件的生命周期、动画】
  4. c++ 获取linux系统信息_linux系统c程序移植
  5. CORS解决WebApi跨域问题(转)
  6. leetcode 738. 单调递增的数字(贪心算法)
  7. CS229 6.18 CNN 的反向传导算法
  8. 做过CC1020的苦逼前人告诉你(分拿来)
  9. 微信App支付全解析
  10. C# 设置Windows程序窗口为穿透状态
  11. eyoucms 网页制作软件有哪些 这些你都知道吗
  12. C/C++ abs 函数 - C语言零基础入门教程
  13. 设计方案,写了才知道有多香
  14. STA | 12. 时序签核方法学及实战经验
  15. 系统清理维护与安全防护软件
  16. MIT 18.01 Single Variable Calculus(单变量微积分)课堂笔记【3】——求导四则运算和三角函数求导
  17. 华硕ROG|玩家国度冰刃6双屏GX650RX Windows11原厂预装系统 工厂模式恢复安装带ASUSRecevory一键还原
  18. 小米手机扩容教程_手把手教你把手机16G内存升级128G 【图文教程】
  19. Windows服务器搭建Node-Media-Server视频服务器
  20. 核心微生物分析_科学网—微生物组核心OTU鉴定usearch otutab_core - 刘永鑫的博文...

热门文章

  1. Web开发到微信小程序
  2. 集成科大讯飞语音听写功能
  3. 彩虹图纸管理系统提升工作效率30%
  4. python用于pmc排产可以吗_PMC-你最想要的实用干货来了!
  5. 计算机会计总结,会计电算化教学总结
  6. 计算机环境下会计循环的八个流程,会计工作的八大基本流程
  7. skyeye模拟uboot启动linux(initrd方式)
  8. 山东地图echarts js文件
  9. tinyId 基于 zookeeper  的 递增分布式id框架(非趋向性递增)
  10. mysql半同步 rpo_PostgreSQL 双节点流复制如何同时保证可用性、可靠性(rpo,rto) - (半同步,自动降级方法实践)-阿里云开发者社区...