原文

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

Cocos2d-x v3.6制作射箭游戏(二)的更多相关文章

  1. [Mugeda HTML5技术教程之14]案例分析:制作网页游戏

    本文档要分析的案例是一个爱消除的网页小游戏,从中可以体会一些Mugeda API的用法和使用Mugeda动画制作网页游戏的方法. (一)游戏规则: 1.开始游戏时,手机出现在最上面一行的任意一格: 2 ...

  2. cocos2dx 制作单机麻将(二)

    cocos2dx 制作单机麻将(二) 打乱麻将顺序2 前面解说了怎样打乱初始给定的麻将牌堆, 另一种是打乱随意给定的麻将牌堆 //混乱扑克2 void RandAppointCardData(BYTE ...

  3. Cocos2D中Action的进阶使用技巧(二)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 上回说到解决办法是使用CCTargetedAction类. C ...

  4. 使用CocosSharp制作一个游戏 - CocosSharp中文教程

    注:本教程翻译自官方<Walkthrough - Building a game with CocosSharp>,官方教程有很多地方说的不够详细,或者代码不全,导致无法继续,本人在看了G ...

  5. 计蒜客-跳跃游戏二 (简单dp)

    题目链接:https://nanti.jisuanke.com/t/20                                         跳跃游戏二 给定一个非负整数数组,假定你的初始 ...

  6. hiho一下 第四十五周 博弈游戏·Nim游戏·二 [ 博弈 ]

    传送门 题目1 : 博弈游戏·Nim游戏·二 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 Alice和Bob这一次准备玩一个关于硬币的游戏:N枚硬币排成一列,有的正面 ...

  7. Unreal Engine 4 系列教程 Part 5:制作简单游戏

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  8. 教你使用Python制作酷炫二维码

    这篇文章讲的是如何利用python制作狂拽酷炫吊炸天的二维码,非常有趣哦! 可能你见过的二维码大多长这样: 普普通通,平平凡凡,没什么特色... 但,如果二维码长这样呢! 或者 这样! 是不是炒鸡好看 ...

  9. Spine应用--使用Spine动画制作动作游戏

    在前面的文章中,已经陆陆续续的讲解了一些使用Spine动画的细节,有了这些细节,我们已经满足了在unity中使用Spine动画制作动作游戏的技术基础. 那么,要使用Spine动画在unity中制作一款 ...

随机推荐

  1. 40. Interleaving String

    Interleaving String Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. Fo ...

  2. EL简介

    一.EL简介 1.语法结构    ${expression}2.[]与.运算符    EL 提供.和[]两种运算符来存取数据.    当要存取的属性名称中包含一些特殊字符,如.或?等并非字母或数字的符 ...

  3. Android软件测试Monkey测试工具

    前言: 最近开始研究Android自动化测试方法,对其中的一些工具.方法和框架做了一些简单的整理,其中包括android测试框架.CTS.Monkey.Monkeyrunner.benchmark.其 ...

  4. 查看/修改 Linux 时间和时区

    查看/修改Linux时区和时间 一.时区      1. 查看当前时区                 date -R      2. 修改设置时区          方法(1)            ...

  5. android menu的问题

    1.简单使用 @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.me ...

  6. js动态改变时间

    <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...

  7. Allegro学习(http://www.asmyword.com/forum.php?mod=forumdisplay&fid=86)

    一.资源 1.网站推荐www.eda365.com,里面有很多有用的东西:当然还有官方代理商的网站http://www.pspice.com.cn/: 2.视频教程:有库源电气的视频教程,还有在www ...

  8. VS2008中调试dll

    1.运行dll实例时,会直接弹出一个小框: 选择可拉起这个dll的exe运行就可以调试了 2.以后每次都会直接运行了,要重新选择程序,弹出上面的框,需要在project-->debugging- ...

  9. SVM2---核函数的引入

    前边总结了线性SVM,最终转化为一个QP问题来求解.后来又考虑到非线性SVM,如果特征特别特别多的话,直接使用QP的话求解不了,我们经过一系列的转化,把这一问题转化为训练集大小n量级的QP问题. ht ...

  10. windows 系统下 Firefox hostadmin插件无法修改Host

    firefox hostAdmin插件无法修改Host了,提示“ write hosts file failed check permissions”,肯定是权限出现了问题??? 使用管理员权限打开c ...