介绍一下Torque引擎,包括脚本、编辑器的使用、以及引擎部分。
第一部分:脚本入门
************************
1)打开控制台(console)
运行torquedemo.exe进入主界面或游戏后,按 ~ 键(不用按shift,其实是`键)激活控制台是一个调试程序的好地方。
2)试一下最简单命令 echo()
在控制台键入echo("hello!"); (注意必须加; 就象c++一样)可以看到console窗口就立即显示 hello!别小看echo(),有时为了调试脚本程序,在适当的地方加入echo(),显示有关变量的值,可以监测程序是否正确。使用echo()时,如要输入多行语句,只须连续输入即可,torque会根据 ;来分行执行如:$a=1;echo($a);输出:1
3)torque的脚本script的特点:
a) 无须预定义,即开即用。 b) 变量类型灵活性,如 "12" 与12 是一样的c) 变理大小写不敏感,%a 与%A 是一样的d) 与c++类似,每个语名均须以 ; 作为结束标记
4)变量,torque脚本中,$表示全局变量,%表示局部变量表示法: $a 为全局变量a (在网络中,全局变量只表示在本客户端的全局变量,并不是整个网络)%b 为局部变量b (一般用于function中)
变量类型:a)数字型 1234 (整数interger)1.234 (浮点数floation point) 1234e-3 (科学记数scientific notation
b)字符型 "abcd" (字符串string) 'abcd' (标记字符串 tagged string)
再试一下一个有意思的语句:echo("1"+"2");会输出:3 而不是把2个字符串连接,连接是用 @ 来操作,如: echo("1" @ "2"); 输出:12 这就是torque的script脚本的灵活之处, 更多的字符连接符: @ 连接两个字符串TAB 两个字符串间加入tabSPC 两个字符串间加入一个空格 NL 两个字符串间加入一个加车(换行)注意:这些操作符的英文必须是大写。如:echo("abcd" NL "CDEF");会输出:abcd CDEF
较常用的是字符的颜色处理:
原则上是对后面的字符产生影响,直到一个新的字体色操作符出现,但只对当前行字符产生影响,并不会影响默认字体色。
echo("/c2hello!/c0hello!");前一个hello!会显示红色,后一个hello!会显示正常的黑色。
C)逻辑型 :与其它语言一样,torque也只有两个值 true (1) false (0)
d)数组(arrays)表示法:$AA[n] 一维数组 $AA[M,N] 多维数组 $AA[M_N] 多维数组的另一种表示法, 变量与数组的名字可以相同,但值并不会产和影响:$a=2;$a[0]=3;更有趣的是,torque脚本中,数组可以用如下方法指向:接着上例,你试试:echo($a0);输出的是$a[0]的值,同理,如果要输出$a[0,0],可以用如下方法:echo($a0_0);再次可见,torque的脚本是具有相当的灵活性。
d)矢量(vectors):格式如:"1.0 1.0 1.0 1.0" 矢量是引擎中一个应用很广的变量类型,在3D坐标,动力,粒子,声效等等都有相应的应用。
5)操作符,这方面与C++很相似
6)控制语句,与c++的基本相同
7)功能模块 function
function 功能模块名([参数0],...,[参数n])
{
语句;
[return 返回值;] 可选
}
8)物体定义,介绍了一些基本的知识,这里开始进入torque特有的语法了。在torque中,所有项目都是“物体”,并且所有游戏中的物体都可以访问,如人物,汽车,场景中的物体。且都可以用脚本进行访问,而物体的属性是在引擎中定义的。
我们看一个脚本中的代码(节选自server/scripts/player.cs )
datablock PlayerData(PlayerBody)
{
renderFirstPerson = false;
emap = true;
className = Armor;
shapeFile = "~/data/shapes/player/player.dts";
cameraMaxDist = 3;
computeCRC = true;
canObserve = true;
cmdCategory = "Clients";
cameraDefaultFov = 90.0;
cameraMinFov = 5.0;
cameraMaxFov = 120.0;
debrisShapeName = "~/data/shapes/player/debris_player.dts";
debris = playerDebris;
...
};
datablock的作用是定义物体的有关属性。
这里就是定义了游戏人的人物的有关属性,包括视角定义,3D文件(dts),等等,
再在server/scripts/game.cs 中载入:
...
exec("./crossbow.cs");
exec("./player.cs"); //载入player.cs中定义的物体
exec("./chimneyfire.cs");
exec("./aiPlayer.cs");
...
载入只是预载入内存,并未真正创建,现在看一下在哪里创建了人物player
在game.cs中,可找到有关功能模块:
function GameConnection::createPlayer(%this, %spawnPoint)
{
if (%this.player > 0) {
// The client should not have a player currently
// assigned. Assigning a new one could result in
// a player ghost.
error( "Attempting to create an angus ghost!" );
}
// Create the player object 就是这里创建了player这个物体,并返回名柄给%player
%player = new Player() {
dataBlock = PlayerBody;
client = %this;
};
MissionCleanup.add(%player); //在场景中加入
// Player setup...
%player.setTransform(%spawnPoint); //设定随机出现的地点,地点在场景编辑器中定义,并在function pickSpawnPoint()功能中产生随机点
%player.setShapeName(%this.name);
// Starting equipment
%player.setInventory(Crossbow,1); //初始时的武器设置
%player.setInventory(CrossbowAmmo,10); //初始时的弹药设置
%player.mountImage(CrossbowImage,0); //设置武器在人物的装配点 0表示mount0,1表示装在mount1,由此类推。
// Update the camera to start with the player
%this.camera.setTransform(%player.getEyeTransform()); //设置视角方式
// Give the client control of the player //把控制交给player这个物体
%this.player = %player;
%this.setControlObject(%player);
}
总结一下,创建物体objcect的方法是:
%ver=new 物体类型(名称) %ver 是返回的句柄,是一个整数,是游戏中的客户端唯一值,相当于物体ID。
{
datablock=datablock定义; //这是必须的,datablock可以是自定义的,或是torque已含有的,可参看torque的datablock附表。
内置属性1=初始化值1; //可选,不选则取默认值
内置属性2=初始化值2;
...
自定义属性1=初始化值1; //可选,自定义属性只能在script中应用,并不影响引擎的内置属性
自定义属性2=初始化值2;
...
}; //这个 ;不可少.
注:返回的句柄在客户端是唯一值,但网络下的ID又不同,所以一个物体存在2个ID,一个是本地ID,一个是网络ID,网络ID是
整个游戏网络中唯一的ID号,进行网络游戏编程时应注意,关于网络ID的获得方法,以后再作说明。
Torque引擎系列之Gui编辑器
一、Gui编辑器概述:
1、进入游戏后按F10快捷键即进入Gui编辑器。其界面如下图所示:
2、GUI Editor由4个部分组成:Content Editor(内容编辑器)、Control Tree(控件树)、Control Insepctor(控件检验器)和Tool Bar(工具栏)组成。Content Editor(内容编辑器)为上图中左上方较大的矩形区域,这是放置、移动、和变换控件的地方。Control Tree(控件树)位于上图中的右上方,它显示了当前内容控件的层次结构。Insepctor(控件检验器)是显示所选控件属性的地方,位于Gui Editor的右下角。Tool Bar(工具栏)用于创建、对齐和保存所编辑的控件及Gui。
3、控件属性:Torque Gui中所有控件的属性都包括下面几类:SimBase、Parent、ToolTip、l18N、Dynamic Fields。除了上述几类共有的属性类外,还有控件本身自带的属性类。例如GuiWindowsCtrl控件的Generl属性类、GuibuttonCtrl控件的misc属性类。Parent属性其实就是GuiControl控件的属性,GuiControl类是所有控件的基类,SimBase是GuiControl类的基类,所以每种控件都会继承上述两种基类的属性。SimBasl一栏中的属性一般都使用默认值。
Parent属性类的属性说明如下:
Profile = " GuiDefaultProfile"; //外形
HorizSizing = "right"; //水平对齐方式
VertSizing = "bottom"; //垂直对齐方式
position = "0 0"; //控件左上角顶点坐标
Extent = "800 600"; //控件大小
Visible = "1"; //是否可见
Bitmap = ""; //位图
autoSize = true; //自动大小
maxLength = 255; //最大长度
resizeWidth = false; //是否可以调整宽度
resizeHeight = false; //是否可以调整高度
Command = "quit();"; //点击执行命令
Accelerator = "escape"; //快捷键
ToolTip类属性说明如下:(用于当用户鼠标悬停在控件上时,显示当前选项的提示条)
Tooltip profile = "GuiToolTipProfile"; //外形,用于设置字体大小、颜色等外观
tooltip = "Adjust basic video, sound and other options"; //提示语字符串
hoverTime = 1000;//这个属性一般使用默认值(1000)。
4、Profile参数说明:Profile的主要功能是为GUI提供规则模版,其中定义了界面的字体、字色、是否透明、填充颜色、位图信息、对齐方式、声音(按钮控件)等。
tab = false; //是否可以使用tab键
canKeyFocus = false; //能成为键焦点
hasBitmapArray = false; //是否是位图数组
mouseOverSelected = false; //是否可以是鼠标结束选择
opaque = false; //是否透明
fillColor = "240 240 240"; //填充颜色
fillColorHL = "150 180 200"; //高亮填充色
fillColorNA = "150 210 210";//not active填充色
border = false; //是否有边界
borderColor = "0 0 0"; //边界颜色
borderColorHL = "179 134 94";//高亮填充色
borderColorNA = "126 79 37";//not active填充色
bevelColorHL = "255 255 255";//斜面高亮时颜色
bevelColorLL = "0 0 0";//斜面低亮时颜色
fontType = "宋体"; //字体类型
fontSize = 12; //字体大小
fontCharset = "GB2312";//字体编码
fontColor = "0 0 0"; //文本正常状态下颜色
fontColorHL = "32 100 100"; //文本高亮状态下颜色
fontColorNA = "0 0 0"; //文本not active状态下颜色
fontColorSEL= "0 0 200"; //文本选中状态下颜色
bitmap = "~/client/ui/bitmaps/roots/Window";//位图信息
textOffset = "0 0"; //文本偏移值
modal = true; //是否为模式状态
autoSizeWidth = false; //是否自动调整宽度
autoSizeHeight = false; //是否自动调整高度
numbersOnly = false; //是否只为数字(用于文本编辑框)
cursorColor = "0 0 0 255"; //光标颜色
soundButtonDown = ""; //按钮按下时声效
soundButtonOver = ""; //按钮划过时声效
以上为Profile部分参数及其说明,其中,soundButtonDown、soundButtonOver为按钮控件所属。
5、怎样在引擎中增加某个控件的属性:
void GuiBitmapButtonCtrl::initPersistFields()
{
Parent::initPersistFields();
addField("bitmap", TypeFilename, Offset(mBitmapName, GuiBitmapButtonCtrl));
addField("shineFreq", TypeS32, Offset(mShinefreq, GuiBitmapButtonCtrl)); //闪烁频率属性
}
如例子中所示在initPersistFields()函数中,使用addField方法来添加。
6、保存Gui文件时,文件目录中为什么不能显示GUI文件列表:
最可能的原因是你的工程文件目录带有中文,Torque中文支持不友好,导致不能显示。解决办法:建立工程时,目录中不能含有中文。
Torque引擎系列之粒子编辑器
一、 概要介绍:
粒子系统主要是用来模拟游戏场景中的固定变化的液体或气体类实体,例如大雨、烟雾、薄雾、火焰等。粒子系统主要由三部分组成:
1、粒子(Particle),指我们所看见的真实物体。
2、粒子发射器(Particle Emitter),指使粒子成为存在实体的东西。
3、粒子发射器节点(Particle Emitter Node),指粒子发射器所依附的对象,当粒子作为独立粒子时,必须要用到粒子发射器节点,例如游戏中的火焰等就必须当作是独立粒子。如果粒子是依附粒子,比如水枪喷射的水、火焰上冒出的烟等依附在游戏玩家、武器、交通工具的粒子就叫做依附粒子,这种类型的粒子只需要粒子和粒子发射器来定义。
二、 属性参数:
粒子编辑器主要是用来在场景中动态设置粒子以及粒子发射器的各种属性,所以在使用粒子编辑器之前必须首先熟悉它们各种属性参数所代表的意思。下面是一个简单例子:
//消防员水枪粒子属性
datablock ParticleData(fireManWaterGunParticle)
{
canSaveDynamicFields = "1"; // 能否动态保存参数设置
dragCoefficient = "0"; // 阻力系数(沿发射方向的减速度)
windCoefficient = "0"; // 风力系数(值越大,受风力影响越大)
gravityCoefficient = "0.559219"; // 重力系数
inheritedVelFactor = "0.908023"; // 继承速度
constantAcceleration = "0"; // 沿发射方向的加速度
lifetimeMS = "3616"; // 粒子寿命(最小值为100MS)
lifetimeVarianceMS = "0"; // 随机性粒子寿命(必须小于lifetimeMS)
spinSpeed = "0.01"; // 图片旋转速度
spinRandomMin = "-100"; // 图片最大允许旋转角度
spinRandomMax = "0"; // 图片最小允许旋转角度
useInvAlpha = "1"; // 透明区域颜色切换
animateTexture = "0"; // 为1时,允许使用动画粒子图片纹理
framesPerSec = "0"; // 动画帧速度
textureName = "starter.fps/data/shapes/particles/dustParticle.png"; // 粒子图片路径
animTexName[0] = "starter.fps/data/shapes/particles/dustParticle.png"; // 动画帧
colors[0] = "1 1 1 1"; // 粒子颜色(各个寿命阶段)
colors[1] = "1 1 1 1";
colors[2] = "1 1 1 1";
colors[3] = "1 1 1 1";
sizes[0] = "0.2"; // 粒子大小(各个寿命阶段)
sizes[1] = "0.38";
sizes[2] = "0.33";
sizes[3] = "0.16";
times[0] = "0"; // 粒子寿命的各个阶段
times[1] = "0.5";
times[2] = "0.75";
times[3] = "1";
};
粒子发射器和粒子节点发射器的各项属性说明不在这里作详细介绍,具体参数详细介绍参见开发大全P520~522页。
三、 函数接口
粒子:ParticleData. reload() // 重新载入粒子数据块
粒子发射器:ParticleEmitterData. reload() // 重新载入粒子发射器数据块
粒子发射器节点:ParticleEmitterNode. setEmitterDataBlock(“data”) // 设置粒子发射器datablock,可以用来变换不同的粒子发射器。
四、 爆炸特效
Tge支持爆炸特效,有一个专门的explosion类用来模拟场景中的爆炸效果。下面以一个简单例子介绍一下ExplosionData各属性的意义。
datablock ExplosionData(CrossbowExplosion)
{
soundProfile = CrossbowExplosionSound; // 爆炸声音
lifeTimeMS = 1200; // 爆炸持续时间
// 爆炸火焰粒子效果
particleEmitter = CrossbowExplosionFireEmitter; // 粒子发射器
particleDensity = 75; // 粒子密度
particleRadius = 2; // 粒子范围
// 爆炸烟雾粒子效果
emitter[0] = CrossbowExplosionSmokeEmitter;
emitter[1] = CrossbowExplosionSparkEmitter;
// 摄像机振动效果
shakeCamera = true; // 摄像机是否振动
camShakeFreq = "10.0 11.0 10.0"; // 摄像机振动频率
camShakeAmp = "1.0 1.0 1.0"; // 摄像机振幅
camShakeDuration = 0.5; // 摄像机振动持续时间
camShakeRadius = 10.0; // 摄像机振动范围
// 爆炸碎片效果
debris = CrossbowExplosionDebris; // 碎片data定义
debrisThetaMin = 0; // 碎片最小旋转角度(围绕X轴)
debrisThetaMax = 60; // 碎片最大旋转角度(围绕X轴)
debrisPhiMin = 0; // 碎片最小旋转角度(围绕Z轴)
debrisPhiMax = 360; // 碎片最大旋转角度(围绕Z轴)
debrisNum = 6; // 碎片数量
debrisNumVariance = 2; // 碎片随机数量
debrisVelocity = 1; // 碎片发散速度
debrisVelocityVariance = 0.5; //碎片随机发散速度
// 爆炸冲击力
impulseRadius = 10; // 爆炸冲击波影响范围
impulseForce = 15; // 爆炸冲击力系数
// 爆炸光热效果
lightStartRadius = 6; // 光在爆炸开始时辐射范围
lightEndRadius = 3; // 光在爆炸结束时辐射范围
lightStartColor = "0.5 0.5 0"; // 光在爆炸开始时颜色
lightEndColor = "0 0 0"; // 光在爆炸结束时颜色
};
Torque引擎系列之场景编辑器
一、任务编辑器介绍
Tge任务编辑器主要是用来创建游戏中的场景、地形、设置路径等。它主要包含了以下几个子编辑器:
World Editor(场景编辑器)、Terrain Editor(地形编辑器)、Terrain Terraform Editor(地形高度
编辑器)、Terrain Texture Editor(地表纹理编辑器)、Mission Area Editor(任务区域编辑器)。
二、World Editor使用方法
World Editor(场景编辑器)和Gui编辑器类似,主要包括三个部分:
World Editor Tree:这个树结构显示了任务文件的层次结构和所有的任务对象。
World Editor Inspector:用于显示被选中任务对象的属性。
World Editor Creator:用于创建新任务对象,相当于Gui编辑器中的各种控件。
场景编辑器可以创建的对象类型:
Interiors (大型建筑对象):例如桥梁、水塔、房屋等,一般模型为Dif格式。
ShapeBase objects:例如item、StaticShape、Player、PathCamera、WayPointMaker、
SpawnSphereMarker等。
Mission objects:例如Sun、Sky、Lighting、Water、Terrain、AudioEmitter、ParticleEmitter、Path
、PathMaker等。
在场景编辑器中,可以动态改变物体的大小、位置等。当你点击World Edit按钮后,你会发现自己浮动在
一个大型的棋盘上。这是World编辑器的默认场景。在World编辑器的屏幕的右边,你可以看到一些插件窗
口,我们先暂时把它们关掉,你可以选择Window菜单的World Editor选项关掉它们,或者用快捷键F2。
当我们编辑地形之前,先用点时间熟悉下Torque的四周移动。默认情况,你的视野是一个自由的摄像头,
当你选择“Camera”菜单的“Toggle Camera”快捷键为Alt加C的时候你就转变为游戏者的视觉了。当你
按住右键拖动的时候,你可以环视四周,而W,A,S,和D键让你能在地面上四处移动。这样移动让你可能感
觉到当你在游戏的时候是怎么移动的。在四周走走,并检查一下,一个更酷的方法来检查方法是第三者的
视觉模式,你可以按住右键并按Tab键来进入。
现在,回到宽阔的摄像头视野模式,把摄像头从游戏者视觉脱离可以选择“Drop Camera at Player”选
项(快捷键:Alt+Q)。现在你又是浮动在地面之上了。在这种视野之下,鼠标右键拖动仍然是环视四周
,W,A,S和D键仍然是四处移动,但这是摆脱了地心引力的移动了。你可以通过按Shift加1到7(参考
Camera菜单)来改变摄像头的移动速度。试着移到上面看看地表,当你走失的时候,你可按“Toggle
Camera”回到游戏者的视觉模式。注意:那个灰白色的浮动在天空的正方形是游戏者的出生点。
三、Terrain Editor使用方法
可以使用Terrain Editor 并借助鼠标操作的画笔来手动修改地形高度地图和正方型属性,把摄像头移动
到游戏者出生处的附近,并选择“Window”菜单的“Terrain Editor”选项。现在当你在屏幕中移动鼠标
的时候,你会发现有一群有色的小正方形跟在它周围。那些小正方形是你能调整的地形的区域。在游戏者
模式下点击某处,并上下拖动鼠标你可以看到一个小土墩形状的。漂亮!当你选择“Action”菜单,你可
以看到你刚才在做的动作其实就是“Adjust Height”。
默认的刷子对我们来说大了一点,所以按Ctrl加Z键来撤销我们的小山,并选择“Brush”菜单,点击
“Size 5×5”选项。你可以看到,这就影响了跟随鼠标的红绿正方形的数量。红色表示这个区域受你的
动作的影响较大,而绿色表示这个区域受你的动作的影响较小。现在,通过你的移动和地形编辑器来创建
一个环形的山脉围着你。当你做的时候,你可以随意试验各种各样刷子。
当你对自己编辑的小山满意的时候,选择“Edit”菜单的“Relight Scene”选项来更新光照。注意,当
你的场景重新载入的时候,Relight的是会自动执行的,但当你在编辑器里改变地形并想不重新载入场景
而看到改变的效果的时候,你需要手动更新光照。现在选择“Camera”菜单的“Toggle Camera”并在游
戏者的视觉里四处走走。
注意:当你编辑地形的时候,可能会出现这种现象,游戏者脚下的地表调整后,游戏者会掉进地表。无论
你是否遇到过,试试这种情况,按“Camera”的“Toggle Camera”选项回到摄像头模式,并移动摄像头
到地表上方,选择“Camera”的“Drop Player at Camera”选项(快捷键为Alt + W),你可以看到你
又回到了游戏者的视野,在你摄像头的地方落下来的。
在我们继续之前,先选择“File”菜单的“Save Mission As…”选项,选择“CameOne/data/misions”
目录,并保存你的工作为“GameOneMission.mis”。
四、地形上色
现在我们准备好进入地形上色部分。回到摄像头视觉并移动到能很好地看到这个山环地地方,并选择
“Window”菜单的“Terrain Texture Painter”的选项,我们现在要干的第一件事就是把我们的场景从
这个棋盘的地表替换成更接近大自然的地表。按屏幕右侧的纹理窗口中的棋盘子窗口下的“Change”按钮
,选择GameOne/data/terrains目录下的sand.jpg纹理并载入,看,是不是很酷呀?
现在我们来绘制一些绿草地表。点击sand纹理下面的空纹理的“Add…”按钮。注意,在Terrain Texture
Painter中纹理的添加一定要按顺序的。选择“patchy.jpg”并点击Load。点击“Action”菜单你可以发
现你的当前动作被自动地设置为“Paint Material”。现在,当你点击地表的时候,你的刷子区域已经被
绘制成“patchy.jpg”纹理了。我们选择更小的刷子来绘制我们的山地表。你可以看到,绿草区域已经自
动和临近的沙子混合好了。
五、放置物品
我们的游戏开始集合更多的东西了。让我们继续添加一些物品到这个游戏世界中来。在游戏世界中添加和
移动物品的时候,用摄像头视觉操作会方便一些,如果你还在游戏者的视觉的话,按Alt加C键。选择
“Window”菜单的“World Editor Creator”选项并看看右边的窗口。上面的窗口和“World Editor
Inspector”模式一样,用树形结构显示你的场景中已经有的物体,而在下面的“Creator window”显示
出一列可以放置在你的场景的东西。你可以在“World”菜单中看到,默认的放置物品的模式是“Drop at
Screen Center”,所以调整好你的视觉让屏幕中央对准好你要放物品的地方。
现在我们选好要放的物品。在右下方窗口,用鼠标点左边的加号展开Shapes表,再展开Items。再这里,
你可以看到TorqueLogoItem物品。点击那个对象会在屏幕的中央创建一个新的Logo物品。那个Logo出现后
,有个一个黄色的盒子包围着它,这就表示它被选定的。在它下面的数字是它的ID号,(null)是这个物
品名字的占位符,那里还有三根蓝色的线在X,Y和Z轴上,它们可以被按住并拖动这个物品。试试按物品
本身拖动它以及按坐标轴来拖动它。
现在选择“Window”菜单的“World Editor Inspector”我们会发现一个新的调整场景中物品的方法。在
那个树形显示的窗口中,你可以发现MissionGroup表,它包含我们任务中的所有物体。如果它尚未展开的
话,点击它左边的加号你就可以看到组成这个场景的物品了,比如太阳,天空等。那个在底下的
“StaticShape”物品就是我们刚创建的Logo物品。点击它,右下方的窗口就会显示这个Logo物品的所有
属性。注意:如果在你打开inspector窗口之前就已经选定了这个“StaticShape”的话,你要点一下这个
树形框中的其他物品,再点这个StaticShape就可以看到它的所有属性了。把scale从“1 1 1”改变为“1
1 10”并按“Apply”按钮看看。是不是有点酷呀?好,不过这个可能不是我们的目的……,按Ctrl加Z撤
销这个操作。还有一些其他有用的操作比如命名这个物品,在“Apply”按钮的旁边的文字输入框就是保
存物品名字的,在那输入“logo”并按“Apply”。在这个教程中,我们来做一个搜集logos的小游戏,所
以继续在场景中放置5到10个logo。
你刚才放置的像logos这样的小物品在Torque中被叫作“shapes”,并且基于.dts文件。Torque中的另一
种主要物体类型是“interior”基于.dif文件。规则是当一个物品大得足够走进去并走出来的时候,你就
要用interior了。让我们放置一个吧。先依次展开World Editor Creator 窗口的
Interiors>GameOne>data>interiors表,就像logos一样,点击“box”我们的interior就会出现在屏幕中
央了。Interior就像地表形状一样,你必须relight场景来看它们更新的纹理映射。一个对话框会出现提
示你这么做。简单地选择“Relight Scene”你就会看到这个盒子地外形了。注意,当你发现自己陷进这
个interior内部的时候,你可以Toggle Camera走出来。
创建这个interior盒子并照亮它之后,你要移动它的Z轴让它的底部在地面上。如果你的盒子在山上的话
,先把它移动到一个平坦的地方。当你调整好以后,切换到游戏者视觉,并确定你能走进去。门在一面的
下面,所以你可能要绕这个盒子走走以找到可以进去的门。最后,旁一些logo在buliding里面。你还可能
需要手动拖动这个建筑物。保存你的工作,我们继续。
六、组织对象
现在我们小谈一下组织部分。先选择“Window”菜单中的“World Editor Creator”选项,并展开
MissionGroup。在列表的下面你可以看到刚才放置的那些logos和建筑。你可能会这样想,在一个完全的
游戏中,这种列表会变得非常大,以至于难与管理。Torque允许你用“SimGroups”(一种像目录一样的
东西)来组织你的物品。
要创建一个SimGroup,展开Creator Window里的“Mission Objects”目录的“System”目录,在里面你
可以看到SimGroup,点击它。现在你可以看到一个对话框要你输入这个新SimGroup的名字。填入“logos
”并点击OK。现在在上面的树形结构中,把你的所有logo物体都拖到Logos目录(SimGroup)下。在整理
的过程中,一个很好的做法是把所有的物品都命好名,有些描述总是最好的,做好以后,把那个建筑物也
命好名,并为它建立一个名为“building”的SimGroup。
Torque引擎系列之核心类(ConsoleObject)
TGE引擎中,ConsoleObject类是所有核心类的基类,所有在脚本中用到的类都必须继承于该类。该类主要提供脚本动态创建对象、类属性脚本初始化等功能。在前面的脚本入门介绍中,我们知道在TGE脚本中通过“new”关键字来创建动态对象,并进行初始化。那么在ConsoleObject class中是如何实现这一点的呢?
当脚本想要创建一个对象时会把一个类名字符串传递到引擎中,比如当脚本中调用 new Player{}时,类名就是“Player”字符串。于是问题就在于怎么通过这个类名字符串new出该类的一个对象。我们很自然的会想到必须建立一个类名和类的关联表,创建的时候通过类名映射到类,进行创建。TGE引擎的思想基本也是如此,但是在实现上做的很有技巧。
TGE中使用了代理模式,当我们定义一个脚本类(在脚本中能够访问的类)时,需要加入一个宏:IMPLEMENT_CONOBJECT(className),可不要小看这个宏。通过它,引擎会生成一个代理类,并把这个代理类注册到一个链表中。ConcreteClassRep代理类是一个类模板,模板参数就是实体类的类名。于是,当我们需要创建对象时,只需要通过类名在链表中查到给类名对应的代理类,然后通过该代理类利用模板机制就可以进行创建了。
我们在脚本中创建游戏对象的同时,必须给对象进行初始化。引擎通过initPersistFields()函数给脚本公布类属性接口consoleObject的每个子类都必须重写该方法。另外我们还可以通过consoleInit()函数给脚本公布全局变量接口。给脚本公布类函数使用:ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage1) 宏。公布全局函数使用:ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) 宏。
总结:在TGE中,任何你需要在脚本中用到的类都必须继承鱼ConsoleObject类,它实现了引擎和脚本的接口。后面,我会继续分析它的重要子类。
Torque引擎系列之类图
一、simObject的作用:
在游戏中,有许许多多的对象,包括人物、交通工具、场景中的建筑、太阳、天空等。所有的这些对象在游戏中必须要有一个管理者,而simObject最主要的作用就是提供了一个游戏对象管理机制。另外它还对ConsoleObject做了一些扩展,例如可以给simObject对象添加动态属性。
二、simObject对象生命周期管理:
在现实生活中,政府为了便于管理,都会给我们每个人分配一个唯一的身份证号。同样,在TGE引擎中,每个新创建的对象也会被系统分配一个id号。通过这个唯一的id号,我们能方便快速地找到对应的object。在引擎中是通过registerObject这个函数来注册对象的,如果注册成功,会分配一个唯一的id号。在TGE中,维护了两张表,一个是name表,另一个是id表。每次注册,都会在这两张表中添加一个记录。另外在该函数中还会调用onAdd函数进行初始化。当我们通过deleteObject函数删除某个对象的时候,会调用unregisterObject函数,在两张表中清除掉该对象的记录,并调用onRemove函数。onAdd和onRemove是两个非常重要的虚函数,基本上每个simObject的子类都必须重写这两个函数。
三、simObject的动态属性
在ConsoleObject中,我们可以通过initPersistFields函数定义类的静态属性。但是仅仅有静态属性肯定是不够的,很多在脚本中用到的对象属性都必须是动态的,simObject类就提供了动态属性这个机制。例如我们创建了一个名叫player的对象,然后要在该对象中添加一个状态属性只需要这样定义:player.state = "root",是不是很简单?以后你也可以随时在脚本里面更改这个属性的值。我们要注意的是这个属性只是在服务端或者客户端有用,如果你只是在服务端更行了这个对象的属性,对应的客户端对象属性值并不会自动更新。在引擎中想要更改或者获得该属性的值可以通过调用getDataField和setDataField方法。这两个方法对静态属性也有效。当我们调用setDataField方法时,产生一个回调函数通知某个属性值已经改变。如果是静态属性则是onStaticModified函数,动态属性是subscribedFieldIsChanged函数。
NetObject类
在上篇文章中,我介绍了一下SimObject类,这篇文章主要是介绍一下它的子类。在所有simObject的子类中,NetObject类是最重要的子类,不过其他的类例如SimSet、SimDataBlock、ScriptObject、ScriptClass都是比较重要的类。在介绍NetObject之前,我们先研究一下这几个类。
一、SimSet class
SimSet是一个容器类,准确的来说是一个存储simObject对象的容器类。和所有的容器类一样,该类提供了基本的增加、删除、访问、寻找等操作。内部的实现方式是一个动态数组。使用simset的时候,有几个地方需要注意一下。1、simset容器中的所有元素不是该容器所独有的,就是说一个元素可以被添加到多个simset容器中。2、当一个simset对象destroy后,里面的元素并没有被destroy。从这两点可以看出,simset里面包含的元素实际上都是simobject的指针。添加的时候,只是把指针复制到该容器中。另外从simset类中进化出一个simGroup子类,simGroup与其父类主要的不同点是simGroup类中的元素是独有的,同时只能被一个simGroup容器包含。simGroup添加元素之前首先会检查该元素是否在别的容器中,如果在的话,会先从之前的那个容器中remove掉,然后再添加到当前容器中。simGroup有一个非常重要的子类就是GuiControl,该类是TGE中所有界面控件的基类,以后有机会我会详细介绍一下TGE的界面控件类。
二、simDataBlock class
我们知道,游戏中的对象例如一个player对象,有许许多多的属性,包括名字、生命值、模型、质量、速度、位置等。在游戏中,我们要时时刻刻进行对象的更新,实际上就是对这些属性进行更新。但是我们知道有些属性例如人物的模型、质量、重力加速度等属性是一些固有属性,在游戏过程中不会改变的属性。这些属性只需要在游戏加载的时候一次性进行初始化,以后不需要更新。这些属性抽象出来,就是simDataBlock。该类有几个重要的函数:preload(),packData()、unpackData()。这三个函数中,第一个函数对于服务端和客户端都有用,主要是在加载datablock之前进行属性的错误检查和数据解析。packData函数只在服务端执行,负责发送datablock。unpackData函数在客户端执行,负责接收datablock。
三、ScriptObject class
在脚本中,我们很多时候需要定义一些简单的对象,例如技能对象、任务对象。TGE为我们提供了ScriptObject类,能够方便的让我们定义出自己所需要的对象。ScriptObject中有两个属性非常重要,一个是class,另一个是superClass。下面举个简单的例子说明一下:
- new ScriptObject(MyObject)
- {
- class = Bar;
- superClass = Foo;
- };
- function MyObject::doSomething(%this)
- {
- echo("Hi,MyObject!");
- Parent::doSomething(%this);
- }
- function Bar::doSomething(%this)
- {
- echo("Hi!Bar");
- Parent::doSomething(%this);
- }
- function Foo::doSomething(%this)
- {
- echo("Hi! Foo");
- }
- function Bar::go(%this)
- {
- %this.doSomething();
- Parent::doSomething(%this);
- }
执行MyObject.doSomething(),输出:"Hi,MyObject!" "Hi!Bar" "Hi! Foo"
执行MyObject.go(),输出:"Hi!Bar" "Hi! Foo"
上面介绍了simObject的几个比较重要的子类,下面我将着重介绍一下netObject类
TGE引擎是一个基于C/S架构的引擎,在这类引擎中。我们要求所有的游戏对象在服务器端进行集中管理,然后在每个客户端进行拷贝。逻辑处理都是在服务端进行,然后把数据更新到每个客户端。为了支持服务端与客户端的这种通信,TGE引用了netObject类。
我们先来看看TGE中服务端与客户端进行通信的流程吧。首先在我们进游戏之前,会有一个加载过程。在这个过程中,服务器会把所有的datablock克隆到所有与该服务器连接的客户端,datablock包括对象的静态属性,即你不需要在游戏过程中进行实时更新的属性。进入游戏后,服务端会有一个管理所有游戏对象的列表,这个列表包括了所有会在游戏中进行更新的对象。当一个客户端的camera进行移动时,会对场景中的物体进行裁剪。从渲染层来看,不在视野范围内的物体是不会被渲染的。当然,这些物体也不需要进行更新。引擎会定时检测摄像机的视野,然后把在视野范围内的对象加入到一个列表中,服务器会发送更新包对这个列表中的对象进行更新,包括对象的位置、动作等等。另外为了提高通信的效率,节省网络带宽,TGE中使用了脏技巧:把对象需要更新的属性分为几类,例如位置、动作、伤害等,每类用一个mask来表示,服务器中某个对象的某类属性需要更新,就把对应的标志位置为脏,然后传送到客户端,客户端根据mask来更新,这样就有效的节省了带宽,提高了性能。
我们了解了TGE中的通信流程后,就能很好地理解netObject类了。该类中最重要的两个函数是packUpdate和unpackUpdate,packUpdate负责服务端发送更新包,unpackUpdate负责客户端接收更新包。这两个函数都是虚函数,netobject的每个子类都必须对该函数进行重写。另外还提供了setMaskBits、clearMaskBits等辅助函数。
Torque引擎系列之核心类(SceneObject)
前面我介绍了一下netObject类,这篇文章我主要介绍它的子类SceneObject,另外两个子类Sun和MissionArea比较简单。在这里就不多介绍了。从名字就可以看出,SceneObject主要是指场景对象类,包括场景中的天空盒、地形、建筑、人物、交通工具、水体等。SceneObject class 在TGE中主要提供了以下几个功能:1、每个该类对象创建之后都会加入到一个scene graph中。引擎可以通过scene graph高效率的进行物体渲染,用的是BSP算法。2、提供了一些非常重要的辅助函数,例如设置(获取)位置、速度、碰撞盒、大小、重量等函数。3、给物体提供了光照。4、提供了碰撞检测。
一、碰撞检测
在游戏中,我们经常需要用到碰撞检测。例如我们用枪发射一枚子弹,就必须利用碰撞检测来检查子弹是否击中了敌人。在TGE中使用containerRayCast函数来进行检测。基本原理是:引擎有一个全局的碰撞盒容器,每个sceneObject被创建的时候,不仅会被加入到scene graph进行渲染,而且会被加入到一个碰撞盒容器中,物体的碰撞盒是从模型中解析的。当我们用枪发射子弹时,实际上是在碰撞盒容器中产生一条射线。再利用线面相交检测算法,检测到哪个面最先与射线先交,从而得知该射线所碰撞到的物体。顺便说一句,TGE中做一次containerRayCast是很耗计算资源的,特别是在大场景中。所以能不用尽量不要用这个函数。另外TGE中还是先了碰撞盒与碰撞盒的碰撞检测,具体的实现方法就不多说了。
二、辅助函数
SceneObject给脚本提供了一些辅助函数,例如getObjectBox(获取局部坐标系下的碰撞盒)、getWorldBox(获取世界坐标系下的碰撞盒)、getForwardVector(获得向前的速度)、getTransform、getPosition、setTransform、getScale等。利用这些函数我们可以在脚本中对游戏对象进行基本的变换,包括对象的位置、朝向、大小等。
三、场景渲染
TGE的渲染流程:GuiTSCtrl::onRender(主界面渲染、每帧调用一次) ——》EditTSCtrl::renderWorld(渲染场景)——》SceneGraph::renderScene(场景树渲染)———》sceneObject::renderObject(每个物体渲染)。游戏的每次渲染首先是由主界面发起,然后调用客户端的全局SceneGraph进行场景渲染,在SceneGraph会根据摄像机的视角进行裁剪,最后再进行单个物体的渲染。每个SceneObject的子类都要重写renderObject函数。
Torque引擎系列之核心类(GameBase)
上篇文章介绍了sceneObject,我们知道sceneobject是一个抽象类。场景中的物体,包括天空、地面、水体、建筑、植物、人物、交通工具、摄像机、标记点、声音源等都是建立在sceneObject class之上。所有这些物体在TGE中都对应了一个实体类。游戏中的类是对现实世界物体的抽象,我们想一想,上面这些物体中怎样去抽象。首先我们可以把物体分为静态物体和动态物体,像天空、地面、建筑、水体这些东西都可近似归为静态物体。而人物、交通工具、摄像机可以归为动态物体。动态物体在游戏中,我们必须实时进行计算更新,包括位置、速度、动作等。而静态物体我们只需要负责进行渲染就可以。基于这个思想,TGE中把动态物体抽象成GameBase类,而另外一些静态物体则直接在sceneObject上进行扩展,例如Sky、TerrainBlock、WaterBlock、TSStatic、Marker、InteriorInstance。下面我会着重讲解一下GameBase类。
一、GameBaseData
在介绍GameBase类之前,我想先介绍一下GameBaseData类,这个类继承于SimDataBlock。GameBaseData实际上就是GameBase类的静态属性,而且GameBase的所有子类都会有一个Data类,例如Player会有一个PlayerData类,在GameBaseData中只有两个简单的属性:category和className。category用于在场景编辑器中进行分类,className用于指定类名。
二、GameBase核心函数
上面我们提到GameBase代表的是动态物体,既然是动态物体,那么我们就必须在游戏中进行实时更新。在GameBase中有三个函数提供了基于时间的运算更新。这三个函数分别是:processTick(Move*)、interpolateTick(float)、advanceTime(float)。这三个函数在引擎中的调用频率是32ms调用一次,也就是说平均一秒调用32次。第一个函数在服务器和客户端都能执行,主要用来更新物体的位置、状态等。interpolateTick函数只在客户端执行,主要用来进行插值计算、advanceTime也只能在客户端执行,用来更新动作。这三个函数的调用都是在ProcessList类中的函数中调用的,ProcessList是一个和GameBase密切相关的一个类,ProcessList可以说是GameBase对象的一个管理器。在TGE中有两个ProcessList的全局对象,分别是gClientProcessList和gServerProcessList,负责客户端和服务端的GameBase对象管理。每个GameBase对象在创建时都会被加入到一个gClientProcessList或gServerProcessList中,引擎就是通过这两个链表对所有的GameBase对象进行管理和更新的。
Torque引擎系列之核心类(ShapeBase)
在游戏世界中,我们不仅可以把物体分为静态的和动态的,而且可以分为有形的和无形的。游戏中的人物、交通工具、建筑都属于有形物体,而一些爆炸、激光、烟雾等粒子效果则是无形的,实际上这里的无形是指没有固定的形状。有形物体需要美工进行3D建模,另外可以挂接和被挂接、可以有动作和生命值。TGE中把这些特性抽象出来形成了ShapeBase类,可以说ShapeBase类是TGE中最重要的一个类,也是我们经常需要修改的类。它是游戏中很多重要实体类的基类,例如Player、Vehicle、Camera、Item.下面我将介绍一下shapeBase类的几个重要特性。
一、挂接和被挂接
在游戏中,我们经常需要把一个物体挂在另一个物体上。例如使用道具、换装、骑马等。在TGE中有两种挂接的方式,分别是mountObject和mountImage。前者主要用来挂接大型物体,而且被挂接的物体可以在场景中单独存在。而后者主要用来挂接一些小型物体(实际上只是图片而已),例如武器、帽子、衣服之类的东西,这些物体不能在场景中单独存在,只能依附于挂接物体。void ShapeBase::mountObject(ShapeBase* obj,U32 node)函数中,第一参数是被挂接物体,第二个参数是挂接点,挂接点需要美工在模型制作中加入。物体被加载的时候在onAdd函数中解析出来,注意物体被挂接时是被挂接物体的原点挂接在node上。而mountImage函数,我们可以在被挂接物体上指定mountPoint。
二、生命值和能量值
在shapeBase中,增加了两个重要的属性。一个是生命值,一个是能量值。并能够在界面中进行血条和生命条的绘制。shapeBase中把物体分为三种受伤状态,Enabled、Disabled和Destroyed。对应有disabledLevel和destroyedLevel,当物体当前的生命值到达某个level后,会进行状态的转换,并执行相应的回调函数。例如Disabled时,可以让人物倒地,或者车辆爆炸等。
三、播放动画
在脚本中,我们可以调用playThread函数播放指定的动画,另外调用setThreadDir函数可以控制动画的播放方向,即从前往后播或者从后往前播,另外调用pauseThread可以暂停动画。在动画播完后,还可以定义相关的回调函数。