|
引言:
本文首先通过简单的入门脚本讲述了LSL程序开发的基本概念和知识,然后在进阶部分创建了一个交通工具的实例,并对这个例子进行了详细分析和解释。
在第二人生中进行脚本编程对于那些没有编程经验的人来说是非常复杂的,就是对于有编程经验的人也是一个挑战,因为它和其他通常的编程语言不同,用到了“状态”。
入门:编写第一个脚本
既然要开始介绍编程基础,那么就要从基本元素开始,不太复杂的例子,因为它就能概括一切了。首先创建一个立方体,进入编辑框的Content属性面板,按下new script按钮。当你打开脚本的时候,你将看到如下内容:
default { state_entry() { llSay(0, "Hello, Avatar!"); }
touch_start(integer total_number) { llSay(0, "Touched."); } } | 这是个非常简单的程序,每个脚本都会自动创建它。当进入到这个世界里,脚本会说:“Hello,Avatar!”,当被出没的时候,说:“Touched”。试一下吧。点击你的立方体。它应该说被点击了。
函数/状态
好了,这是怎么工作的呢?在LSL术语里,default和主“函数”或“状态”类似,是每个事物开始的地方。函数“state_entry()”和“touch_start()”和开关一样,当它们被打开时在大括号中的事件发生。“state_entry()”意思是每次脚本进入到世界中,它被调用或者“打开”。“touch_start()”意思是每当物体被触摸时被调用。在两个大括号里,都有一个IISay()的函数。IISay是说一些东西。第一个数字是使用的频道(有几百万个频道可用,但是一般avatars只在0频道交流,因而大家可以通过共同的0频道听到别人说的话)。第二个参数是要说的内容字符串。一个字符串基本就是一系列字符,通过””定义。注意参数是被逗号隔开的。
这是对你的第一个第二人生代码的总览。下面,你将学习关于分支和变量的知识。
注释
什么是注释?注释就是帮助你理解别人或自己代码的多余说明。它帮助人们了解代码正在做什么,你可能在阅读几个月前的代码时忘记了编码过程,需要用到它。这里只有一种单行注释。如果你需要一个长注释,你需要将多个单行注释并在一起。单行注释使用“//”定义,右边的所有内容是“注释”。注释也被用来忽略掉一行(或多行)代码。它在调试过程中非常有用。
还要说明的是注释帮助人们理解,所以不要注释那些显而易见的内容。一个长代码即使是使用良好的编码排版也容易引起误解,所以不要认为排版了就不需要注释了。
空白
空白是在行行代码间多余的空行。你可以注意到每行最后都有一个分号(;)。这是显示行结束的标志。使用分号,你可以在一行放置多行代码。但是,不推荐这样因为比较难以阅读。另外注意函数和状态块不需要最后的翻好。因为他们在继续其他的工作,分号意味着“这行已经结束了”。当然,你也可以将一行代码分为2行。但是要注意两件事情:
·字符串不能被分隔开;
·函数参数不能被分隔开,一个参数不能在两行出现;
使用空白,你可以有一个便于阅读和理解的编程风格。有一些程序员选择使用的风格,也有一些他们必须遵循的风格:
·分号必须是行尾的标识,除了在注释内;
·如果一行太长你分开了它,那么第二行必须有一个缩进;
·对于每个块(循环,函数等),代码都必须进行缩进;
排版命令是可以用在状态和函数上的(还有循环,以及{}内的内容)。最流行的风格是(可以用LSL脚本编辑器自动排版):
//Here is a single line comment //Here is a state definition with format one
default{ //The code that should be here } //Notice how the '{' is right beside the state name
//Here is format number 2
//The format is one I use more, and also the one //a lot of script editors use. I recommend using this one, not //only because it is easy to read, but also because it //is the default format in the LSL script editor
//Here is a state definition with format one
default { //The code that should be here } //Notice how in this case, the '{' is after the state name //This method makes it generally clearer to see the functions blocks //Also note how the code inside is indented, which helps programmers easily //see where the code blcoks are. | 使用变量
变量保存着大量的数据。你需要使用它们来记录事情。比如,如果你正在做一个步进计数器,你需要记住上一个数值,因此可以保持增加步长。这就需要变量的使用了。变量可以是数字,小数,字符串,和其他LSL(林登脚本语言)所支持的数据类型,诸如vector和rotation。
为了创建变量,首先需要定义。下表列出了一些数据类型:
|
数据类型 |
定义 |
| 整数 |
从-2147483648到2147483647的非小数数值。它用来技术和保存重要数字,因为比浮点型精确。 |
| 浮点数 |
浮点数是有小数部分的数字,没有整数精确。范围在1.175494351E-38 和3.402823466E+38之间 |
| Vector |
向量是一组3个数字,通常用来标识RGB杨色或者xyz坐标。 |
| Rotation |
Rotation用来标识回转和名称。包括x,y,z和s数值。 |
| Key |
Key用来应用第二人生中的对象,比如用户,一个物体等等。 |
| 字符串 |
一个字符串是一组字符,用做语言交流。 |
| 列表 |
列表是一些变量的集合。 | 下面是个例子:
integer counter = 0; default { state_entry() { llSay(0, "Hello, Avatar!"); }
touch_start(integer total_number) { counter++; string touchtimes = "Touched " + (string)counter + " times"; llSay(0, touchtimes); } } | 这个程序是一个触摸计数器。每次你点击一下立方体(触摸),计数器增加。它告诉你触摸了该物体多少次。
声明变量
变量通过如下格式声明:
[type of variable] [name of variable]
例子:
· integer myNumber · float hello · string word534
变量名必须满足如下要求:
·不超过256个字符
·可以由数字和字母组成
·第一个字符必须是字母
给变量赋值
在我们的程序中,我们在声明时赋值。你也可以将声明和定义分开:
integer counter; counter = 0;
为了设定一个数值,你将它放在等号左边。等号右边是任何有效值。你可以使用如下:
|
操作符 |
定义 |
| + |
相加数值和字符,如thisString = “Hello” + “And Welcome!” |
| - |
相减 |
| * |
相乘 |
| / |
相除 |
| % |
取余(11%3=2) |
| ++ |
增加1 |
| -- |
减1 | 在我们的程序里:
counter = counter + 1;
也可以写作
counter++;
当然,字符串也可以进行加法。如:
thisString = Hello + Welcome //thisString is equal to Hello Welcome | 注意字符串一定要放在双引号里,以免编译器出错。
在我们程序里有:
| string touchtimes = "Touched " + (string)counter + " times"; | 这里有一个(string)。这个是一个类型转换操作符。它将一个变量转化为另外一个变量。你需要它是因为你只允许使用加减等等操作符来操作相同的变量。它的使用格式如下:
( [type of variable] ) [variable to be converted]; 如 string welcome = (string)numberOfTimes;
没种转换都必须显式定义(意思是你必须要清楚写出,除非是一些隐式的转换,如整型到浮点型,字符串到key)。下面是支持显式转换的:
· Integer § Integer to String § Integer to List · Float § Float to Integer § Integer to String § Integer to List · Vector § Vector to String § Vector to List · Rotation § Rotation to String § Rotation to List · Key § Key to List · String § String to List § String to Integer § String to Float § String to Vector § String to Rotation
注意当我们使用IISay()的时候我们没有将字符串变量放在括号里,因为我们需要的是字符串代表的值而不是“touchtimes”这个字符串。
“++”和“--”操作符
尽管这两个操作符不需要在右边有等号,它们也可以有。这种情况下它们被视为普通的变量赋值:
myInteger = otherInteger;
但是有一些使用规则:
integer counter = 0; integer counter2 = counter++; | 这里你可能认为首先counter加1,然后将counter赋值给counter。刚好相反!它首先将counter赋值给counter2,然后counter加1。要首先使counter加1,这样写:
integer counter = 0; integer counter2 = ++counter; |
最后看一眼程序
程序首先声明一个计数器,赋值为0.每次触摸,增加1。然后声明一个字符串,将其定义为“Touched x time”,这里x等于counter的值。每次touch_start()发生,它就会报告一次该字符串。
循环和判断
什么是循环?就是事情不停的做,直到满足了一个条件,循环停止。有三种循环:for循环,do…while循环和while循环。没种都有各自的目的。
布尔操作符
布尔操作符是干什么用的?根据情况,它给你真或假的值。者用来做循环的决定,如:
如果counter为5,做这个…
这里是所有的布尔操作符:
|
布尔操作符 |
描述 |
| == |
等于 |
| < |
小于 |
| > |
大于 |
| <= |
小于等于 |
| >= |
大于等于 |
| != |
不等于 | 这里的循环和普通的编程语言中的循环类似,我们给出一些简单的实例,不再详述。
For循环
格式:
for( [value of the case number] ; [the incremention/decrementation type] ; [the requirements of the case number’s value] )
例子:
for( integer i = 0; i++; i<12) { //Do something; } | do…while循环
格式:
do{ //Do something; }while( [ a Boolean case] ); | 例子:
do{ //Do something; }while( counter != 0); | While循环
格式:
while( [a Boolean case] { //do something; } | 例子:
while(counter != 0) { //Do Something; } | If判断
格式:
if( [a Boolean case] ) { //Do something; } | 例子:
if(ownerName == The Monster) { llSay( 0, You’re a monster!); } | Else判断
格式:if decision, as in if…then do this… else..then do this
例子:
if(counter == 0) { sayString = ?Counter is equal to 0?; } else if(counter == 1) { sayString = ?Counter is equal to 0?; } else { sayString = ?Counter is equal to something else?; } llSay(0,sayString); | 函数
函数是用来将很多的相关代码放在一个块中,适用于反复执行的情况。格式如下:
[type value] [name of function] ( [requirements, more requirements] ) { //Do something (optional) return [a value]; } | 调用方式是:
| [name of function]( [function requirements…]); | 下面给出一个例子:
float findsquare(integer number) { llSay( 0, "Find square..."); float temp = number*number; return temp; }
saysquare( float square, integer number) { llSay( 0, "The square of " + (string)number + " is " + (string)square); } integer counter = 0;
default { state_entry() { }
touch_start(integer total_number) { counter++; float square = findsquare(counter); saysquare(square, counter); } } | 进阶:编写交通工具代码
创建交通工具
创建交通工具需要很多步。但是你需要有一个清晰的步骤:
·调用IIPassCollisions(integer pass):函数,pass被设置为TRUE。这将导致交通工具能撞向物体,如山等等。
·设置你的基本元素为车,以及车的类型
·为你的车设置标识符和参数
·检查驾驶者是否是你
·如果是,检查是否允许使用键盘(上下左右键等)
·如果允许,那么开始键盘控制
·创建对键盘的事件处理器(按下向上键,向前开),设置动作
·如果不能接受键盘,那么车是不能开的
看起来还是有些弄不清,但是当我们创建一个车之后,你就能创建很多类型的其他车了。
创建交通工具的代码
以下是基本代码:
default { state_entry() { llPassCollisions(TRUE); llSetSitText("Drive"); llSitTarget(<0.6, 0.03, 0.20>, ZERO_ROTATION);
llSetVehicleType(VEHICLE_TYPE_CAR);
llSetVehicleFlags(VEHICLE_FLAG_NO_DEFLECTION_UP | VEHICLE_FLAG_LIMIT_ROLL_ONLY | VEHICLE_FLAG_LIMIT_MOTOR_UP);
llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.10); llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.80); llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 0.10); llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 0.10);
llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, 1.0); llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 0.2); llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0.1); llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 0.5);
llSetVehicleVectorParam(VEHICLE_LINEAR_FRICTION_TIMESCALE, <10.0, 0.5, 1000.0> ); llSetVehicleVectorParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE, <0.5, 10.0, 0.5> );
llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 0.50); llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 0.40);
llCollisionSound("", 0.0); }
changed(integer change) { if (change & CHANGED_LINK) { key agent = llAvatarOnSitTarget();
if (agent) { if (agent != llGetOwner()) { llSay(0, "You aren't the owner"); llUnSit(agent); llPushObject(agent, <0,0,100>, ZERO_VECTOR, FALSE); } else { llSetStatus(STATUS_PHYSICS, TRUE); llRequestPermissions(agent, PERMISSION_TAKE_CONTROLS); } } else { llSetStatus(STATUS_PHYSICS, FALSE); llReleaseControls(); } }
} run_time_permissions(integer perm) { if (perm) { llTakeControls(CONTROL_FWD | CONTROL_BACK | CONTROL_RIGHT | CONTROL_LEFT | CONTROL_ROT_RIGHT | CONTROL_ROT_LEFT, TRUE, FALSE); } }
control(key id, integer level, integer edge) { vector angular_motor;
if(level & CONTROL_FWD) { llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <50,0,0> );
} if(level & CONTROL_BACK) { llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <-20,0,0> ); } if(level & (CONTROL_RIGHT|CONTROL_ROT_RIGHT)) { angular_motor.z -= PI*3; } if(level & (CONTROL_LEFT|CONTROL_ROT_LEFT)) { angular_motor.z += PI*3; }
llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,angular_motor); }
} | 让我们来理解一下代码是什么意思。
llPassCollisions(TRUE);
允许交通工具和其他物体相撞
llSetSitText("Drive");
右键点击汽车时能看到的不是“Sit”而是“Drive”,意思更明确
llSitTarget(<0.6, 0.03, 0.20>, ZERO_ROTATION);
设置坐的目标,你通常需要设置avatar是如何坐上去的,所以这并不奇怪。注意你必须试着先坐到你的基本元素上,然后才能开动它。
llSetVehicleType(VEHICLE_TYPE_CAR);
这里我们设置交通工具,有如下类型可以供选择,我们选择了汽车:
| 类型 |
描述 |
| VEHICLE_TYPE_SLED |
雪橇类,和汽车相似但没有引擎 |
| VEHICLE_TYPE_CAR |
地面上的汽车 |
| VEHICLE_TYPE_BOAT |
水上的漂浮物 |
| VEHICLE_TYPE_AIRPLANE |
飞机类交通工具 |
| VEHICLE_TYPE_BALLOON |
移动类交通工具或者漂浮类交通工具,如潜水艇,热气球,直升飞机等 | llSetVehicleFlags(VEHICLE_FLAG_NO_DEFLECTION_UP|VEHICLE_FLAG_LIMIT_ROLL_ONLY | VEHICLE_FLAG_LIMIT_MOTOR_UP);
这里设置交通工具的一些标识。你可以查找LSL指南的C.25来阅读它们的具体含义。
llSetVehicleFloatParam(…);
这里设置交通工具的参量,如引擎的老化速度,引擎的速度等等。这里你需要阅读LSL指南的相关部分来学习一下这些参数具体含义。参数通过llSetVehicle[type]Param( Constant Type, Value);设置。
llCollisionSound("", 0.0);
在地面上移动可以被认为是“撞击地面”(物体间发生摩擦),因此这里需要程序逻辑来控制这个动作。这里将这个撞击认为是无声的,并且关闭了音量。第一个参数声音名,第二个是声音大小。
changed(integer change){}
这个事件在基本元素变化时调用。我们需要使用的是CHANGED_LINK,因为它表示一个基本元素是否链接上了,或者是失去了,并且这里avatar也是链接考虑的对象。
if (change & CHANGED_LINK){} 这行代码检查是否change数值中包含CHANGED_LINK。
key agent = llAvatarOnSitTarget();
if (agent) { if (agent != llGetOwner()) { llSay(0, "You aren't the owner"); llUnSit(agent); llPushObject(agent, <0,0,100>, ZERO_VECTOR, FALSE); } else { llSetStatus(STATUS_PHYSICS, TRUE); llRequestPermissions(agent, PERMISSION_TAKE_CONTROLS); } } | 这些代码首先检查是否对象链接上了。然后IIPushObject()主要将物体推向一个方向,但不会移动物体。Else下的代码发生在车主(玩家)坐在交通工具里,那么“Physics”被设置为真,使得物体变真实了,如受到重力作用,碰撞等。然后要求允许用户来接管控制。一般就是使用键盘来操纵交通工具。
run_time_permissions(integer perm) { if(perm) { llTakeControls(CONTROL_FWD | CONTROL_BACK | CONTROL_RIGHT | CONTROL_LEFT | CONTROL_ROT_RIGHT | CONTROL_ROT_LEFT, TRUE, FALSE); } } | llRequestPermission()被调用后该事件发生。首先检查perm是否为NULL空值,如果不是那么你就可以通过函数IITakeControls()来控制交通工具了。第一个参数是你想控制哪些,由|隔开。你可以查找LSL Guide的C.11来发现更多控制常量。第二个参数是物体是否响应键盘事件,第三个参数是avatar是否响应键盘事件。这里只将第二个设置为true,因为开车时让avatar说话可不好。
control(key id, integer level, integer edge) 这是键盘事件,level是按下的键,edge是刚刚松开的键
vector angular_motor;
if(level & CONTROL_FWD) { llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <50,0,0> ); } if(level & CONTROL_BACK) { llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <-20,0,0> ); } if(level & (CONTROL_RIGHT|CONTROL_ROT_RIGHT)) { angular_motor.z -= PI*3; } if(level & (CONTROL_LEFT|CONTROL_ROT_LEFT)) { angular_motor.z += PI*3; }
llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION,angular_motor); | 这几行代码决定当键被按下时发生的事件。这里使用了&操作符,因为level中,一次可能有多个按键被按下。VEHICLE_LINEAR_MOTOR_DIRECTION参数使得交通工具向一个方向直开,angular motor则使其以一个角度方向开行。尽管angular motor号称是原转3圈,以及直走的距离是50米,但是其实不会,因为要考虑到引擎启动和熄火的速度。
else { llSetStatus(STATUS_PHYSICS, FALSE); llReleaseControls(); } | 尽管这些代码被放在changed()事件中,但是它却是最后被执行的,因为它发生在avatar离开驾驶座位时。代码取消了交通工具的物理特性,使得模拟世界运行更加平滑。同时,它也释放了控制,这样avatar可以开始再次行走/飞行/跑步了。
|