Cocos2d-x v3.6制作射箭游戏(二)
原文
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方法的参数为触摸点的位置。
- 获取角色弓箭在游戏场景中位置;
- 计算弓箭的旋转角度。
这里利用三角正切函数来计算,原理如下图所示:
vector(offX,offY) 是触摸点到弓箭之间的向量,通过 getAngle 方法,我们可以得到 vector 向量与X轴之间的弧度。
再者,我们需要把弧度 rotateRadians 转化为角度,CC_RADIANS_TO_DEGREES
就是能把弧度转化为角度的宏。转化时乘 -1 是因为Cocos2d-x中规定顺时针方向为正,这与我们计算出的角度方向相反,所以转化的时候需要把角度a变为-a。 - 控制旋转角度的范围,即只让它在角色右半边内旋转。
- 计算弓箭旋转时间。
speed表示炮塔旋转的速度,0.5 / M_PI其实就是 1 / 2PI,它表示1秒钟旋转1个圆。
rotateDuration表示旋转特定的角度需要的时间,计算它用弧度乘以速度。 - 让弓箭执行旋转动作。
触摸响应
好了,现在 Player 就初步定义好了。接下来,我们回到游戏场景把Player加入进去,并来测试下弓箭是否跟随触摸点旋转。
在 Cocos2d-x 3.x 引擎中,实现触摸响应的流程基本是一致的。所以在 3.6 中,其过程依旧是:
- 重载触摸回调函数;
- 创建并绑定触摸事件;
- 实现触摸回调函数。
所以我们要测试弓箭是否跟随触摸点旋转,第一步请先在 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制作射箭游戏(二)的更多相关文章
- [Mugeda HTML5技术教程之14]案例分析:制作网页游戏
本文档要分析的案例是一个爱消除的网页小游戏,从中可以体会一些Mugeda API的用法和使用Mugeda动画制作网页游戏的方法. (一)游戏规则: 1.开始游戏时,手机出现在最上面一行的任意一格: 2 ...
- cocos2dx 制作单机麻将(二)
cocos2dx 制作单机麻将(二) 打乱麻将顺序2 前面解说了怎样打乱初始给定的麻将牌堆, 另一种是打乱随意给定的麻将牌堆 //混乱扑克2 void RandAppointCardData(BYTE ...
- Cocos2D中Action的进阶使用技巧(二)
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 上回说到解决办法是使用CCTargetedAction类. C ...
- 使用CocosSharp制作一个游戏 - CocosSharp中文教程
注:本教程翻译自官方<Walkthrough - Building a game with CocosSharp>,官方教程有很多地方说的不够详细,或者代码不全,导致无法继续,本人在看了G ...
- 计蒜客-跳跃游戏二 (简单dp)
题目链接:https://nanti.jisuanke.com/t/20 跳跃游戏二 给定一个非负整数数组,假定你的初始 ...
- hiho一下 第四十五周 博弈游戏·Nim游戏·二 [ 博弈 ]
传送门 题目1 : 博弈游戏·Nim游戏·二 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 Alice和Bob这一次准备玩一个关于硬币的游戏:N枚硬币排成一列,有的正面 ...
- Unreal Engine 4 系列教程 Part 5:制作简单游戏
.katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...
- 教你使用Python制作酷炫二维码
这篇文章讲的是如何利用python制作狂拽酷炫吊炸天的二维码,非常有趣哦! 可能你见过的二维码大多长这样: 普普通通,平平凡凡,没什么特色... 但,如果二维码长这样呢! 或者 这样! 是不是炒鸡好看 ...
- Spine应用--使用Spine动画制作动作游戏
在前面的文章中,已经陆陆续续的讲解了一些使用Spine动画的细节,有了这些细节,我们已经满足了在unity中使用Spine动画制作动作游戏的技术基础. 那么,要使用Spine动画在unity中制作一款 ...
随机推荐
- 40. Interleaving String
Interleaving String Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. Fo ...
- EL简介
一.EL简介 1.语法结构 ${expression}2.[]与.运算符 EL 提供.和[]两种运算符来存取数据. 当要存取的属性名称中包含一些特殊字符,如.或?等并非字母或数字的符 ...
- Android软件测试Monkey测试工具
前言: 最近开始研究Android自动化测试方法,对其中的一些工具.方法和框架做了一些简单的整理,其中包括android测试框架.CTS.Monkey.Monkeyrunner.benchmark.其 ...
- 查看/修改 Linux 时间和时区
查看/修改Linux时区和时间 一.时区 1. 查看当前时区 date -R 2. 修改设置时区 方法(1) ...
- android menu的问题
1.简单使用 @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.me ...
- js动态改变时间
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...
- Allegro学习(http://www.asmyword.com/forum.php?mod=forumdisplay&fid=86)
一.资源 1.网站推荐www.eda365.com,里面有很多有用的东西:当然还有官方代理商的网站http://www.pspice.com.cn/: 2.视频教程:有库源电气的视频教程,还有在www ...
- VS2008中调试dll
1.运行dll实例时,会直接弹出一个小框: 选择可拉起这个dll的exe运行就可以调试了 2.以后每次都会直接运行了,要重新选择程序,弹出上面的框,需要在project-->debugging- ...
- SVM2---核函数的引入
前边总结了线性SVM,最终转化为一个QP问题来求解.后来又考虑到非线性SVM,如果特征特别特别多的话,直接使用QP的话求解不了,我们经过一系列的转化,把这一问题转化为训练集大小n量级的QP问题. ht ...
- windows 系统下 Firefox hostadmin插件无法修改Host
firefox hostAdmin插件无法修改Host了,提示“ write hosts file failed check permissions”,肯定是权限出现了问题??? 使用管理员权限打开c ...