一. 基本语法不同
1. 字符串的比较.
C++中可以通过重载运算符实现比较,Torque Script则直接用$=和!$=进行比较,或者通过脚本内置函数strcmp进行比较.
2. 变量表示
脚本中没有类型(实际上都是c++的字符串),即无须声明即可使用,并且变量的命名并不区分大小写.即%var1和%vaR1是一个变量.
变量有3种写法,分别是%前缀表示局部变量,只能在局部使用.$前缀表示全局变量,可以在任何地方使用.还有一种没有前缀的,类似于全局变量的使用,实际上在引用该变量的时候也可以加引号来表示.如下例子中的
myClientConnection,跟”
myClientConnection”是一回事.
new GameConnection
(myClientConnection);
if
(
$JoinGameAddress
!$=
""
)
myClientConnection
.
connect
(
$JoinGameAddress
);
//
等同于
”myClientConnection”
.
connect
(
$JoinGameAddress
);
else
myClientConnection
.
connectLocal();
这个例子
new
了一个
GameConnection
对象,然后调用他的方法
connect
连接到服务器
.
还有一种
标记字符串
,如下例子可以说明它与一般字符串的不同之处
.
$testN
=
'nihao'
; //TGE
会给标记符号和它的唯一标示数字
echo
(
$testN
);
$testN
=
StripMLControlChars
(
$testN
);
//
这里可以去掉标记符号
.
echo
(
$testN
);
后面我们也会看到它的应用
3.字符串相加
Script中字符串相加用@, 例如:%myName@”你好”
二.脚本中的面向对象支持
这里主要通过OO的三个基本要素阐述脚本对于OO的支持程度.
1. 封装:
脚本支持在内置对象基础上添加成员变量和函数.但是并不支持自定义类
类似c++中 class myClass{};之类的声明是不存在的.
如下例中,AiPlayer的成员变量path和函数spawn(相对于c++中的类静态函数)
和followPath(类成员函数)
function
AIPlayer
::
spawn(
%name
,
%spawnPoint
)
{
// Create the demo player object
%player
=
new AiPlayer
() {
dataBlock
=
DemoPlayer;
path
=
""
;
};
%player.followPath(
"MissionGroup/Paths/Path1"
,
-
1
);
MissionCleanup
.
add(
%player
);
%player
.
setShapeName(
%name
);
%player
.
setTransform(
%spawnPoint
);
return
%player
;
}
function
AIPlayer
::
followPath(
%this
,
%path
,
%node
)
{
// Start the player following a path
%this
.
stopThread(
0
);
if
(
!
isObject
(
%path
)) {
%this
.
path
=
""
;
return
;
}
if
(
%node
>
%path
.
getCount()
-
1
)
%this
.
targetNode
=
%path
.
getCount()
-
1
;
else
%this
.
targetNode
=
%node
;
if
(
%this
.
path
$=
%path
)
this
.
moveToNode(
%this
.
currentNode);
else
{
%this
.
path
=
%path
;
%this
.
moveToNode(
0
);
}
}
脚本中有一个特殊的关键字
,datablock,
它类似与
c++
中的
struct,
当脚本被加载之后,
就会为该数据块分配内存
,
当
client
连接到服务器的时候,所有的
datablock
都被发送到客户端
.
2. 继承:
脚本通过
activatePackage
和
deactivatePackage
实现了类似继承的机制
.
最先加载的
package
作为父类接口
.
随后加载的作为其子类.以此类推
.
在调用时,首先调用最底层的
package.
具体例子可以在
demo
工程中搜索
activatePackage.
当一个被加载的脚本中,一个函数有多个实现时,会调用离最后被加载的那个函数实现
.
3. 多态
脚本并不支持于c++多态类似的语法和功能.
三.脚本重要函数和类介绍:
1. commandToServer和commandToClient函数
服务器和客户端通信的接口.其中前者是向服务器发包,后者是向客户端发.
如下例子:
commandToClient(%client, ‘sendMsg’, %text);
客户端负责接收clientCmdSendMsg(%text);
服务器端需要在前端加标示符serverCmd.
或者
commandToClient(%client, ‘sendMsg’, “%1说:%2”, %userName, %msg);
类似于c中的sprintf语法.
2.exec(filename)
编译,执行函数,给变量赋值,加载程序包和数据块.
3.GameConnection类
游戏连接类,UDP发包.类成员函数可以参考 开发大全书上的介绍.这里说一下它的脚本回调函数(通过引擎里面的Con::executef去调用).
C结尾的表示用于客户端回调,其他用于服务器回调
onConnectionTimedOut //C
onConnectionAccepted //C
onConnect
onConnectRequestTimedOut
onConnectionDropped //C
onConnectRequestRejected
onConnectRequest
onConnectionError //C
onDrop
initialControlSet //C
onDataBlocksDone
书写回调的一般格式如下
:
function
GameConnection
::
onConnectionTimedOut(
%this
)
{
// Called when an established connection times out
disconnectedCleanup();
MessageBoxOK(
"TIMED OUT"
,
"The server connection has timed out."
);
}
具体回调函数的参数,可以在引擎代码中进行查询.
四.脚本基本实现原理
相关的宏定义:
DECLARE_CONOBJECT(myClass);
脚本类定义
IMPLEMENT_CONOBJECT(myClass); 脚本类声明
ConsoleMethod
脚本类方法定义.
ConsoleFunction
脚本全局函数定义
这里简单介绍一下c++实现脚本的二个重要方式:
1. 根据函数名调用c++中的函数:
关键宏:
#define ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) /
static returnType c##name(SimObject *, S32, const char **argv); /static ConsoleConstructor g##name##obj(NULL,#name,c##name,usage1,minArgs,maxArgs); /
static returnType c##name(SimObject *, S32 argc, const char **argv)
所有的函数都被简化成 returnValue scriptFun(SimObject *obj,int argc, char* argv[]);
根据不同的函数值定义不同的函数指针.主要以下5个函数指针的定义:
typedef const char * (*StringCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef S32
(*IntCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef F32
(*FloatCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef void
(*VoidCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef bool
(*BoolCallback)(SimObject *obj, S32 argc, const char *argv[]);
如果定义的是脚本全局函数,第一个参数为NULL.
ConsoleConstructor::first会把所有的对象通过一个链表串起来.根据函数名字找到相对应的函数指针,调用即可.
2. 根据类名new相应的类,跟mfc的机制类似.
关键宏:
#define IMPLEMENT_CONOBJECT(className) /
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } /
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } /
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } /
ConcreteClassRep<className> className::dynClassRep(#className, 0, -1, 0, className::getParentStaticClassRep())
这个宏建立了一个类的层次.通过在构造函数中建立类之链表.
链表头定义如下:
AbstractClassRep *
AbstractClassRep::classLinkList = NULL;
动态创建只需要一个个匹配,相等则调用create函数..
virtual AbstractClassRep* getClassRep() const得到该类的所有信息,比如类id,类名和子类,父类的AbstractClassRep等.
通过构造ConcreteClassRep<T>类实现AbstractClassRep之相关接口.
通过virtual ConsoleObject*
create () const = 0;去根据串名动态的创建对象.