Torque脚本启动调用流程
经常有朋友问如何学习TGE脚本(TGEA的脚本大致与TGE相同).一方面是看书和官网的文档,另外最有效最重要的就是看引擎所带的那几个范例了.
初学者看到那么一大堆脚本文件,肯定会有疑问,为什么某些代码要添加在特定的地方呢?各级子目录中有 许多脚本文件,他们是按什么顺序执行的呢?最终又是如何进入游戏场景的呢?
为了解答这些问题,需要研究TGE脚本的运行流程。了解了脚本流程后,更加利于研究example下自带的例子的代码,从中吸取经验,学习。
这是以前写的一篇文档,引擎版本为TGE1.5.2.整理了放在blog上,希望对大家有点用.
在介绍Torque脚本流程之前.先介绍一些基本知识。Torque Script是Torque自带的一个脚本语言,C/C++风格,面向对象,语法规范和C/C++类似,解释执行。
脚本中一个很重要的命令的exec,这个命令的作用是执行脚本文件。如下:
A遇到语句则执行。
B遇到对象或是函数定义则加载(这个命名是我自己这么称呼的,不准确-。-!)。也就是你要使用你定义的函数或是对象,必须先用exec将包含相应定义的文件载入,然后才可以使用。
我把脚本的流程分为两个部分. 一是引擎(TGE.exe)启动后,会加载根目录下的main.cs,执行,然后根据main.cs开始加载执行整个游戏的脚本代码----脚本初始化流程。
二是当进入游戏窗口时,出现主界面,点击确定后,载入地形,进入游戏场景,开始游戏逻辑等----游戏逻辑启动。
一 根目录下的”main.cs”开始,脚本初始化流程
我们从根目录的main.cs开始分析。因为当TGE.exe启动时,会自动加载根目录下的“main.cs”,所以根目录下的“main.cs”是脚本的起始点。
1 开始都是一些变量的赋值与函数定义,跳过。比较重要的变量赋值语句如下:
$defaultGame = "tutorial.base";
$userMods = "creator;" @ $defaultGame;
此时$userMods的值为"creator;tutorial.base "。
2 然后loadMods($userMods);这个语句开始加载mod,重要。
见函数定义:
function loadMods(%modPath)
{
![](/Images/OutliningIndicators/None.gif)
%modPath = nextToken(%modPath, token, ";");
if (%modPath !$= "")
![](/Images/OutliningIndicators/None.gif)
if(exec(%token @ "/main.cs") != true)
{
![](/Images/OutliningIndicators/None.gif)
$modcount--;
}
}
nextToken的定义如下:
Tokens
nextToken( tokenList , tokenVar , delimeter )
Purpose
Use the nextToken function to get the first token found in tokenList, where tokens are
separated by the character(s) specified in delimeter. The token itself is stored in a
variable whose name is specified in tokenVar. This function provides complex power in a
simple package. Please read the notes below, they are very important.
Syntax
tokenList – The string containing token(s).
tokenVar – The 'name' of the variable to store the token in.
delimeter – The character(s) to use as a delimeter. A delimeter may be a single
character, or a sequence of characters.
Returns
Returns a copy of tokenList, less the first token and the first delimiter. If there are
no more tokens, a NULL string is returned.
因此递归调用loadMods,逆序执行$userMods中的各个mod,即先执行“tutorial.base”,后执行“creator”,又见“tutorial.base”的main.cs,发现开头有:
loadDir("common");
加载任何一个mod都会先加载common。
因此各个mod中的main.cs的调用顺序如下
Exec common main.cs
Exec tutorial.base main.cn
Exec common main.cs
Exec creator main.cs
在这里我们要介绍一下package。
注意区别mod 和package。mod是一个目录,每个mod目录下都会有一个main.cs,为了方便起见,我们把根目录称为根mod(虚mod)。每个mod的main.cs中都定义有一个Package,它是一个结构,可以认为是一堆函数定义的集合。如common的main.cs中有:
function displayHelp() {}
function parseArgs(){}
function onStart(){}
function onExit(){}
};
又如tutorial.base的main.cs
package ttb
{
function onStart(){}
function onExit(){}
};
Package的工作原理类似栈。但使用ActivatePackage命令激活一个包时,就会将这个包压入栈。例如你调用onStrat()函数时,调用的就是栈顶的那个包中的onStrat()函数定义。
可以看到,不同mod下的包的函数名都是相同的。所以我认为package可以提供函数的“继承”“和多态”。通过ActivatePackage与deactivatePackage来控制当前栈顶的package.同时也可以通过Parent::来调用栈中上一级包中定义的同名函数。
看完了包之后,我们继续回到脚本流程。各个mod的main.cs的执行顺序如上,但是加载mod(实质是指加载package)顺序不是这样的。
关键在于那个ActivatePackage,当激活一个package时,这个package就被压入栈,但是如果栈中已有同名的package时,则不会将同名的package压入栈。
所以common包只加载一次。最终栈中的package的顺序如下,creator在栈顶:
根—common—tutorial.base—creator
3 继续看代码:
{
enableWinConsole(true);
displayHelp();
quit();
}
else
{
onStart(); //各个mod的起点
echo("Engine initialized");
}
表面上看此处的onStart()调用的是creator包中的onStart(),因为creator位于栈的顶端。但是并不是这样的,见creator包中的onStart()定义:
{
Parent::onStart();
…
}
所以实际上各个mod的的onStart()的调用顺序为:
根mod的onStart()--common—tutorial.base—creator
而具体每个包的流程从他们的onstrar开始~~~~
各个mod的onStart()大家可以自己去看,多为加载脚本,初始化,并没有什么特别的功能函数。这里注意下tutorial.base的onStart()
onStart()
-->initClient()
加载GUI文件的语句就放在这里。
二 游戏逻辑启动
在开始之前,再总结一下TGE脚本的重载。有两种,一种是利用package的函数重载。多个package中定义的同名函数,调用的是当时激活的package的函数。但是也可以通过parent来调父级的函数。二是依靠exec的顺序。如果两个同名的函数先后被exec,那么后被exec的函数有效。例如:common/server/ game.cs tutorial.base/server/game.cs中均有onServerCread(),则执行的是tutorial.base下的。因为tutorial.base下的脚本文件后被exec.
这个流程的启动是由主界面上的按钮enter调用loadMyMission()函数开始的,我们从这个函数开始:
{
// make sure we are not connected to a server already
disconnect();
// Create the server and load the mission
createServer("SinglePlayer", expandFilename("./data/missions/flat.mis"));
// Make a local connection
%conn = new GameConnection(ServerConnection);
RootGroup.add(ServerConnection);
%conn.setConnectArgs("Player");
%conn.setJoinPassword("None");
%conn.connectLocal();
}
通过调用这个函数,开始创建服务器,创建客户机,并让客户机连接服务器,开始游戏.
(1) 下面开始分析,侧重control包,common包对于初学者来说,并不需要投入太多精力去研究,仅供参考.
(2) 初学者并不要求对整个流程都了解,只需了解需要加的功能的脚本,和插入点即可.
(3) 另有些函数在common与T中均有定义,重载.若T中有定义则执行T,若无则执行common中的.这种函数的重载与package的重载不同,package可通过parent::来调用父级的函数定义.
1 createServer("SinglePlayer",expandFilename("./data/missions/flat.mis"));
createrserver() //位于Serve: common/server/server.cs
-->destroyServer() //先销毁服务器
-->onServerCread() //两处重载: common/server/ game.cs tutorial.base/server/game.cs
//执行的是tutorial.base下的.作用是加载游戏逻辑的脚本(exec)。(在tutorial.base中看不出来,因为它是最简的,可以参starter.fps中的例子)
-->loadMission() //服务器加载地形,位于Server: common/server/missionload.cs
-->endmission() //先结束
-->将mission信息发送给客户端
-->loadMissionStage2()
-->onMissionLoaded() //两处:common/sever/game.cs,game/sever/game.cs
//在tutorial.base中很重要,可放置游戏逻辑脚本(参fps或别的游戏的例子),调用GameMgr启动,逻辑代码启动.
2
%conn = new GameConnection(ServerConnection);
RootGroup.add(ServerConnection);
%conn.setConnectArgs("Player");
%conn.setJoinPassword("None");
%conn.connectLocal();
创建一个客户端的连接,并将这个连接连到服务端上,开始下载mission到客户机上,开始游戏。
这个函数会引起引擎执行一系列函数。最后回调脚本的GameConnection::onConnectRequest()----从这里开始下载mission到客户机。
暂略:涉及到common及引擎源码层,较复杂,以后有空再说吧。
最终到达:
Server: common/server/missionload.cs
serverCmdMissionStartPhase3Ack()
-->GameConnection::startMission() 位于Server:common/server/clientconection.cs
-->GameConnection::OnClientEnterGame()
位于Server:game/server/game.cs中,重要,用于新建玩家,摄像机等.