前言
如果你选择了Torque,那么TorqueScript对于你来说就变得非常重要,因为为了直接跨平台和与最新版本同步,你就要尽量少的修改源代码,多多的用脚本开发.
使用脚本,不是光有一些变量支持,语法支持就行的,它最重要的作用是与引擎通信,通过接口调用和事件的捕获来完成各种各样的任务,而对于接口调用这里不再多说,你只要搜索工程(Console*****)就会看到.
我在这里分享一下脚本中对象方法的创建,引擎事件的捕获以及内部原理.
举例
举个例子先:
function Test()
{
%set = new SimSet() { class="SetClass"; };
Game.ActiveStage.add( %set );
%set.onCall( "test set namespace entry :)" );
}
function SetClass::onCall( %this, %data )
{
echo( "setclass received call:" @ %data );
}
上面的代码有三个重要点:
1. 创建SimSet: 继承自SimObject
2. 设置"class": SimObject的域名
3. 声明"SetClass::onCall": 自定义回调
问题
引擎是如何调用到SetClass::onCall的? 举一反三,引擎内部的一些事件抛出到脚本也是一样的原理,只不过消息名不一样,我们要如何做才能捕获到他们?
Namespace and Entry
在Torque中,链表被大量的使用,Namesapce也是其中之一,他很重要,可以理解为是引擎和脚本的桥梁,它管理了所有的通信接口和脚本数据,方法.
一个Namspace就可以看做是一个方法类,或者功能包.
Entry是具体某个子功能的封装结构,比如OnCall这个方法在引擎内部就是一个Entry,而SetClass就是一个namespace.两者是一对多的关系.
在SimObject的域初始化方法中有:
void SimObject::initPersistFields()
{
.....
// Namespace Linking.
addGroup("Namespace Linking");
addProtectedField("superclass", TypeString, Offset(mSuperClassName, SimObject), &setSuperClass, &defaultProtectedGetFn, &writeSuperclass, "Script Class of object.");
addProtectedField("class", TypeString, Offset(mClassName, SimObject), &setClass, &defaultProtectedGetFn, &writeClass, "Script SuperClass of object.");
endGroup("Namespace Linking");
}
有两个域和名称空间有关系,而他们的具体实现方法:
void SimObject::setClassNamespace( const char *classNamespace )
{
mClassName = StringTable->insert( classNamespace );
linkNamespaces();
}
void SimObject::setSuperClassNamespace( const char *superClassNamespace )
{
mSuperClassName = StringTable->insert( superClassNamespace );
linkNamespaces();
}
linkNamespaces中,会对superclas,class两个域进行判定,如果存在则会链接到namspace的链表中,在引擎调用脚本对象方法的时候,能够找到.
bool linkNamespaces(const char *parent, const char *child)
{
Namespace *pns = lookupNamespace(parent);
Namespace *cns = lookupNamespace(child);
if(pns && cns)
return cns->classLinkTo(pns);
return false;
}
Namespace *Namespace::find(StringTableEntry name, StringTableEntry package)
{
for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext)
if(walk->mName == name && walk->mPackage == package)
return walk;
Namespace *ret = (Namespace *) mAllocator.alloc(sizeof(Namespace));
constructInPlace(ret);
ret->mPackage = package;
ret->mName = name;
ret->mNext = mNamespaceList;
mNamespaceList = ret;
return ret;
}
再结合编译过程中的一段代码:
case OP_FUNC_DECL:
if(!noCalls)
{
fnName = U32toSTE(code[ip]);
fnNamespace = U32toSTE(code[ip+1]);
fnPackage = U32toSTE(code[ip+2]);
bool hasBody = bool(code[ip+3]);
Namespace::unlinkPackages();
ns = Namespace::find(fnNamespace, fnPackage);
ns->addFunction(fnName, this, hasBody ? ip : 0, curFNDocBlock ? dStrdup( curFNDocBlock ) : NULL );// if no body, set the IP to 0
if( curNSDocBlock )
{
if( fnNamespace == StringTable->lookup( nsDocBlockClass ) )
{
char *usageStr = dStrdup( curNSDocBlock );
usageStr[dStrlen(usageStr)] = '\0';
ns->mUsage = usageStr;
ns->mCleanUpUsage = true;
curNSDocBlock = NULL;
}
}
Namespace::relinkPackages();
// If we had a docblock, it's definitely not valid anymore, so clear it out.
curFNDocBlock = NULL;
//Con::printf("Adding function %s::%s (%d)", fnNamespace, fnName, ip);
}
ip = code[ip + 4];
break;
我们可以看出,Namspace的名字不能够重复,在脚本编译阶段,如果遇到ClassName::Function这样的字段,编译器会自动寻找ClassName的名称空间,找不到直接创建个新的,然后加入一个Function的Entry,对象创建时的class设置于class本身的方法定义没有强制性的先后关系.
总结
Torque提供了很多的脚本对象事件,如果你有工程,那么直接搜executef就可以看到大部分,结合上面的方法可以在脚本中捕获,处理. :)