怎样制作基于cocos2d x lua-x的SLG游戏

怎样制作基于Cocos2d-x的SLG游戏-第6章 | 泰然网
108 次浏览 |
文章目录原创:
前篇教程中我们已经创建好了播种界面,而本章我们将继续对单独的瓦片进行操作,完成在土地上播种的功能,同时我们将添加其他类似的界面来操作瓦片,如:清理界面(可以将树木、房屋等等从地图中清除),收获界面(收获成熟的农作物)等等。
再次说明,由于模拟经营类游戏的操作多样性,本系列教程将不会尽善尽美的实现整个SLG游戏的所有功能,这里只选取其中有代表的一些功能来实现,有兴趣的同学欢迎在本游戏的基础上进行扩展。本游戏最终将要实现的功能是这样的:
点击商品项和其他障碍物所在层(这里统称第2层)上的瓦片,将出现一个清理界面,该界面上有一把叉子,玩家点击叉子可将点击的瓦片从地图中移除,不过点击土块系瓦片除外。
点击空土块瓦片将出现上节中创建的播种界面,玩家选取播种层中的农作物选项,就可以在土块上栽种相应的种子。
种子种下去的同时,该处瓦片上将会生成一个计时面板,用于纪录种子多久后成熟。如果该处瓦片再次处于未选中状态,那这个计时面板将会隐藏起来,直到种子完全成熟后该处的计时界面才会被销毁。
种子成熟后,瓦片上的农作物将会变为相应的成熟作物。如果这时选中这些成熟的作物,那么将出现一个收获界面。收取作物后瓦片又将变为什么都没有的空土块。
以上就是对单独瓦片进行操作的全部功能,后面我们将会一一实现。
已经创建好播种界面,所以我们接下来首先来实现播种这项功能。
1、 根据游戏功能需求,定义如下的一个枚举类型来标示玩家所点击的瓦片类型:
typedef enum // 定义瓦片类型。如果需要扩展,可这这里定义更多类型。
GROUD = 1,
GROUD_CROP = 2,
CROP_HARVEST,
2、 接着在玩家开始触碰屏幕的地方(既onTouchesBegan函数中)判断所触碰瓦片的类型。如下,在onTouchesBegan函数的if (gid != 0)中添加以下一段代码:
switch (gid)
case 9: // 触碰到的是空土块
tileType = TileType::GROUD;
// 触碰到的是种了农作物幼苗的土块
tileType = TileType::GROUD_CROP;
// 触碰到种植有成熟农作物的土块
tileType = TileType::CROP_HARVEST;
// 其他情况,也就是可以被清除的其他瓦片类型
tileType = TileType::OTHER;
以上代码中的gid对应了下图中的瓦片项。
3、 当玩家松开手指的时候,创建播种界面。
在onTouchesEnded函数的if(press)中添加如下的代码:
if(tileType == GROUD)
auto panel = SeedChooseLayer::create();
panel-&setPosition(screenPos);
this-&addChild(panel, 10, SEEDPANEL_TAG);
其中SEEDPANEL_TAG是播种界面的标示,我们可以用它来判断播种界面是否存在,是否移除该界面等操作。其他界面也同样有着自己的标示,如下所示:
#define SEEDPANEL_TAG
#define HARVESTPANEL_TAG
#define REMOVEPANEL_TAG
4、 创建播种界面后,我们就该播种了。
不过首先请打开文件,我们来整理下瓦片层:在最底层的地图层上只放置草地瓦片,在草地层之上的层用来放置障碍物和商品项,最上层置空,用来设置提醒项。同时,修改之前的图层名字,规范代码逻辑(不然以后自己都看不懂自己写的什么),如下图所示:
打开游戏主场景GameScene,添加update函数,并实现它。如下所示:
void GameScene::update(float dt)
// 通过标示得到场景中的子节点
auto seedPanel = this-&getChildByTag(SEEDPANEL_TAG);
// 判断瓦片是否为空土块,并且是否已经创建好了播种界面
if(tileType == GROUD && seedPanel!= NULL)
// 得到选择的农作物类型
auto type = ((SeedChooseLayer*)seedPanel)-&getCurrType();
// 根据农作物类型种植相应的农作物
switch (type)
case WHEAT:
map-&getLayer(&goodsLayer&)-&setTileGID(18, touchObjectPos);
this-&removeChild(seedPanel);
case CORN:
map-&getLayer(&goodsLayer&)-&setTileGID(20, touchObjectPos);
this-&removeChild(seedPanel);
case CARROT:
map-&getLayer(&goodsLayer&)-&setTileGID(22, touchObjectPos);
this-&removeChild(seedPanel);
不要忘了在init函数中加上scheduleUpdate(),这样才会每一帧都调用update()。
5、 当玩家触摸到屏幕其他地方时,一定要把已有的播种界面清除,以确保场景中最多只有一个播种界面。所以回到onTouchesBegan函数中,清除已有播种界面。
auto seedPanel = this-&getChildByTag(SEEDPANEL_TAG);
if(seedPanel){
this-&removeChild(seedPanel);
现在运行我们的代码,就可以像下图一样在土地上播种庄稼了。
接下来我们创建一个清理界面,用来移除除土块以外的其他瓦片。它同播种界面原理一样,其定义如下:
class RemoveLayer: public Layer
virtual bool init()
bool onTouchBegan(Touch *touch, Event *event);
CREATE_FUNC(RemoveLayer);
CC_SYNTHESIZE(bool, remove, Remove);
该界面只有一个叉子供玩家选择,所以我们用布尔类型的变量来标示玩家是否选中它。其事件回调函数代码如下:
bool RemoveLayer::onTouchBegan(Touch *touch, Event *event)
auto target = static_cast&Sprite*&(event-&getCurrentTarget());
Point locationInNode = target-&convertTouchToNodeSpace(touch);
Size size = target-&getContentSize();
Rect rect = Rect(0, 0, size.width, size.height);
if (rect.containsPoint(locationInNode))
target-&setOpacity(180);
// 判断是否选中叉子
if (target == fork)
实现了清理界面后,按照创建和销毁播种界面的思路把清理界面添加到游戏场景中,既在onTouchesEnded中创建清理面板,在onTouchesBegan中销毁该清理面板,并在update函数中检测更新:
if(NULL != removePanel && tileType == OTHER)
if(((RemoveLayer*)removePanel)-&getRemove() == true)
map-&getLayer(&goodsLayer&)-&removeTileAt(touchObjectPos);
this-&removeChild(removePanel);
这样一来,我们就可以移除地图中的看不顺眼的瓦片了。
运行游戏,当前效果如下图所示:
此时点击长有幼苗的土块,将会出现一个记录庄稼成熟时间的面板出现,当时间到时,庄稼将会成熟。这个时候如果再点击庄稼,玩家就可以所获它了。
由于计时面板的创建有别于其他小界面的创建,所以这里我们先跳过这一步,先来创建收获面板。
收获界面同清理界面基本是一致的,它将会在庄稼成熟的时候被创建,其定义如下:
class HarvestLayer: public Layer
virtual bool init()
bool onTouchBegan(Touch *touch, Event *event);
CREATE_FUNC(HarvestLayer);
CC_SYNTHESIZE(bool, harvest, Harvest);
Sprite* harvestS
变量harvest用来标示玩家是否选中收获项,HarvestLayer的实现很简单,这里就不赘述了。
本章所用资源已更新,可进行下载,文章中的源代码将在本系列教程结束时上传,敬请期待。
没有个人说明
2 2 1 1 1 1 1 1 1 1
4342 3406 3202 3185 2912 2737 2559 2360 2287 2074
22 杰鹏的梦
18 泰然处之1)">1)">1" ng-class="{current:{{currentPage==page}}}" ng-repeat="page in pages"><li class='page' ng-if="(endIndex<li class='page next' ng-if="(currentPage
相关文章阅读怎样制作基于Cocos2d-x的SLG游戏-第5章 | 泰然网
132 次浏览 |
文章目录原创:
上一章中,我们实现了商品的拖动操作,把商店的商品拖动到了地图上,但本章将实现拖动商品的校对检测,同时将实现瓦片的触摸控制(移动、销毁),播种、除草、收获。
前面我们拖动商品到什么地方,它就跟着移动到什么地方,如下图网格里的示意图所示。但一般情况下,为了更加形象的体现出这一特性,一般都不能让待入列的瓦片那些肆掠的移动,所以接下来我们将实现拖动商品项的细节调整。
优化拖动操作
首先,先在头文件中设置两个变量用于记录鼠标/手指当前和在此之前的移动坐标(这个坐标指地图坐标)。
Vec2 currP
接着修改moveCheck函数,如下代码所示:
void GameScene::moveCheck(Vec2 position, int tag)
auto mapSize = map-&getMapSize();
auto tilePos = this-&convertTotileCoord(position);
canBliud =
perPos = currP
if( tilePos.x &= 0 && tilePos.x &= mapSize.width - 1 && tilePos.y &= 0 && tilePos.y &= mapSize.height - 1)
currPos = tileP
int gid = map-&getLayer(&2&)-&getTileGIDAt(tilePos);
if (gid == 0){
buyTarget-&setTexture(move_textures[tag]);
canBliud =
buyTarget-&setTexture(move_textures_en[tag]);
canBliud =
auto screenPos = this-&convertToScreenCoord(tilePos);
buyTarget-&setPosition(screenPos);
if(perPos != currPos){
map-&getLayer(&3&)-&removeTileAt(perPos);
map-&getLayer(&3&)-&setTileGID(17, currPos);
buyTarget-&setPosition(position);
buyTarget-&setTexture(move_textures_en[tag]);
map-&getLayer(&3&)-&removeTileAt(perPos);
canBliud =
给之前坐标赋值,让它等于当前坐标.
把当前地图坐标转换为屏幕坐标,同时设置商品项的位置到这个屏幕坐标上。这里convertToScreenCoord函数将完成这一转换。这样一来,screenPos坐标就将会是固定的一些值。注意:不要忘了删掉SpriteCallback函数中设置商品项位置的函数段。
鼠标/手指移动到哪里,就在那里的瓦片上设置一个可以标识它的记号,同时当移动到别的地方,要删除之前位置上的记号。
当tilePos超出地图范围时,让商品项跟着鼠标/手指的移动而移动,同时移除perPos位置上的记号。
convertToScreenCoord函数中的数学公式其实就是convertTotileCoord函数中数学原理的一个反推公式,其代码如下:
Vec2 GameScene::convertToScreenCoord(Vec2 position)
auto mapSize = map-&getMapSize();
auto tileSize = map-&getTileSize();
auto tileWidth = map-&getBoundingBox().size.width / map-&getMapSize().
auto tileHeight = map-&getBoundingBox().size.height / map-&getMapSize().
auto variable1 = (position.x + mapSize.width / 2 - mapSize.height) * tileWidth * tileH
auto variable2 = (-position.y + mapSize.width / 2 + mapSize.height) * tileWidth * tileH
int posx = (variable1 + variable2) / 2 / tileH
int posy = (variable2 - variable1) / 2 / tileW
return Point(posx, posy);
最后还缺什么啦,我们来用手指想一下。对的,一般当拖动商品项到一个“不空”的瓦片上或地图之外的区域时,将会提示此处不可放。所以,接下来我们的工作来加这段提示的代码吧。
在SpriteCallback方法的if( canBliud == true ){}函数段后加上如下代码:
// 得到放手时鼠标/手指的屏幕坐标,这个坐标是相对于地图的。所以计算它时应该要考虑到地图的移动和缩放。
auto endPos =Vec2((widget-&getTouchEndPos().x - bgOrigin.x)/bgSprite-&getScale(), (widget-&getTouchEndPos().y - bgOrigin.y)/bgSprite-&getScale());
// 把上面得到的屏幕坐标转换围地图坐标
auto coord = convertTotileCoord( endPos);
// 再把地图坐标转换为固定的一些屏幕坐标
auto screenPos = this-&convertToScreenCoord(coord);
// 创建提醒项,把它设置在screenPos处
auto tips = Sprite::create(&tip.png&);
tips-&setPosition(screenPos);
bgSprite-&addChild(tips);
// 让提醒项出现一段时间后移除它
tips-&runAction(Sequence::create(DelayTime::create(0.1f),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, tips)),
运行你的游戏,你会发现它的效果如下图所示:
对单独的瓦片进行操作
商品项的拖动效果已经近乎完美,接下来是该对单独的瓦片进行操作了。经营模式游戏中,当玩家触碰到地图瓦片时,将可以进行相应的操作,比如:触碰到树木将可以砍掉它;触碰到土地将可以播撒小麦、土豆、玉米等农作物,如果上成熟状态可以收获它们;触碰到建筑物时将可以修整,拆除它等等。如果玩家长按某个瓦片,我们还能移动它。
反正在这类游戏中对瓦片的操作是非常繁琐的,接下来我们就来一步一步的攻克它吧。最终的目标是做一个类似下图(《全面农场》)的效果。
接下来我们需要先判断玩家的触碰是长按还是短按,然后在考虑其他的问题。
判断短按长按
Cocos2d-x中是没有长按事件的触发机制的,所以如果想实现长按的检测,需要自己实现。本教程中通过schedule来实现长按操作,其原理如下:
当玩家触碰到屏幕上的瓦片(除了草地,既的最底层)时,开始计时。如果在计时过程中发生了触摸移动或者异常中断了触摸响应,那么就取消本次计时。在计时过程中,如果达到预定时间,那么则执行相应的函数。最后在结束本次触摸时(抬起鼠标/手指),结束计时。
根据以上阐述的原理,我们需要一个变量来判断是否继续计时,所以请先在头文件中定义如下的变量:
Vec2 touchObjectP
其中touchObjectPos由于记录触摸到的瓦片的地图坐标。
接下来找到onTouchesBegan函数,在函数中添加如下的一段代码:
if(touches.size() == 1)
auto touch = touches[0];
auto screenPos = touch-&getLocation();
auto mapSize = map-&getMapSize();
pos.x = (screenPos.x - bgOrigin.x)/bgSprite-&getScale();
pos.y = (screenPos.y - bgOrigin.y)/bgSprite-&getScale();
auto tilePos = this-&convertTotileCoord(pos);
if( tilePos.x &= 0 && tilePos.x &= mapSize.width - 1 && tilePos.y &= 0 && tilePos.y &= mapSize.height - 1){
int gid = map-&getLayer(&2&)-&getTileGIDAt(tilePos);
// 如果瓦片地图的&2&层上tilePos处存在其他瓦片,则执行以下代码。
if (gid != 0)
touchObjectPos = tileP
map-&getLayer(&3&)-&setTileGID(17, tilePos);
this-&schedule(schedule_selector(GameScene::updatePress), 2);
// longPress在开始按下的时候为true,如果移动,取消,则为false,在抬起的时候如果变量为true,那么执行schedule中的updatePress函数
schedule的原理我就不废话了,不清楚的同学可以查看官方文档一文。updatePress函数如下:
void GameScene::updatePress(float t)
// 取消计时
this-&unschedule(schedule_selector(GameScene::updatePress));
log(&是长按&);
map-&getLayer(&3&)-&removeTileAt(touchObjectPos);
接下来在onTouchesMoved函数中添加如下代码:
map-&getLayer(&3&)-&removeTileAt(touchObjectPos);
this-&unschedule(schedule_selector(GameScene::updatePress));
最后添加一个用于出来结束触摸响应的onTouchesEnded事件函数,其代码如下所示:
void GameScene::onTouchesEnded(const std::vector&Touch*&&touches, Event
this-&unschedule(schedule_selector(GameScene::updatePress));
Size winSize = Director::getInstance()-&getWinSize();
if(touches.size() == 1)
auto touch = touches[0];
auto screenPos = touch-&getLocation();
log(&是短按。此处应该创建相应的操作面板了&);
map-&getLayer(&3&)-&removeTileAt(touchObjectPos);
创建操作面板
对于一个模拟经营类游戏来说,操作面板之多,所以本教程就不大动干戈挨个的创建了,象征性的例举几个例子就行,毕竟这只是一篇教程,而不是商业项目,所以感兴趣的同学请跟着教程思路自己去拓展一下。
下面以播种面板为例,创建一个播种面板SeedChooseLayer,它继承于Layer,其定义如下所示:
// 定义农作物类型
typedef enum
WHEAT = 0,
class SeedChooseLayer: public Layer
virtual bool init()
// 重载触摸回调函数
bool onTouchBegan(Touch *touch, Event *event);
CC_SYNTHESIZE(CropsType, currType, CurrType);// 选中的作物类型
CREATE_FUNC(SeedChooseLayer);
对去播种面板层的触摸响应,这里和地图的响应略有不同,它是单点触摸事件,所以以上重载的是用于出来单点事件的onTouchBegan函数(触摸点击开始事件)。
接下来,我们来实现SeedChooseLayer的各个方法。如下是init的实现。
bool SeedChooseLayer::init()
if (!Layer::init())
currType = CropsType::NOTHING;
auto bgSprite = Sprite::create(&chooseBg.png&);
bgSprite-&setAnchorPoint(Vec2(1, 0));
this-&addChild(bgSprite);
wheat = Sprite::create(&corn.png&);
wheat-&setAnchorPoint( Point(0, 0));
wheat-&setPosition(Point(0, 0));
bgSprite-&addChild(wheat);
corn = Sprite::create(&wheat.png&);
corn-&etPosition(Point(bgSprite-&getContentSize().width/2, bgSprite-&getContentSize().height/2));
bgSprite-&addChild(corn);
carrot = Sprite::create(&carrot.png&);
carrot-&setAnchorPoint( Point(1, 1));
carrot-&setPosition(Point(bgSprite-&getContentSize().width, bgSprite-&getContentSize().height));
bgSprite-&addChild(carrot);
// 创建事件监听器,OneByOne表示单点
auto touchListener = EventListenerTouchOneByOne::create();
// 设置是否向下传递触摸,true表示不向下触摸。
touchListener-&setSwallowTouches(true);
touchListener-&onTouchBegan = CC_CALLBACK_2(SeedChooseLayer::onTouchBegan, this);
_eventDispatcher-&addEventListenerWithSceneGraphPriority(touchListener, wheat);
_eventDispatcher-&addEventListenerWithSceneGraphPriority(touchListener-&clone(), corn);
_eventDispatcher-&addEventListenerWithSceneGraphPriority(touchListener-&clone(), carrot);
在init函数中,我们初始化了播种面板中种子的布局,并且创建绑定了触摸事件。这里要注意:在面板中,我们设置了三个农作物选项供玩家选择,每一个选项都要处理触摸响应事件。所以,绑定需要绑定到每个选项上。然而当我们再次使用事件监听器的时候,需要使用 clone() 方法重新克隆一个,因为每个监听器在添加到事件调度器中时,都会为其添加一个已注册的标记,这就使得它不能够被添加多次。
绑定好触摸事件后,接下来需要实现具体的触摸回调。在onTouchBegan函数中我们要做的是:当玩家触碰到某选项时,重新设置其透明度,向玩家表明该项是被选中的;并且确定其选择的作物类型,方便我们后面代码的获取和使用。其代码如下图所示:
bool SeedChooseLayer::onTouchBegan(Touch *touch, Event *event)
auto target = static_cast&Sprite*&(event-&getCurrentTarget());
Point locationInNode = target-&convertTouchToNodeSpace(touch);
Size size = target-&getContentSize();
Rect rect = Rect(0, 0, size.width, size.height);
if (rect.containsPoint(locationInNode))
target-&setOpacity(180);
if (target == wheat)
currType = CropsType::WHEAT;
}else if(target == corn)
currType = CropsType::CORN;
}else if(target == carrot)
currType = CropsType::CARROT;
currType = CropsType::NOTHING;
返回触摸事件当前作用的目标节点。
把touch对象中保存的屏幕坐标转换到GL坐标,再转换到目标节点的本地坐标下。
在Node对象中有几个函数可以做坐标转换。convertToNodeSpace方法可以把世界坐标转换到当前node的本地坐标系中;convertToWorldSpace方法可以把基于当前node的本地坐标系下的坐标转换到世界坐标系中;convertTouchToNodeSpace这个函数可以把屏幕坐标系转换到GL坐标系,再转换到父节点的本地坐标下。
计算目标节点的矩形区域。
判断触碰点在不在目标节点的矩形区域内,即判断是否被选中。
根据选择的目标确定作物的类型。
以上我们就已经实现了播种界面的创建了,最后在onTouchesEnded函数中判断为短按的地方加上如下代码就可以实现播种界面的显示了。
panel = SeedChooseLayer::create();
panel-&setPosition(screenPos);
this-&addChild(panel, 10, SEEDPANEL_TAG);
效果如下图所示:
本章资源已上传,可进行下载,文章中的源代码将在本系列教程结束时上传,敬请期待。
没有个人说明
2 2 1 1 1 1 1 1 1 1
4342 3406 3202 3185 2912 2737 2559 2360 2287 2074
22 杰鹏的梦
18 泰然处之

我要回帖

更多关于 cocos2d x游戏开发 的文章

 

随机推荐