终于可以接着往下写了,这两天查找错误可算把我害惨了,下面会用红字详细说明是什么地方出了问题。
好了,我们再往游戏里添加一个NPC对象。我们先把npc.png图片复制到我们得项目中。这里我们要用到Tiled编辑器里面的添加对象层功能。在图层面板上右键选择“添加对象层”,重命名为“object”。选中object图层,在工具栏中选择插入对象
,在地图的(4,10)处放置一个对象,宽和高皆为1。选中视图菜单中的对齐网格选项,可以在绘制对象时自动对齐。地图中的灰色方框就是NPC对象。
接下来,选择工具栏中的“选择对象按钮”,在NPC对象上点击鼠标右键,选择对象属性就会弹出一个对话框,可以在里面配置键值对,按下图进行配置:image表示该对象对应的图片纹理,rectX和rectY分别表示在纹理中的起始x坐标和y坐标。
到这里,地图的绘制工作基本完成了,保存文件,开始编写代码。首先,我们用一个NPC类来保存上述配置的信息。NPC类的头文件如下:
#ifndef __NPC_H__
#define __NPC_H__
#include "MTGame.h"
using namespace cocos2d;
class NPC:public CCObject
{
public:
//构造函数中要根据传递的属性表初始化各个变量
NPC(CCDictionary* dict,int x,int y);
~NPC(void);
//用于显示NPC的精灵
CCSprite* npcSprite;
//保存在TileMap中配置的name项
CCString* npcId;
//npc所在的TileMap坐标
CCPoint tileCoord;
//图片纹理的文件路径
CCString* imagePath;
//纹理的rect
CCRect rect;
//对应配置中的type项
CCString* type;
};
#endif
NPC类的具体实现方法如下:
#include "NPC.h"
NPC::NPC(CCDictionary* dict,int x,int y)
{
//获取名称
std::string key="name";
npcId=(CCString*)dict->objectForKey(key);
//获取类型
key="type";
type=(CCString*)dict->objectForKey(key);
//获取image项
key="image";
imagePath=(CCString*)dict->objectForKey(key);
//获取rectX和rectY
key="rectX";
int x1=((CCString*)dict->objectForKey(key))->intValue();
key="rectY";
int y1=((CCString*)dict->objectForKey(key))->intValue();
rect=CCRectMake(x1,y1,32,32);
//position为cocos2d-x坐标,tileCoord为TileMap坐标
CCPoint position=ccp(x,y);
tileCoord=sGlobal->gameMap->tileCoordForPosition(position);
//创建用于显示的NPC精灵
npcSprite=CCSprite::create(imagePath->m_sString.c_str(),rect);
npcSprite->setAnchorPoint(CCPointZero);
npcSprite->setPosition(position);
sGlobal->gameLayer->addChild(npcSprite,kZNPC);
//从动画管理器中根据npcId获取动画,开始永久播放
CCAnimate* animation=sAnimationMgr->createAnimate(npcId->m_sString.c_str());
if(animation!=NULL){
CCActionInterval* action=CCRepeatForever::create(animation);
npcSprite->runAction(action);
}
}
NPC::~NPC(void)
{
//释放CCString对象
CC_SAFE_RELEASE(npcId);
CC_SAFE_RELEASE(imagePath);
CC_SAFE_RELEASE(type);
}
在构造函数中,我们创建了一个CCSprite用于在地图上显示NPC,然后从AnimationManager中根据npcId取得对应的动画,让CCSprite不停地执行。在AnimationManager的初始化方法中,加载此动画,具体代码如下:
//加载NPC动画
CCAnimationCache::sharedAnimationCache()->addAnimation(createNPCAnimation(), "npc0");
当然我们首先应该在.h文件里面声明它:
//加载NPC动画模版
CCAnimation *createNPCAnimation();
createNPCAnimation的具体实现如下:
//加载NPC动画模版
CCAnimation *AnimationManager::createNPCAnimation()
{
CCTexture2D *heroTexture=CCTextureCache::sharedTextureCache()->addImage("npc.png");
CCSpriteFrame *frame0,*frame1,*frame2,*frame3;
frame0=CCSpriteFrame::createWithTexture(heroTexture,CCRectMake(32*0,0,32,32));
frame1=CCSpriteFrame::createWithTexture(heroTexture,CCRectMake(32*1,0,32,32));
frame2=CCSpriteFrame::createWithTexture(heroTexture,CCRectMake(32*2,0,32,32));
frame3=CCSpriteFrame::createWithTexture(heroTexture,CCRectMake(32*3,0,32,32));
//在2.0版本以后,CCMutableArray取消了
CCArray* animFrames=CCArray::createWithCapacity(4);
//将创建的四个纹理保存到数组中
animFrames->addObject(frame0);
animFrames->addObject(frame1);
animFrames->addObject(frame2);
animFrames->addObject(frame3);
//创建一个动画,0.2f表示间隔时间
animation = CCAnimation::createWithSpriteFrames(animFrames, 0.05f);
//若不retain()则会出现错误
animation->retain();
return animation;
}
有了NPC类,就可以在GameMap的初始化方法中读取object层中的所有对象,将它们创建成为NPC类的实例。我们新建了一个initObject方法,用于初始化对象层数据。CCTMXTiledMap类已经提供了相当方便的方法来访问对象层的所有对象以及它们的属性。
//初始化对象层
void GameMap::initObject()
{
//初始化NPC对象
npcDict=new CCDictionary();
//获取对象层
CCTMXObjectGroup* group=this->objectGroupNamed("object");
//获取对象层内的所有对象
CCArray* objects=group->getObjects();
CCDictionary* dict=NULL;
CCObject* pObject = NULL;
CCARRAY_FOREACH(objects,pObject)
{
dict=(CCDictionary*)pObject;
if(!dict)
break;
//获取x坐标
const char* key = "x";
int x=((CCString*)dict->objectForKey(key))->intValue();
key="y";
//获取y坐标
int y=((CCString*)dict->objectForKey(key))->intValue();
CCPoint tileCoord=tileCoordForPosition(ccp(x,y));
//计算唯一ID
int index=tileCoord.x+tileCoord.y*this->getMapSize().width;
key="type";
//获取对象类别
CCString* type=(CCString*)dict->objectForKey(key);
//如果类型是NPC对象
if(type->m_sString=="npc"){
NPC *npc=new NPC(dict,(int)x,(int)y);
npcDict->setObject(npc,index);
}
}
}
好了,这里就会出现隐藏问题了。如果你是在cocos2d-x引擎的解决方案里新建的项目,那么可以无视这个问题。但你如果适合我一样按照子龙山人的这篇文章
《Cocos2d-x建工程时避免copy文件夹和库》来配置的环境,那么当你编译运行时,“int x=((CCString*)dict->objectForKey(key))->intValue()”这行代码有可能会报内存访问错误,跟踪一下,我们发现CCDictionary的大小为0。我找了一天多错误原因,就在快要崩溃的时候忽然看见了下面这条评论,原来早有人经历过这个问题的发生。
按照评论所说,我重新把Debug.win32文件下的dll文件复制到了System32(32位)/SysWOW64(64位)下。编译运行,成功。看来想要便捷的方法是要付出一定代价的。
最后,我们在Hero的checkCollision方法中加入NPC对象的碰撞检测:首先根据坐标计算出对应的index,然后根据index在npcDict中查询是否有对应的NPC存在,如果存在则与NPC进行交互。具体代码如下:
//从NPC字典中查询
int index=targetTileCoord.x+targetTileCoord.y*sGlobal->gameMap->getMapSize().width;
NPC *npc=(NPC*)sGlobal->gameMap->npcDict->objectForKey(index);
if(npc!=NULL)
{
actWithNPC();
return kNPC;
}
为简单起见,仅在actWithNPC方法中调用gameLayer的showTip方法显示一条提示信息:
//与NPC交互
void Hero::actWithNPC()
{
sGlobal->gameLayer->showTip("talking with npc",getPosition());
}
至此,我们已经大致完成了添加NPC对象的目标,虽然交互还是非常简单。上面用到的方法都需要进行声明,要注意。下面看运行结果: