(14)如何使用Cocos2d-x 3.0制作基于tilemap的游戏:第二部分
引言
程序截图:
这篇教程是《如何使用Cocos2d-x 3.0制作基于tilemap的游戏》的第二部分。在上一个教程中,我们创建了一个简单的基于tiled地图的游戏,里面有一个忍者在沙漠里寻找可口的西瓜!
在第一部分教程中,我们介绍了如何基于tiled创建地图,怎样把地图增加到游戏中去,以及如何滚动地图来跟随主角移动、还有如何使用对象层。
在这部分教程中,我们将会介绍如何在地图中制作可以碰撞的区域,如何使用tile属性,如果收集游戏物品并且动态地修改地图、如何确保你的忍者不会吃得太饱!
因此,让我们继续我们上篇教程所学并且让它更像一个真实的游戏吧!
tiled地图和碰撞检测
你可能已经注意到了,目前我们的忍者可以毫无阻拦地穿过墙壁和障碍物。他是一个忍者,但是即使是真正的忍者,他也没这么厉害啊!
因此,我们需要找到一种方法,通过把一些tile标记成“可碰撞的”,这样的话,我们就可以防止玩家穿过那些点的位置。有很多方法可以做得到(包括使用对象层),但是,我想向你们展示一种新的技术。我认为它更高效,并且也是一次好的学习锻炼--使用一个元层(meta layer)和层属性。
让我们开始动手吧!再一次启动Tiled软件,点击“LayerAdd tile Lyaer...”,并且命名为“Meta”,然后选择OK。我们将在这个层里面加入一些假的tile代表一些“特殊tile”。
因此,现在我们需要增加我们的特殊tile。点击“MapNew tileset...”,在你的Resources文件夹下面找到mate_tiles.png,然后选择打开。设置Margin和Spacing都为1并点击OK。
这时,你可以在Tilesets区域看到一个新的标签。打开它,而且你会看到2个tile:一个红色的和一个绿色的。
这些tile并没有什么特殊的东西--我只是制作了一个简单的图片,里面包含了一个红色的和一个绿色的半透明tile。接下来,我们把红色的tile当作是“可碰撞的”(后面我们会用到绿色的),然后,合适地绘制我们的场景。
因此,确保Meta层被选中,选择"Stamp Brush"工具(菜单上面那个像图章一样的工具),选择红色的tile,然后把任何你不想让忍者通过的地图都涂一遍。当你做完的时候,应该看起来像下面的图示一样:
接下来,我们可以设置tile的属性,这样的话,我们在代码中就可以识别这个tile是“可以碰撞的(穿不过去的)”。在Tileset里面的红色tile上在,右击,选择“Properties...“。增加一个新的属性,叫做”Collidable“,并且设置成”Ture“:
保存map,并返回编译器。在HelloWorldScene.h中做如下改动:
// Inside the HelloWorld class declaration
cocos2d::TMXLayer *_meta;
同时修改HelloWorldScene.cpp文件如下:
// In init, right after loading background
_meta = _tileMap->getLayer("Meta");
_meta->setVisible(false); // Add new method
Point HelloWorld::tileCoordForPosition(Point position)
{
int x = position.x / _tileMap->getTileSize().width;
int y = ((_tileMap->getMapSize().height * _tileMap->getTileSize().height) - position.y) / _tileMap->getTileSize().height;
return Point(x, y);
}
好了,让我们先停一会儿。像之前一样,我会meta层声明了一个成员变量,而且从tile map中加载了一个引用。注意,我们把这个字当作是不可见的,因为我们并不想看见这些对象,它们的存在只是为了说明,那个区域是可以碰撞的。
接下来,我们增加一个新的帮助方法,这个方法可以帮助我们把x,y坐标转换成”tile坐标“。每一个tile都有一个坐标,从左上角的(0,0)开始,到右下角的(49,49)。(本例中,地图的大小是49×49)
上面的截屏是java版本的tiled界面。能否显示tile的坐标,我不确定这个功能在QT版本的tiled中是否存在(编者表示qt版没有,所以上面的直接引用原教程图)。不管怎么说,我们将要使用的一些功能会使用tile坐标,而不是x,y坐标。因此,我们需要一种方式,将x,y坐标转换成tile坐标。这正是那个函数所需要做的。
获得x坐标非常容易--我们只需要让它除以一个tile的宽度就可以了。为了得到y坐标,我们不得不翻转一些东西,因为,在Cocos2d-x里面(0,0)是在左下角的,而不是在左上角。
接下来,把setPlayerPosition替换成以下内容:
void HelloWorld::setPlayerPosition(Point position)
{
Point tileCoord = this->tileCoordForPosition(position);
int tileGid = _meta->getTileGIDAt(tileCoord);
if (tileGid) {
auto properties = _tileMap->getPropertiesForGID(tileGid).asValueMap();
if (!properties.empty()) {
auto collision = properties["Collidable"].asString();
if ("True" == collision) {
return;
}
}
} _player->setPosition(position);
}
在这里,我们把玩家的x,y坐标转换成tile坐标。然后,我们使用meta层中的getTileGIDAt方法来获取指定位置点的GID号。
对了,什么是GID呢?GID代表”全球唯一标志符“(我个人意见)。但是,在这个例子中,我认为它只是我们使用的tile的一种标识,它可以是我们想要移动的红色区域。
当我们使用GID来查找指定tile的属性的时候。它返回一个属性字典,因此,我们可以遍历一下,看是否有”可碰撞的“物体被设置成”True“,或者是仅仅就是那样。编译并运行工程,因此还没有设置玩家的位置。
就这么多!编译并运行程序,它将会向你展示,现在你不能够通过那些红色的tile组成的地方了吧:
动态修改Tiled Map
目前为此,我们的忍者已经有一个比较有意思的冒险啦,但是,这个世界有一点点无趣。而且简单无任务事可做!加上,我们的忍者看起来比较贪吃,而且背景将会随着玩家移动而移动。因此,让我们创建一些东西让忍者来玩吧!
为了使之可行,我将不得不创建一个前景层,这样做可以让用户收集东西。那样的话,我们仅仅从前景层中删除不用的tile(当tile被玩角拾取的时候),这个过程中,背景将会随之移动。
因此,打开Tiled,选择”LayerAdd Tile Layer...“,把这个层命名为”Foreground“,然后选择OK。确保前景层被选择,而且增加一对可以拾取的物品在游戏中。我喜欢放置一些向西瓜或者别的什么东西。
现在,我们需要把这些tile标记成可以拾取的,类似的,参照我们是如何把tile标志成可以碰撞的。选择Meta层,转换到Meta_tiles。现在,我们需要使这些tile可以拾取,点击”LayerMove Layer Up“来确保你的meta层是在最顶层,并且保持绿色可见的。
接下来,我们需要为tile增加属性,这样把它标记成可拾取的。点键点击Tilesets选项卡里的绿色的tile,然后点“Properties...”,再增加一个新的属性,命名为“Collectable”,值设置为“True”。
保存地图,然后返回到编译器。在HelloWorldScene.h中做如下修改:
// Inside the HelloWorld class declaration
cocos2d::TMXLayer *_foreground;
同时,相应地修改HelloWorldScene.cpp:
// In init, right after loading background
_foreground = _tileMap->getLayer("Foreground"); // Add to setPlayerPosition, right after the if clause with the return in it
auto collectable = properties["Collectable"].asString();
if ("True" == collectable) {
_meta->removeTileAt(tileCoord);
_foreground->removeTileAt(tileCoord); this->_numCollected++;
this->_hud->numCollectedChanged(_numCollected);
}
这里是一个常用的方法,用来保存前景层的句柄。不同之处在于,我们测试玩家正朝之移动的tile是否含有“Collectable”属性。如果有,我们就使用removeTileAt方法来把tile从mata层和前景层中移除掉。编译并运行工程,现在你的忍者可以尝尝西瓜的滋味啦!
创建一个计分器
我们忍者非常高兴地吃西瓜啦,但是,作为一个游戏玩家,我们想知道自己到底吃了多少个西瓜。你懂的,我们并不想让他吃得太胖。
通常的做法是,我们在层上面添加一个label。但是,等一下:我们在不停地移动这个层,那样的话,label就会看不到了,怎么办?
这是一个非常好的机会,如果在一个场景中使用多个层--这正是我们现在面临的难题。我们将保留HelloWorld层,但是,我们会再增加一个HelloWorldHud层来显示我们的label。(Hud意味着Heads up display,大家可以google一下,游戏中常用的技术)
当然,这两个层之间需要一种方式联系起来--Hud层应该知道什么时候忍者吃了一个西瓜。有许许多多的方式可以使2个不同的层相互通信,但是,我只介绍最简单的。我们在HelloWorld层里面保存一个HelloWorldHud层的句柄,这样的话,当忍者吃了一个西瓜就可以调用Hud层的一个方法来进行通知。
因此,在HelloWorldScene.h里面增加下面的代码:
// Before HelloWorld class declaration
class HelloWorldHud : public cocos2d::Layer
{
public:
void numCollectedChanged(int numCollected);
virtual bool init();
CREATE_FUNC(HelloWorldHud); cocos2d::LabelTTF *label;
}; // Inside HelloWorld class declaration
int _numCollected;
static HelloWorldHud *_hud;
同样的,修改HelloWorldScene.cpp文件:
// At top of file
HelloWorldHud *HelloWorld::_hud = NULL; // implement some methods of HelloWorldHud
bool HelloWorldHud::init()
{
if (!Layer::init())
{
return false;
} auto visibleSize = Director::getInstance()->getVisibleSize();
label = LabelTTF::create("", "fonts/Marker Felt.ttf", 18.0f, Size(, ), TextHAlignment::RIGHT);
label->setColor(Color3B(, , ));
int margin = ;
label->setPosition(visibleSize.width - (label->getDimensions().width / ) - margin,
label->getDimensions().height / + margin);
this->addChild(label); return true;
} void HelloWorldHud::numCollectedChanged(int numCollected)
{
char showStr[];
sprintf(showStr, "%d", numCollected);
label->setString(showStr);
} // Add to the HelloWorld::createScene() method, right before the return
auto hud = HelloWorldHud::create();
_hud = hud; scene->addChild(hud); // Add inside setPlayerPosition, in the case where a tile is collectable
this->_numCollected++;
this->_hud->numCollectedChanged(_numCollected);
一切很明了。我们的第二个层从Layer派生,只是在它的右下角加了一个label。我们修改scene把第二个层也添加进去,然后传递一个Hud类的引用给HelloWorld层。然后修改HelloWorldLayer层,当计数器改变的时候,就调用Hud类的方法,这样就可以相应地更新Hud类了。
编译并运行,如果一切ok,你将会在屏幕右下角看到统计忍者吃西瓜的Label。
来点音效和音乐
如果没有很cool的音效和背景音乐的话,这就不能算作是一个完整的游戏教程了。
增加音效和音乐非常简单,只需在HelloWolrdScene.cpp作如下修改:
// At top of file
#include "SimpleAudioEngine.h"
using namespace CocosDenshion; // At top of init for HelloWorld layer
SimpleAudioEngine::getInstance()->preloadEffect("pickup.mp3");
SimpleAudioEngine::getInstance()->preloadEffect("hit.mp3");
SimpleAudioEngine::getInstance()->preloadEffect("move.mp3");
SimpleAudioEngine::getInstance()->playBackgroundMusic("TileMap.mp3", true); // In case for collidable tile
SimpleAudioEngine::getInstance()->playEffect("hit.mp3"); // In case of collectable tile
SimpleAudioEngine::getInstance()->playEffect("pickup.mp3"); // Right before setting player position
SimpleAudioEngine::getInstance()->playEffect("move.mp3");
现在,我们的忍者可以开怀大吃了!
何去何从?
通过这个教程的学习,你对Cocos2d里面的tiled map的使用,应该有一个非常好的理解了。
这里有这个教程的完整源代码:TileGame2.zip
接下来,是原作者的一个朋友写的,这个系列教程的终结版:《如何使用Cocos2d-x3.0制作基于tilemap的游戏:第三部分·完》————加入敌人和战斗。
(14)如何使用Cocos2d-x 3.0制作基于tilemap的游戏:第二部分的更多相关文章
- (15)如何使用Cocos2d-x 3.0制作基于tilemap的游戏:第三部分(完)
引言 程序截图: 在第二部分教程中,Ray教大家如何在地图中制作可碰撞的区域,如何使用tile属性,如何制作可以拾取的物品以及如何动态修改地图.如何使用“Heads up display”来显示分数. ...
- (13)如何使用Cocos2d-x 3.0制作基于tilemap的游戏:第一部分
引言 程序截图: 本教程将会教大家如何使用Cocos2d-x来做一个基于tile地图的游戏,当然还有Tiled地图编辑器.(我们小时候玩的小霸王小学机里面的游戏,大部分都是基于tile地图的游戏,如坦 ...
- cocos2d-x3.0创建第一个jsb游戏
第一步: 最新的cocos2d-x.下载地址https://github.com/cocos2d/cocos2d-x github上最新的引擎,值得注意的是官网上发布的引擎是稳定版.选择哪种就看个人喜 ...
- Cocos2D将v1.0的tileMap游戏转换到v3.4中一例(一)
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 首先说一下为什么要转换,这是为了后面的A*寻路算法做准备.由于在 ...
- 【原创】VB6.0应用程序安装包的生成(Setup Factory 9.0制作安装包的方法)
VB6.0应用程序安装包的生成,利用其自带的打包工具生成的安装程序很简陋,一点不美观:如果想让自己的应用程序安装的时候显得高大上一点,本教程提供使用Setup Factory 9.0制作安装包的方法. ...
- Windows PE3.0制作方法(从Win7中提取制作)
Windows PE3.0制作方法(从Win7中提取制作 在d:新建文件夹winpe,在winpe中新建sources.pe3和new文件夹,把附件中提供的工具imagex连文件夹一起放到winpe目 ...
- 使用Busybox-1.2.0制作根文件系统
使用Busybox-1.2.0制作根文件系统 cross-3.3.2 make-3.8.1 STEP 1: 创建根文件系统目录,主要包括以下目录/bin,/etc,/dev,/mnt,/sbin,/u ...
- 高屋建瓴 cocos2d-x-3.0架构设计 Cocos2d (v.3.0) rendering pipeline roadmap(原文)
Cocos2d (v.3.0) rendering pipeline roadmap Why (the vision) The way currently Cocos2d does rendering ...
- Windows 下VC++6.0制作、使用动态库和静态库
Windows 下VC++6.0制作.使用动态库和静态库 一.VC++6.0制作.使用静态库 静态库制作 1.如图一在VC++6.0中new一个的为win32 static library工程并新建一 ...
随机推荐
- Java精选笔记_IO流(转换流、常用流、流操作规律、字符编码)
IO流 用来处理设备之间的数据传输,java对数据的操作是通过流的方式,java用于操作流的对象都在IO包中 按操作数据分为:字节流和字符流:按流向分为:输入流和输出流. 程序从输入流中读取数据,向输 ...
- 九度 1534:数组中第K小的数字(二分法变形)
题目描述: 给定两个整型数组A和B.我们将A和B中的元素两两相加可以得到数组C.譬如A为[1,2],B为[3,4].那么由A和B中的元素两两相加得到的数组C为[4,5,5,6].现在给你数组A和B,求 ...
- Android中Invalidate与postInvalidate的区别<转>
http://www.cnblogs.com/it-tomorrow/archive/2012/11/08/2760146.html 示例:http://rayleung.iteye.com/blog ...
- Linux 虚拟终端:screen
screen 是一个虚拟终端,我们可以把执行时间很长的命令放在虚拟终端中执行,即使终端断开,这个虚拟终端也会在后台执行 [root@localhost ~]$ yum install -y scree ...
- Python 文件类型
Python的文件类型分为以下几种: 1. 源代码文件,也就是以 .py 为扩展名的文件,由 python 程序解释,不需要编译 2. 字节代码文件,python 源代码文件经过编译后生成的扩展名为 ...
- cocos2dx游戏--欢欢英雄传说--添加攻击按钮
接下来添加攻击按钮用于执行攻击动作.同时修复了上一版移动时的bug.修复后的Player::walkTo()函数: void Player::walkTo(Vec2 dest) { if (_seq) ...
- Hacking up an armv7s library
NOTE: Please take care with this. I obviously cannot test if this will actually work on a new iPhone ...
- Android 生成分享长图并且添加全图水印
转载自 : http://blog.csdn.net/gengqiquan/article/details/65938021 领导最近觉得携程的截屏生成长图分享效果比较好,所以我们也加了个:产品觉得分 ...
- Linux命令学习之xargs命令
xargs命令是给其他命令传递参数的一个过滤器,也是组合多个命令的一个工具.它擅长将标准输入数据转换成命令行参数,xargs能够处理管道或者stdin并将其转换成特定命令的命令参数.xargs也可以将 ...
- 使用springBoot进行快速开发
springBoot项目是spring的一个子项目,使用约定由于配置的思想省去了以往在开发过程中许多的配置工作(其实使用springBoot并不是零配置,只是使用了注解完全省去了XML文件的配置),达 ...