中国虚拟军事网(VME)- 专注于武装突袭系列虚拟军事游戏

 找回密码
 加入VME

QQ登录

只需一步,快速开始

搜索
楼主: FFUR2007SLX2_5

[教程] 《武装突袭3》脚本编写高级教程【255楼,武装突袭3——疯狂的戴夫和他的重量】

    [复制链接]
发表于 2013-10-9 19:00:16 | 显示全部楼层
学习下
发表于 2013-10-9 22:38:05 | 显示全部楼层
不错,学习下!!
发表于 2013-10-9 23:21:34 | 显示全部楼层
真正的好东东 赞一个先!
发表于 2013-10-10 00:41:23 | 显示全部楼层
复古风格嘎嘎嘎嘎嘎嘎嘎嘎嘎嘎嘎嘎
发表于 2013-10-10 17:40:42 | 显示全部楼层
感谢   
发表于 2013-10-11 11:19:13 | 显示全部楼层
支持一下,正需要这种教程
 楼主| 发表于 2013-10-11 16:01:26 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2013-10-11 22:38 编辑

99楼 武装突袭3之循环全解析(一)



现在我们脚本写到现在,想要问问大家能够说出武装突袭3到底支持多少种循环呢?第一个当然是while咯,然后for,再然后么forEach。三种吧,其实不然,武装突袭3共支持5种循环,另外两个是OnEachFrame和fsm的无限循环机。今天我们要做的就是对这5个循环做一个大比拼,让大家对循环有一个更深入的了解。

很多时候我们对武装突袭的了解还是太少,或许我们会说循环不就是和sleep一起使用吗?比如什么跟随脚本咯,刷兵咯,跳伞脚本咯什么的。这种理解是正确的,但到了高级篇,我们必须开始从循环的根去了解。

先来看看for循环,for可以将语块中的代码迅速的一次性执行完毕(汗,难道while不是吗?先别急,慢慢来)我们通过BiWiki可以看到对for循环的条件设置共有两种句法,而恰恰是两种不同的句法会造成运行速度上的差异!(codePerformance又来了,它是来测量速度的)我们先来看这句:
  1. for "_i" from 5 to 93 step 7 do {hint str _i}; //hint is 96
复制代码


看一下它的速度。有人会问为什么结果超过了93?注意这种句法是先求值再判断。

这里我们注意一下_i这个东西,它不受外面赋值的影响,无论你在for循环外做了什么,循环内的_i不受干扰,请看:
  1. _i = 101;
  2. for [{private "_i"; _i=5},{_i<=93},{_i=_i+7}] do {};
  3. hint str _i; //hint is 101
复制代码
关于无限的小插曲请看:
  1. for "_i" from 0 to 1 step 0 do {}; //这就是数学界所谓的无限循环
复制代码
回到主题,如果我们的句法是先判断再求值呢?请看:
  1. for [{_i=5},{_i<=93},{_i=_i+7}] do {hint str _i}; //hint is 89
复制代码
这种句法的结果也不一样,那速度呢?



一样快!当然如果我们把它变复杂点,让每一个循环都要运算那肯定是慢了:
  1. k = 3; a = {k = k + 0.5; k};
  2. for [{_i=0},{_i<(call a)},{_i=_i+1}] do {
  3.      diag_log format ["_i:%1/k:%2", _i, k];
  4. };
  5. //.rpt file
  6. //"_i:0/k:3.5"
  7. //"_i:1/k:4"
  8. //"_i:2/k:4.5"
  9. //"_i:3/k:5"
  10. //"_i:4/k:5.5"
  11. //"_i:5/k:6"
  12. //"_i:6/k:6.5"
复制代码
还有一个要注意的是for循环时我们必须要保持条件为true才会循环,否则就不会,如下:
  1. for [{_i=0},{true},{_i=_i+1}] do {};
复制代码
接下来有人开始叫了,我才不用for循环呢!这种事情while不是都能做吗?要for干什么?直接删了不就得了?那我们看看while是怎么会玩脱的吧。
  1. _i=5; while {_i<=93} do {hint str _i; _i=_i+7}; //hint is 89
  2. _i=5; while {_i<=93} do {_i=_i+7}; hint str _i; //hint is 96
复制代码
怎么会不一样?玩脱了是不?如果我们不用hint去除错那岂不又要徒手击穿屏幕了?这就是为什么我们要把for和while区分开来。不仅如此,for和while还有什么区别?我们来做一个有趣的实验:
  1. i=0; while {true} do {i=i+1}; hint str i; //hint is 10000
复制代码
奇怪,怎么到10000就停了呢?while可是无限循环的呀!很抱歉,while不是真正意义上的无限循环,武装突袭3为了防止while玩脱给它加了一个贞操环,它最多也就只能循环10000次!

不过不用担心,解铃还须系铃人,我们看看何种情况下可以解除while的循环限制:

当它在init.sqf中使用时可以无限循环,通过spawn也可以无限循环。

spawn {while {true} do {}} //无限循环
call {while {true} do {}} //从FSM / event handler / init field调用循环限制10000
call {while {true} do {}} //从 init.sqf / execVM脚本调用为无限循环
spawn {call {while {true} do {}}} //无限循环


下面我们来看一个高级点的用法,我们很多写脚本的人在中期阶段喜欢用while循环来检查条件,这是万万不可的做法,到时候无论你的CPU多强大都不可能跑起这种恶劣代码。
  1. lastVehicle = objNull;
  2. lastSeat = "";
  3. null = [] spawn {
  4.     private ["_veh","_roles","_seat"];
  5.     while {true} do {
  6.         _veh = vehicle player;
  7.         if (_veh != player) then {
  8.             _roles = assignedVehicleRole player;
  9.             _seat = if (count _roles > 0) then [{_roles select 0},{""}];
  10.             if (_veh != lastVehicle || _seat != lastSeat) then {
  11.                 lastVehicle = _veh;
  12.                 lastSeat = _seat;
  13.                 [_veh, _seat, player] call isIn;
  14.             };
  15.         } else {
  16.             if (!isNull lastVehicle) then {
  17.                 [lastVehicle, lastSeat, player] call isOut;
  18.             };
  19.         };
  20.         sleep 0.1;
  21.     };
  22. };
复制代码
上面这一坨代码就是反面教材,如果我们想要检查玩家有没有在车内可不能这么写,作为一名高级程序员,我们有必要用最简单的代码,比如下面这个:
  1. _veh addEventHandler ["GetIn",{_this call isIn}];
  2. _veh addEventHandler ["GetOut",{_this call isOut}];
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?加入VME

x
 楼主| 发表于 2013-10-11 16:02:26 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2013-10-11 22:38 编辑

100楼 武装突袭3之循环全解析(二)



分析了for循环和while循环后,我们来看看forEach循环,很多人不认为它像一个循环,不过事实是它就是一个循环。我们来看一下forEach是怎么工作的,这里我们直接用特殊私用变量_forEachIndex了,看过之前几篇的同志应该对它不陌生了。
  1. _array = [1,2,3];
  2. diag_log format ["_array:%1", _array];
  3. {
  4.     _array = _array + [0];
  5.     diag_log format ["_x:%1/_forEachIndex:%2/_array:%3", _x, _forEachIndex, _array];
  6. } forEach _array;
  7. diag_log format ["_array:%1", _array];
  8. //.rpt file
  9. //"_array:[1,2,3]"
  10. //"_x:1/_forEachIndex:0/_array:[1,2,3,0]"
  11. //"_x:2/_forEachIndex:1/_array:[1,2,3,0,0]"
  12. //"_x:3/_forEachIndex:2/_array:[1,2,3,0,0,0]"
  13. //"_array:[1,2,3,0,0,0]"
复制代码
既然forEach干的不错,那么我们赋值数组时就用它吧,不过这里有一个技巧,在forEach中使用set时要配合min,我们又要叫了,min是什么东西,怎么还要搞上这玩意儿?通过翻查BiWiki我们知道min是两数之间取小值,max是两数之间取大值。如果我们不用min做限制那么数组将无限扩张下去,请看:
  1. _array = [1,2,3];
  2. diag_log format ["_array:%1", _array];
  3. {
  4.     _array set [(count _array) min 5, 0];
  5.     diag_log format ["_x:%1/_forEachIndex:%2/_array:%3", _x, _forEachIndex, _array];
  6. } forEach _array;
  7. diag_log format ["_array:%1", _array];
  8. //.rpt file
  9. //"_array:[1,2,3]"
  10. //"_x:1/_forEachIndex:0/_array:[1,2,3,0]"
  11. //"_x:2/_forEachIndex:1/_array:[1,2,3,0,0]"
  12. //"_x:3/_forEachIndex:2/_array:[1,2,3,0,0,0]"
  13. //"_x:0/_forEachIndex:3/_array:[1,2,3,0,0,0]"
  14. //"_x:0/_forEachIndex:4/_array:[1,2,3,0,0,0]"
  15. //"_x:0/_forEachIndex:5/_array:[1,2,3,0,0,0]"
  16. //"_array:[1,2,3,0,0,0]"
复制代码
这就是我们用min的原因,当然forEach还有几个兄弟,也是干循环这一行的,但使用范围有局限,它们是forEachMember, forEachTeamMember 和forEachMemberAgent。这些我就不讲了,biwiki上都有。

现在我们来插播一段广告:



大家好,我的名字叫fraps,大家测帧都用我。这时OnEachFrame叫嚣着走过来了:还在用Fraps?那简直是弱爆了,我测帧可以精确到小数点后4位!而且还不浪费资源!



75.4717帧!咋地用?
  1. onEachFrame {hintSilent str diag_fps};
复制代码


广告结束,是时候OnEachFrame出场了,作为2代1.63的朋友,我想大家对它不陌生,其实也没什么新鲜的,因为在Biwiki上也都有 http://community.bistudio.com/wiki/onEachFrame

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?加入VME

x
 楼主| 发表于 2013-10-11 16:04:04 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2013-10-11 22:50 编辑

101楼 武装突袭3之循环全解析(三)



接上一篇我们讲了OnEachFrame,有人说我用while也可以测帧啊,事实是无论你给while的sleep设得多少短并不意味着CPU就能跑出每一个循环,换言之,如果CPU突然卡成幻灯片,那就别指望游戏照样能流畅的运行每一帧循环,帧数是游戏运行的关键,对代码的优化将成为高级编码者所义不容辞的任务。(这里我不得不再次提及像追踪弹道这类代码,我们看到很多子弹视角之类的脚本,其实很多写法的运行效率不高,由于这类脚本只适用于玩家,所以看不出什么问题,但倘若成百上千AI同时执行则会造成巨大的问题,这将在后面的教程中示范)

现在我们来科普一下,如果我们从FSM中(恐怖的无限循环机)运行for循环引擎每秒可以执行170000次,从spawn和execVM中for循环可以运行25000次/秒,而现实是我们只要求与游戏帧数相匹配的速度就可以了,基本上得控制在60次/秒。
我们在使用OnEachFrame时得注意不要同时运行两个,在确保你关闭了一个OnEachFrame再开始第二个,取消的方法是OnEachFrame {};

其实OnEachFrame还有一个兄弟叫做addMissionEventHandler,这是一个每帧运行的eventhandler,通常我们使用Draw3D来最大方式优化一些实时更新的东西比如3D 坐标,3D HUD图标等等。(Draw3D我会在“武装突袭3之创意园区篇”中集中实例)当然addMissionEventHandler也是不允许有sleep等延迟出现的。

现在让我们来看一下FSM中的循环(FSM全集教程我会在日后放出),FSM的条件判断也是每帧进行的,当然你还可以选择PreCondition,里面的代码也是每帧进行的,而且这些代码可以在condition之前运行,岂不是双重判断,一举两得?FSM不喜欢sleep,而且你不能直接用sleep,有些人会说嘿,我已经看到BI的FSM里用sleep的,但记住那些都是在spawn的语块中的。FSM的代码都会被优先处理,sqf什么的得排在它后面。



最后我们再来看一个特殊的循环waitUntil,由于Biwiki并不把它归类到循环,所以暂时我把它归类到特殊的循环中去。很多人不知道waitUntil到底该怎么用,不就是等一个条件触发后就能接下去运行下面的代码了吗?是的,不过这是初级教程教我们的方法,在这里,waitUntil内不仅可以包含条件,还可以运算,插入代码,其中所有代码也是每帧运算的。我们可以通过spawn同时运行多个waitUntil,也可以手动强制退出waitUntil。
关于强制退出WaitUntil语块我们必须手动在语块的最后部分强制执行true,如果要一直运行循环则强制执行false。下面这个是waitUntil当作循环来用的例子:
  1. _null = [] spawn {
  2.     _time = diag_tickTime + 1;
  3.     _i = 0;
  4.     waitUntil {
  5.         _i = _i + 1;
  6.         diag_tickTime >= _time
  7.     };
  8.     hint format [
  9.         "Code executed %1 times per second",
  10.         _i
  11.     ];
  12. };
  13. //代码每秒执行50次
复制代码
在此篇的最后部分我们来做一个引擎极限测试,我们在waitUntil中使用sleep(其实waitUntil是可以加sleep的),当sleep 大于0.0005秒时会造成waitUntil错失一帧的运算,请看实验:
  1. _null = [] spawn {
  2.     _time = diag_tickTime + 1;
  3.     _i = 0;
  4.     waitUntil {
  5.         sleep 0.00051;
  6.         _i = _i + 1;
  7.         diag_tickTime >= _time
  8.     };
  9.     hint format [
  10.         "Code executed %1 times per second",
  11.         _i
  12.     ];
  13. };
  14. //代码每秒执行 25 次
复制代码
好了我们来做个大总结吧,循环速度最快的是for,然后依次为forEach,while,OnEachFrame并列fsm,最后waitUntil。为什么把waitUntil放最后呢?因为通过实验我们可以看到这是唯一一个以帧数运行的循环且呈非同步占用结构,这就意味着它可以同时多个运行,而不像OnEachFrame。

再接下来的一章中我们将开始武装突袭3的代码结构——语块(花园,房子和卧室)

请切换至108楼开始下一章节代码结构——语块(花园,房子和卧室)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?加入VME

x
发表于 2013-10-11 18:43:11 | 显示全部楼层
FFUR2007SLX2_5 发表于 2013-10-11 16:01
武装突袭3之循环全解析(一)

_veh addEventHandler ["GetIn",{_this call isIn}];
_veh addEventHandler ["GetOut",{_this call isOut}];

这样的确是可以判断是否在不在车上~但是。。。如何将addEventHandler这种代码应用到脚本里呢?

比如 如果一开火 就执行某脚本 或者命令 这种课题

我经常在脚本中使用的时候~都是发生这种情况:一开火~命令执行~然后:命令被重复多次反复地执行~

除非addEventHandler是写在初始化栏里面的~否则跑到死~~
发表于 2013-10-11 19:10:51 | 显示全部楼层
好東西一定要持支一下的
发表于 2013-10-11 19:57:27 | 显示全部楼层
教程不错,又学到了
没想到代码还有这样简单高效的写法,以前一直让CPU承受莫大的压力,真主安拉 我有罪~
发表于 2013-10-11 20:54:36 | 显示全部楼层
下网上载 发表于 2013-10-11 18:43
_veh addEventHandler ["GetIn",{_this call isIn}];
_veh addEventHandler ["GetOut",{_this call isOu ...

有add,当然有remove啦,呵呵
发表于 2013-10-11 21:26:12 | 显示全部楼层
qevhytpl 发表于 2013-10-11 20:54
有add,当然有remove啦,呵呵

在add执行那个块里面 再执行remove?
发表于 2013-10-11 21:39:28 | 显示全部楼层
能单独为某一事件指定ID吗?如9=add…
这个问题解决了其他的也都好办了
您需要登录后才可以回帖 登录 | 加入VME

本版积分规则

小黑屋|中国虚拟军事网

GMT+8, 2024-4-29 05:08

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表