前言
如果你选择了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就可以看到大部分,结合上面的方法可以在脚本中捕获,处理. :)