NPC(Non-Player Character)在游戏中广泛存在。本游戏是c++为主,lua为辅,然后在脚本中来控制游戏流程,以后的NPC应用广泛,可以是普通的人物,还可以是宝箱,传送阵,乃至是田地,即角色扮演+模拟经营。当然,前提是我们得在c++做好足够的工作才行。
首先,添加一个枚举体
//游戏状态
enum class GameState
{
Normal,/*正常状态,即行走状态*/
Fighting,/*战斗状态*/
Script,/*脚本状态*/
};
GameState即为当前的游戏状态,同时GameScene中加入m_gameState。然后,主角只有在正常状态下才可以操作人物,故EventLayer需要过滤事件:
bool EventLayer::onTouchBegan(Touch* touch,SDL_Event* event)
{
auto location = touch->getLocation();
auto gameScene = GameScene::getInstance();
auto gameState = gameScene->getGameState();
if (gameState == GameState::Normal)
gameScene->clickPath(location);
return true;
}
void EventLayer::onTouchMoved(Touch* touch,SDL_Event* event)
{
}
void EventLayer::onTouchEnded(Touch* touch,SDL_Event* event)
{
}
即当游戏状态为GameState::Normal时,才进行事件转发。
然后,需要在GameScene中提供一些常用的脚本接口,以便于外界对lua的操作。
//执行函数 参数和函数需要先入栈
int executeFunction(int nargs, int nresults);
//开始或者恢复协程 仅开始时需要函数入栈,其他恢复协程则只需要参数即可
int resumeFunction(int nargs);
int resumeLuaScript(WaitType waitType);
//把对应类型的值放入栈顶,并返回其类型
int getLuaGlobal(const char* name);
//把t[k]放入栈顶,并返回其类型
int getLuaField(int index, const char* key);
lua_State* getLuaState() const { return m_pLuaState;}
//参数入栈
void pushInt(int intValue);
void pushFloat(float floatValue);
void pushBoolean(bool boolValue);
void pushString(const char* stringValue);
void pushNil();
void pop(int n);
bool toLuaBoolean(int index);
const char* checkLuaString(int index);
上面的函数除了resumeLuaScript()外(此函数后面会提到),其余的都是GameScene类中简单调用了lua的函数(组合模式的弊端)。
int GameScene::executeFunction(int nargs, int nresults)
{
int ret = LUA_ERRRUN;
//检测-(nargs + 1)是否是函数
if (lua_isfunction(m_pLuaState, -(nargs + 1)))
{
ret = lua_pcall(m_pLuaState, nargs, nresults,NULL);
}
else
{
lua_pushfstring(m_pLuaState, "the index isn't function %d", -(nargs + 1));
}
return ret;
}
int GameScene::resumeFunction(int nargs)
{
int ret = LUA_ERRRUN;
//检测是否存在足够的参数
if (lua_gettop(m_pLuaState) >= nargs)
{
ret = lua_resume(m_pLuaState, nullptr, nargs);
m_nCoroutineRet = ret;
}
else
{
lua_pushfstring(m_pLuaState, "the index isn't function %d", -(nargs + 1));
}
//当执行无误时应该返回LUA_OK或者是LUA_YIELD
return ret;
}
上面两个函数的主要区别就是executeFunction()内部使用了lua_pcall(),而resumeFunction()内部则是使用了lua_resume()。lua_resume()类似于lua_pcall(),不过lua_resume()主要用于开始或者恢复协程,有关于lua的协程概念,详细的可以百度。推荐看下lua官方网站的wiki,好多疑问上面都可以找到解答。
int GameScene::getLuaGlobal(const char* name)
{
return lua_getglobal(m_pLuaState, name);
}
int GameScene::getLuaField(int index, const char* key)
{
return lua_getfield(m_pLuaState, index, key);
}
void GameScene::pushInt(int intValue)
{
lua_pushinteger(m_pLuaState, (lua_Integer)intValue);
}
void GameScene::pushFloat(float floatValue)
{
lua_pushnumber(m_pLuaState, (lua_Number)floatValue);
}
void GameScene::pushBoolean(bool boolValue)
{
int value = boolValue ? 1 : 0;
lua_pushboolean(m_pLuaState, value);
}
void GameScene::pushString(const char* stringValue)
{
lua_pushstring(m_pLuaState, stringValue);
}
void GameScene::pushNil()
{
lua_pushnil(m_pLuaState);
}
void GameScene::pop(int n)
{
lua_pop(m_pLuaState, n);
}
bool GameScene::toLuaBoolean(int index)
{
return lua_toboolean(m_pLuaState, index) == 1;
}
const char* GameScene::checkLuaString(int index)
{
return luaL_checkstring(m_pLuaState, index);
}
上面的几个函数都是简单调用了lua提供的函数。
接着就是NPC。一般情况下,NPC是依附于地图的,即在地图创建的同时,也创建该地图对应的NPC;地图销毁则回收销毁对应的NPC。在本游戏中,每个地图文件(*.tmx)都是有着一个名为 "script layer"的对象层,脚本对象的属性如下:
chartlet | 贴图名,当使用时需要在character.plist中注册,可为空 |
priority | 优先级,同图块优先级 |
script | 脚本文件路径,可为空 |
trigger | 触发方式 |
优先级在StaticData.h中定义
/*优先级 */
#define PRIORITY_SAME 0 /*与人物能产生碰撞*/
#define PRIORITY_LOW 1 /*NPC显示在下*/
#define PRIORITY_HIGH 2 /*NPC显示在上*/
然后就是触发方式,这个在NonPlayerCharacter.h中有定义:
//脚本触发方式
enum class TriggerType
{
None,
Click,/*点击NPC*/
Touch,/*触摸触发*/
All,
};
Touch指的是当角色和NPC的碰撞面积交叉时,NPC会被触发,比如传送阵。
Click则指的是当玩家"点"到NPC时,之后主角移动到NPC面前,NPC会被触发,比如商人。
先看下NonPlayerCharacter.lua脚本:
NonPlayerCharacter = {};
function NonPlayerCharacter:new(o)
o = o or {};
setmetatable(o,self);
self.__index = self;
return o;
end
--初始化
function NonPlayerCharacter:initialize(id)
self.id = id;
end
--playerId 触发的主角id
--返回值 为true则吞并事件
function NonPlayerCharacter:execute(playerId)
return false;
end
function NonPlayerCharacter:update()
-- body
end
function NonPlayerCharacter:clean()
self = nil;
end
上面创建了一个包含了5个函数的table,也可以认为是类。它与c++中的NonPlayerCharacter类对应,每个NPC都有一个对应的table,当NPC对象创建时,对应的table也会创建,当NPC对象销毁时,对应的table也会销毁。
NonPlayerCharacter.h
#ifndef __NonPlayerCharacter_H__
#define __NonPlayerCharacter_H__
#include <string>
#include "lua/lua.h"
#include "Character.h"
using namespace std;
//脚本触发方式
enum class TriggerType
{
None,
Click,/*点击NPC*/
Touch,/*触摸触发*/
All,
};
class NonPlayerCharacter : public Character
{
SDL_SYNTHESIZE(TriggerType, m_triggerType, TriggerType);
SDL_BOOL_SYNTHESIZE(m_bDirty, Dirty);//是否是脏的,即是否要删除
SDL_BOOL_SYNTHESIZE(m_bUpdate, Update);//是否调用update函数
SDL_SYNTHESIZE_READONLY(int, m_nPriority, Priority);//优先级
private:
string m_name;//脚本名称
string m_filename;//脚本文件名
Rect m_boundingBox;//包围盒
public:
NonPlayerCharacter();
~NonPlayerCharacter();
static NonPlayerCharacter* create(const ValueMap& objects);
bool init(const ValueMap& objects);
//主角进行交互并执行对应函数 当返回值为true则吞并事件
bool execute(int playerID);
void update(float dt);
void clean();
//是否和对应矩形发生碰撞
bool intersectRect(const Rect& rect);
//是否能触发
bool isTrigger(TriggerType triggerType);
//是否能通过
bool isPassing() const;
private:
bool initialize();
static TriggerType getTriggerTypeByString(const string& sType);
};
#endif
NPC继承自Character,即NPC可以有行走图。另外,initialize(),execute(),update(),clean()函数都是调用了对应table中的函数,把可扩展性留给脚本去实现。
bool NonPlayerCharacter::init(const ValueMap& objects)
{
bool ret = false;
float x = objects.at("x").asFloat();
float y = objects.at("y").asFloat();
float width = objects.at("width").asFloat();
float height = objects.at("height").asFloat();
//获取脚本名
m_name = objects.at("name").asString();
//获取对应属性
const auto& properties = objects.at("properties").asValueMap();
//获取文件名
m_filename = properties.at("script").asString();
m_nPriority = properties.at("priority").asInt();
//动画名可能不存在
if (properties.find("chartlet") != properties.end())
{
m_chartletName = properties.at("chartlet").asString();
}
if (properties.find("update") != properties.end())
{
m_bUpdate = properties.at("update").asBool();
}
auto triggerStr = properties.at("trigger").asString();
m_triggerType = this->getTriggerTypeByString(triggerStr);
//设置碰撞面积
m_boundingBox.origin = Point(x,y);
m_boundingBox.size = Size(width,height);
int uniqueID = this->getUniqueID();
//进行脚本的初始化
ret |= this->initialize();
auto chartletName = this->m_chartletName;
//获取贴图
auto size = m_boundingBox.size;
auto origin = m_boundingBox.origin;
//设置逻辑大小
if (!chartletName.empty())
{
ret |= Character::init(chartletName);
}
else if (this->getSprite() == nullptr)
{
this->setContentSize(size);
}
this->setPosition(Point(origin.x + size.width/2,origin.y + size.height/2));
return ret;
}
init函数中获取valueMap的相关内容,并且如果chartletName不空的话,还会接着调用Character的init函数。
bool NonPlayerCharacter::initialize()
{
auto gameScene = GameScene::getInstance();
auto id = this->getUniqueID();
//执行脚本
int ret = LUA_OK;
if (!m_filename.empty())
{
gameScene->executeScriptFile(m_filename.c_str());
}
//脚本加载成功,调用对应的初始化函数
if (ret == LUA_OK)
{
//获取函数
gameScene->getLuaGlobal(m_name.c_str());
gameScene->getLuaField(-1, "initialize");
//放入参数
gameScene->getLuaGlobal(m_name.c_str());
gameScene->pushInt(id);
//执行函数
gameScene->executeFunction(2, 0);
gameScene->pop(1);
return true;
}
else
{
string errMsg = gameScene->checkLuaString(-1);
gameScene->pop(1);
printf("NonPlayerCharacter::init error:%s\n", errMsg.c_str());
}
return false;
}
initlize函数先尝试加载对应的脚本文件,默认加载成功,之后调用对应table的initialize函数。
bool NonPlayerCharacter::execute(int playerID)
{
auto gameScene = GameScene::getInstance();
bool ret = false;
//获得函数
gameScene->getLuaGlobal(m_name.c_str());
gameScene->getLuaField(-1, "execute");
//放入参数
gameScene->getLuaGlobal(m_name.c_str());
gameScene->pushInt(playerID);
//执行函数
gameScene->resumeFunction(2);
//获取返回值
ret = gameScene->toLuaBoolean(-1);
gameScene->pop(2);
return ret;
}
void NonPlayerCharacter::update(float dt)
{
auto gameScene = GameScene::getInstance();
//获得函数
gameScene->getLuaGlobal(m_name.c_str());
gameScene->getLuaField(-1, "update");
//放入参数
gameScene->getLuaGlobal(m_name.c_str());
gameScene->pushFloat(dt);
//执行函数
gameScene->executeFunction(2,0);
gameScene->pop(1);
}
void NonPlayerCharacter::clean()
{
auto gameScene = GameScene::getInstance();
if(gameScene != nullptr)
{
//获得函数
gameScene->getLuaGlobal(m_name.c_str());
gameScene->getLuaField(-1 ,"clean");
//放入参数
gameScene->getLuaGlobal(m_name.c_str());
//执行函数
gameScene->executeFunction(1,0);
gameScene->pop(1);
}
}
上面几个函数和initialize函数类似。另外,在使用lua函数的时候,应该注意对lua栈的维护,否则会导致栈满而报错。解决办法:要么每次使用lua函数时严格控制栈;要么在每帧调用的函数(如GameScene中的update)中进行调用lua_settop()进行清理(不推荐)。
bool NonPlayerCharacter::intersectRect(const Rect& rect)
{
//查看对应table中是否存在字段range
auto gameScene = GameScene::getInstance();
gameScene->getLuaGlobal(m_name.c_str());
int ret = gameScene->getLuaField(-1, "range");
//不存在该字段,检测矩形碰撞
if (ret == LUA_TNIL)
{
gameScene->pop(2);
auto r = this->getBoundingBox();
return r.intersectRect(rect);
}
//获取字段
string range = gameScene->checkLuaString(-1);
gameScene->pop(2);
//全范围
if (range == "all")
return true;
else
return m_boundingBox.intersectRect(rect);
}
在检测矩形碰撞时做了一点点改动,这个函数会先检测对应table中是否有range这个字段,如果没有,则调用Rect的insectRect进行检测;如果有这个字段,则检测是否是"all",是则表示会发生碰撞,否则则会使用m_boundingBox进行碰撞检测,m_boundingBox中在init函数中进行赋值,其大小是tmx中的对应对象的大小。这个改动主要是为了以后的踩雷式遇敌做的准备。
bool NonPlayerCharacter::isTrigger(TriggerType triggerType)
{
if (triggerType == TriggerType::All)
return true;
return this->m_triggerType == triggerType;
}
bool NonPlayerCharacter::isPassing() const
{
return m_nPriority != PRIORITY_SAME;
}
TriggerType NonPlayerCharacter::getTriggerTypeByString(const string& sType)
{
TriggerType type = TriggerType::None;
if (sType == "Touch")
type = TriggerType::Touch;
else if (sType == "Click")
type = TriggerType::Click;
return type;
}
然后就是ScriptLayer类,ScriptLayer类似于PlayerLayer,在内部同样只是保存了对NPC的引用,NPC实际的"父亲"和PlayerLayer中的角色一样都是collisionLayer,这样NPC也能很方便地实现和主角同样的场景遮挡。
#ifndef __ScriptLayer_H__
#define __ScriptLayer_H__
#include <vector>
#include "SDL_Engine/SDL_Engine.h"
class NonPlayerCharacter;
enum class TriggerType;
using namespace std;
USING_NS_SDL;
/*脚本等待类型*/
enum class WaitType
{
None,
Time,
Click,
Button,
};
class ScriptLayer : public Layer
{
SDL_SYNTHESIZE(WaitType, m_waitType, WaitType);//脚本等待类型
SDL_SYNTHESIZE(float, m_waitTime, WaitTime);//等待时间
private:
vector<NonPlayerCharacter*> m_npcList;
//避免在脚本中进行地图切换时发生错误
vector<NonPlayerCharacter*> m_npcListCopy;
public:
ScriptLayer();
~ScriptLayer();
CREATE_FUNC(ScriptLayer);
bool init();
virtual void update(float dt);
void updateWaitTime(float dt);
/*触发事件,并返回是否可通过当前脚本事件*/
bool checkAndTriggerScriptEvent(const Rect& boundingBox, int playerID, TriggerType type, int priority);
//添加NPC
void addNPC(NonPlayerCharacter* npc);
//清除全部脚本事件
void clear();
};
#endif
当添加NPC时,ScriptLayer对象会先引用该NPC,然后添加到容器m_npcListCopy中,之后在ScriptLayer::update()函数中把m_npcListCopy的项全部转移至m_npcList中。这样做的原因是为了避免直接添加从而迭代器失效导致运行错误(如checkAndTriggerScriptEvent中是在循环中进行检测碰撞和执行对应的脚本函数的)。
void ScriptLayer::update(float dt)
{
for (auto it = m_npcListCopy.begin();it != m_npcListCopy.end();)
{
auto npc = *it;
m_npcList.push_back(npc);
it = m_npcListCopy.erase(it);
}
for (auto it = m_npcList.begin();it != m_npcList.end();)
{
auto script = *it;
if (!script->isDirty())
{
//只有在应该更新的时候才会调用update函数
if (script->isUpdate())
script->update(dt);
it++;
}
else//删除
{
it = m_npcList.erase(it);
script->clean();
script->removeFromParent();
SDL_SAFE_RELEASE_NULL(script);
}
}
}
在update函数中会对npc进行转移,并且会检测更新以及删除操作。
void ScriptLayer::updateWaitTime(float dt)
{
if (m_waitType == WaitType::Time)
{
m_waitTime -= dt;
//回调函数
if (m_waitTime <= 0.f)
{
m_waitTime = 0.f;
GameScene::getInstance()->resumeLuaScript(m_waitType);
}
}
}
这个函数主要是为了实现脚本中的延时操作,配合lua中的协程进行实现,比如一个角色从(1,1)走到(3,3),此时的脚本因为有协程的存在而暂停,但是c++进行不可能暂停,又因为需要唤醒对应的协程,故需要一个计时器,除了WaitType::Time之外,还有Click和Button,Click对应的是点击操作,Button则是虚拟按钮。
int GameScene::resumeLuaScript(WaitType waitType)
{
int nargs = 0;
int ret = LUA_ERRRUN;
if (waitType == WaitType::None)
return ret;
//需要提前添加参数入栈
else if (waitType == WaitType::Button)
nargs = 1;
m_pScriptLayer->setWaitType(WaitType::None);
//恢复协程
ret = this->resumeFunction(nargs);
return ret;
}
resumeLuaScript函数就是唤醒(resume 而不是start)协程,且WaitType::Button会传递一个参数,这个以后用到再详细说明。
bool ScriptLayer::checkAndTriggerScriptEvent(const Rect& boundingBox, int playerID, TriggerType type, int priority)
{
bool bPassing = true;
//是否吞并事件
bool bSwallowed = false;
for (auto it = m_npcList.begin();it != m_npcList.end();it++)
{
auto script = *it;
//发生碰撞 脚本不同
if (!script->isDirty() && script->getUniqueID() != playerID
&& script->intersectRect(boundingBox))
{
//触发条件相同 优先级相同 并且没有吞并事件 调用对应的脚本
if (script->isTrigger(type) && (script->getPriority() & priority)
&& !bSwallowed)
{
bSwallowed = script->execute(playerID);
}
//只有在触发条件和主角同级时才会判断是否会碰撞
if (script->getPriority() == PRIORITY_SAME)
{
bPassing &= script->isPassing();
}
}
}
return bPassing;
}
检测并且触发脚本。有的脚本可能会吞并事件(避免同时有若干个NPC同时响应的bug),且会判断与角色是否发生碰撞。
void ScriptLayer::addNPC(NonPlayerCharacter* npc)
{
SDL_SAFE_RETAIN(npc);
m_npcListCopy.push_back(npc);
}
void ScriptLayer::clear()
{
//滞后删除
for (auto it = m_npcList.begin();it != m_npcList.end();it++)
{
auto script = *it;
script->setDirty(true);
}
}
在Game Scene类中添加
ScriptLayer* m_pScriptLayer;
并在init函数中创建。
然后就是changeMap的改动。前面也说过NPC是依附于地图的,所以在切换地图时需要创建该地图对应的NPC。
void GameScene::changeMap(const string& mapName, const Point &tileCoodinate)
{
//获取主角列表 并清除
auto &characterList = m_pPlayerLayer->getCharacterList();
for (auto character : characterList)
{
character->retain();
character->removeFromParent();
}
//改变当前地图
m_pMapLayer->clear();
m_pMapLayer->init(mapName);
//重新设置A星算法的大小
StaticData::getInstance()->getAStar()->setMapSize(m_pMapLayer->getTiledMap()->getMapSize());
//获取碰撞层
auto collisionLayer = m_pMapLayer->getCollisionLayer();
//设置主角位置
auto tileSize = m_pMapLayer->getTiledMap()->getTileSize();
auto pos = Point(tileSize.width * (tileCoodinate.x + 0.5f)
,tileSize.height * (tileCoodinate.y + 0.5f));
//添加主角
for (unsigned int i = 0; i < characterList.size();i++)
{
auto character = characterList.at(i);
//添加主角,并设置localZOrder
collisionLayer->addChild(character,CHARACTER_LOCAL_Z_ORDER - i);
character->setPosition(pos);
character->release();
}
//改变当前中心点
this->setViewpointCenter(pos);
//删除脚本事件
m_pScriptLayer->clear();
//获取脚本对象
auto scriptObjects = m_pMapLayer->getScriptObjectGroup();
if (scriptObjects != nullptr)
{
ValueVector& vec = scriptObjects->getObjects();
//生成npc
for (const auto& object : vec)
{
const auto& valueMap = object.asValueMap();
NonPlayerCharacter* npc = NonPlayerCharacter::create(valueMap);
//添加到脚本事件层
m_pScriptLayer->addNPC(npc);
//遮挡处理
int localZOrder = CHARACTER_LOCAL_Z_ORDER;
if (npc->getPriority() == PRIORITY_LOW) localZOrder -= 1;
else if (npc->getPriority() == PRIORITY_HIGH) localZOrder += 1;
//添加到碰撞层
collisionLayer->addChild(npc,localZOrder);
}
}
}
//删除脚本事件
m_pScriptLayer->clear();
//获取脚本对象
auto scriptObjects = m_pMapLayer->getScriptObjectGroup();
if (scriptObjects != nullptr)
{
ValueVector& vec = scriptObjects->getObjects();
//生成npc
for (const auto& object : vec)
{
const auto& valueMap = object.asValueMap();
NonPlayerCharacter* npc = NonPlayerCharacter::create(valueMap);
//添加到脚本事件层
m_pScriptLayer->addNPC(npc);
//遮挡处理
int localZOrder = CHARACTER_LOCAL_Z_ORDER;
if (npc->getPriority() == PRIORITY_LOW) localZOrder -= 1;
else if (npc->getPriority() == PRIORITY_HIGH) localZOrder += 1;
//添加到碰撞层
collisionLayer->addChild(npc,localZOrder);
}
}
}
然后就是关于触发,目前先添加Touch的触发。
需要在initializeMapAndPlayer函数中添加:
//添加角色移动结束触发器
_eventDispatcher->addEventCustomListener(CHARACTER_MOVE_TO_TILE,
SDL_CALLBACK_1(GameScene::moveToTile, this), this);
Character对象在每次到达一个图块中心时(A*算法得到的是一个路径数组,Character对象使用MoveTo+Callback来根据该数组行走,在Callback中会发送事件)都会发送一个用户事件,我们所做的就是捕获这个事件,然后在回调函数中进行对应的操作。
void GameScene::moveToTile(EventCustom* eventCustom)
{
auto character = (Character*) eventCustom->getUserData();
//获取主角
auto player = m_pPlayerLayer->getPlayer();
auto playerID = player->getUniqueID();
//该角色不是主角,直接返回
if (playerID != character->getUniqueID())
return;
auto rect = Rect(player->getPosition(),Size(1.f,1.f));
//触发高低优先级的脚本事件
m_pScriptLayer->checkAndTriggerScriptEvent(rect, playerID, TriggerType::Touch,
PRIORITY_HIGH | PRIORITY_LOW);
}
getUniqueID()每个继承自Object的对象都有一个唯一的ID(cocos2d-x中没有该函数,但有_uniqueID,加上即可)。
moveToTile()会在任何角色移动到图块中心时回调,因此需要检测下是否是主角,然后再判断是否触发NPC。另外,只有PRIORITY_SAME才会检测碰撞,PRIORITY_LOW PRIORITY_HIGH则不会,它们影响的只是渲染顺序而已。
然后添加一个测试脚本:Map01_02.lua
require "NonPlayerCharacter"
--NPC 主角的妹妹
Map01_02_NPC01 = NonPlayerCharacter:new();
function Map01_02_NPC01:initialize(id)
NonPlayerCharacter:initialize(id);
end
function Map01_02_NPC01:execute()
-- 已经接受了任务
--if LUA_isTaskAccepted("Map01_Task01_01") then
--elseif LUA_isTaskSuccess("Map01_Task01_01") then
--end
return false;
end
--传送门 传送到1.tmx
Map01_02_Portal01 = NonPlayerCharacter:new();
function Map01_02_Portal01:execute(playerID)
print("execute success");
return false;
end
该脚本对应地图1_2.tmx。且该文件只会在Map01_02_Portal01对象加载一次。此时要注意tmx中创建对象的顺序,1_2.tmx的xml表示如下:
<objectgroup name="script layer">
<object id="1" name="Map01_02_Portal01" x="383" y="624" width="48" height="48">
<properties>
<property name="priority" type="int" value="1"/>
<property name="script" value="script/Map01_02.lua"/>
<property name="trigger" value="Touch"/>
</properties>
</object>
<object id="4" name="Map01_02_NPC01" x="528" y="144" width="48" height="48">
<properties>
<property name="chartlet" value="moon"/>
<property name="priority" type="int" value="0"/>
<property name="script" value=""/>
<property name="trigger" value="Click"/>
<property name="update" type="bool" value="true"/>
</properties>
</object>
</objectgroup>
目前共有两个对象,Map01_02_Protal01在前,故按照顺序,此对象是第一个加载的(可在GameScene中维护一个表来避免重复加载)。
---------------------------------------------------------分割线----------------------------------------------------------
先修改如下:
在Game Scene中添加成员:
//保存已经执行过的文件
vector<string> m_loaded;
然后就是:
//执行脚本文件 force是否强制执行该文件
int executeScriptFile(const char* filename, bool force = false);
如果force为true,则无论m_loaded中是否存在,强制加载该文件。
int GameScene::executeScriptFile(const char* filename, bool force)
{
bool bShouldLoad = false;
auto it = find(m_loaded.begin(), m_loaded.end(), filename);
//第一次,加载
if (it == m_loaded.end())
{
bShouldLoad = true;
m_loaded.push_back(filename);
}
else if (force)
bShouldLoad = true;
if (!bShouldLoad)
return LUA_OK;
FileUtils* utils = FileUtils::getInstance();
string fullpath = utils->fullPathForFilename(filename);
int ret = LUA_ERRRUN;
unsigned int size = 0;
unique_ptr<char> chunk = utils->getUniqueDataFromFile(fullpath, &size);
//加载并执行
if (chunk != nullptr)
{
const char* data = chunk.get();
ret = luaL_loadbuffer(m_pLuaState, data, size, nullptr);
if (ret == LUA_OK)
{
ret = lua_pcall(m_pLuaState, 0, 0, 0);
}
chunk.reset();
}
return ret;
}
bool bShouldLoad = false;
auto it = find(m_loaded.begin(), m_loaded.end(), filename);
//第一次,加载
if (it == m_loaded.end())
{
bShouldLoad = true;
m_loaded.push_back(filename);
}
else if (force)
bShouldLoad = true;
if (!bShouldLoad)
return LUA_OK;
FileUtils* utils = FileUtils::getInstance();
string fullpath = utils->fullPathForFilename(filename);
int ret = LUA_ERRRUN;
unsigned int size = 0;
unique_ptr<char> chunk = utils->getUniqueDataFromFile(fullpath, &size);
//加载并执行
if (chunk != nullptr)
{
const char* data = chunk.get();
ret = luaL_loadbuffer(m_pLuaState, data, size, nullptr);
if (ret == LUA_OK)
{
ret = lua_pcall(m_pLuaState, 0, 0, 0);
}
chunk.reset();
}
return ret;
}
简单的过滤,无法区别"Map01_02.lua"和"script/Map01_02.lua",不过目前倒是够用了。