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

 找回密码
 加入VME

QQ登录

只需一步,快速开始

搜索
楼主: FFUR2007SLX2_5

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

    [复制链接]
发表于 2013-10-5 01:24:23 | 显示全部楼层
qian laii xue xi !!
 楼主| 发表于 2013-10-5 09:29:36 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2013-10-5 09:38 编辑

64楼,武装突袭3 – 变量与namespace



上一篇我们讲了私用变量,这一部分我们讲局部变量和特殊局部变量。局部变量的定义我这里不说了(基础篇,参看推荐的两篇sqf基础教程)

这里提一下我们局部变量的命名不要和特殊局部变量相冲突,否则引擎会出错。举个例子:
  1. false = true;
  2. call = "It's true!";
  3. if (false) then {hint call}; //"It's true!"
复制代码
(当然谁会那么变态呢?)
特殊局部变量是啥玩意?就是像time,player, true false time enemy friendly sideLogic sideUnknown sideFriendly sideEnemy playerSide east west civilian resistance independent opfor blufor nil objNull grpNull netObjNull locationNull taskNull controlNull displayNull teamMemberNull player等等,这些东西其实就是游戏的引擎词汇,命名局部变量时避免这一问题就可以了。(当然最好给自己的变量戴上帽子,比如FFUR_VAR等等。中级篇,资深玩家可跳过)

如果你想使一个局部变量失效,请使用:FFUR_var = nil;

接下来讲讲namespace,namespace是什么东西?biwiki上看得一头雾水,似乎BI的开发人员和写服务器脚本的人很喜欢用namespace,打开fx viewer里面全部都是这玩意儿。那它们到底是用来干什么的呢?简单的说“从namespace调用出来的局部变量更加安全,可以有效的防止服务器在调用变量时被客户端加上嵌入代码造成被黑等等”。武装突袭3共有4种namespace.

MissionNameSpace

我们所有定义的局部变量都可以从MissionNameSpace中返还值。比如:



同样missionnamespace中定义的局部变量也可以直接返还:



那我要问了既然都一样我们还要namespace干吗?干脆不直接定义,搞那么麻烦?先别急,missionnamespace中的局部变量的存活时间仅限于任务开始到任务结束这段时间,一但玩家退出任务或返回游戏大厅这些变量就都没了。

下面我们就引进了uiNamespaceparsingNamespace

在这两个namespace下定义的局部变量可以存活更长时间,就是说即使你返回游戏大厅,退出任务后再进去这些变量依旧活着,什么时候它们才会消失?除非你关掉游戏。所以这就是为什么我们看到写服务器的朋友们喜欢用uinamespace。
另外一点要提一下如果我们把uiNamespace和parsingNamespace定位父类块,把missionnamespace定位子类块,就会发现他们是两个区间,在missionnamespace中定义的变量是无法在uiNamespace和parsingNamespace中显示的:



这就意味着我们在missionnamespace中和uinamespace中可以有相同命名的变量却可以赋予不同的值而且不受干扰。(更高级的用法我会在之后的服务器端反黑客嵌入式代码入门中详细阐述)



最后一个最变态的namespace是叫做profileNamespace
属于爷爷级别的,只要你还在用你那帐号在玩游戏,在“爷爷块儿”里面的变量将一生一世与你同在,n个星期后再玩游戏那变量还活着,除非你把你的profile给删了。或换个帐号,但换回来后那些变量又回来了。

所以做个namespace的小总结,在namespace中定义变量可以用:
  1. with uiNamespace do {};
  2. with parsingNamespace do {};
  3. with profileNamespace do {};
复制代码
例子2:
  1. myvar = 123;
  2. with uiNamespace do {
  3.     hint str (isNil "myvar"); //true
  4.     myvar = 456;
  5. };
  6. with profileNamespace do {
  7.     hint str (isNil "myvar"); //true
  8.     myvar = 789;
  9. };
  10. with uiNamespace do {
  11.     hint str myvar; //456
  12. };
  13. with profileNamespace do {
  14.     hint str myvar; //789
  15. };
  16. hint str myvar; //123
复制代码
我们还可以用setvariable来定义变量,效果和直接定义是一样的:
  1. missionNamespace setVariable ["tro","lolol"];
  2. //同下
  3. with missionNamespace do {
  4.     tro = "lolol";
  5. };
  6. //同下
  7. tro = "lolol";
复制代码
再细致一点,高级一点,当我们需要更快的执行效率和更快的速度时,普通变量定义和namespace的区别就显现出来了。我们的老朋友codePerformance又要登场了,比比谁跑的更快。

给我们一个任务,要求列出100个变量值。如果我们不会namespace而用老办法,结果是:
  1. var1 = 1;
  2. var2 = 2;

  3. Var100 = 100;
  4. for "_i" from 1 to 100 do {
  5.     private "_myvar";
  6.     call compile format ["_myvar = var%1;", _i];
  7.     diag_log _myvar;
  8. };
复制代码
(貌似算法很激烈)
用namespace呢?不用算了,行数少了,自然快了:
  1. var1 = 1;
  2. var2 = 2;

  3. Var100 = 100;
  4. for "_i" from 1 to 100 do {
  5.     diag_log (missionNamespace getVariable (format ["var%1", _i]));
  6. };
复制代码
用namespace还有一个好处是直接定义变量所无法企及的,当使用setvariable时第3个值可以直接设成全局变量,虽然MP模式下的代码编写是广大玩家不敢逾越的雷池,但是既然是高级教程,我想我们有必要跨越这片雷池。使用getvariable时第二个默认值还可以赋值首次使用的变量,确实很帅,在学习了这篇教程后,大家不会再赤裸裸的定义变量了吧!
  1. _myrank = profileNamespace getVariable ["MY_RANK", "Private"];
复制代码
当然取消赋值可以这样:
  1. profileNamespace setVariable ["MY_RANK", nil];
复制代码

本帖子中包含更多资源

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

x
 楼主| 发表于 2013-10-5 09:30:30 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2013-10-5 09:41 编辑

65楼,全局变量【服务器变量传递与addPublicVariableEventHandler】



上一讲我们了解了namespace及其优势,在全局变量开始前我县讲讲武装突袭3共支持多少种类的变量赋值。我们每对一个变量赋值游戏就会识别其所属的种类:

_var = 0; hint typeName _var; //SCALAR
_var = ""; hint typeName _var; //STRING
_var = true; hint typeName _var; //BOOL
_var = []; hint typeName _var; //ARRAY
_var = {}; hint typeName _var; //CODE
_var = objNull; hint typeName _var; //OBJECT
_var = grpNull; hint typeName _var; //GROUP
_var = controlNull; hint typeName _var; //CONTROL
_var = teamMemberNull; hint typeName _var; //TEAM_MEMBER
_var = displayNull; hint typeName _var; //DISPLAY
_var = taskNull; hint typeName _var; //TASK
_var = locationNull; hint typeName _var; //LOCATION
_var = opfor; hint typeName _var; //SIDE
_var = parseText ""; hint typeName _var; //TEXT
_var = configFile; hint typeName _var; //CONFIG
_var = missionNamespace; hint typeName _var; //NAMESPACE

以上这些就是我们所要用到的全部赋值了。但要注意了,如果你未给一个变量赋值,那么typeName不会返回任何种类,同时-showscripterrors也不会报错,如下所示:
  1. _var = nil; hint typeName _var; //nothing
  2. if (typeName _var == "") then {
  3.     diag_log "true";
  4. } else {
  5.     diag_log "false";
  6. };
  7. diag_log "neither";
  8. //.rpt
  9. //"neither"
复制代码
接着我们回到正题,全局变量。既然变量的变化只能在一台电脑识别,那么要让其他电脑识别我们就需要使用publicVariable(老外很喜欢这玩意儿,不过我们用的还是很少)看示例:
  1. //computer 1
  2. myvar = "somevar";
  3. //computer 2
  4. hint format ["%1", myvar]; //"any"
  5. //computer 3
  6. hint format ["%1", myvar]; //"any"

  7. //computer 1
  8. myvar = "somevar";
  9. publicVariable "myvar";
  10. //computer 2
  11. hint format ["%1", myvar]; //"somevar"
  12. //computer 3
  13. hint format ["%1", myvar]; //"somevar"
复制代码
服务器变量每公布一次,所有客户端便更新一次,但是对于那些一直都在变化的变量呢?是不是把它扔到循环中去更新所有客户端?那可不行,这是黑客做的事,它会秒杀一切客户端使游戏卡到西伯利亚。这时我们引入addPublicVariableEventHandler概念,只要变量变化了它便自动给所有客户端更新一次,大大优化了带宽利用率。和publicvariable类似,publicVariableClient是只对客户端进行变量更新,publicVariableServer是客户端向服务器发出指令(还有高级的我们后面说)

还有一个全局变量是嵌入物体中的,任务中的有些物体是所有客户端共享的,而且这些嵌入物体中的变量没有eventhandler去侦测其变化,所以在服务器编码中我们可以把一些额外的变量藏到这些物体中去,当需要的时候取回赋值,我们使用setVariable,它可以将变量公布到所有客户端但对本机依旧保持局部变量。看示例:
  1. //computer 1
  2. myobj = (typeOf player) createVehicle (position player);
  3. myobj setVariable ["lastCreated", time];
  4. hint str (myobj getVariable ["lastCreated", 0]); //时间设定从任务开始算起
  5. publicVariable myobj;
  6. //computer 2
  7. hint str (myobj getVariable ["lastCreated", 0]); //"0"
复制代码
如果我们想把最新创建的变量公布到客户端,那么setVariable的第三个参数要设为true,看示例:
  1. //computer 1
  2. myobj = (typeOf player) createVehicle (position player);
  3. myobj setVariable ["lastCreated", time, true];
  4. hint str (myobj getVariable ["lastCreated", 0]); //时间设定从任务开始算起
  5. publicVariable myobj;
  6. //computer 2
  7. hint str (myobj getVariable ["lastCreated", 0]); //时间设定从任务开始算起
复制代码
对于那些将变量嵌入到所有客户端共享物体中去的做法,相当于publicVariable,这些变量最好不是异常活跃的,否则频繁的变换,公布,变换会增加带宽负荷,到时候又卡到西伯利亚了。

对于被赋值变量的物体来说要谨慎选择,否则你把显示屏击穿了也没人负责,不要选择房子和那些通过脚本途中生成的物体,因为它们不是“常住人口”,武装突袭3为了优化多人模式下的运行效率不可能加载整个岛屿全部物体,她只加载玩家附近的,就好比在你身边的房子未必在其他人电脑上就存在,除非他跑到你身边才会看得到。(否则那些写沙盒的人都可以下岗待业了)

在我们下一篇教程:老友重逢之数组篇之后就是重头戏了。

本帖子中包含更多资源

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

x
 楼主| 发表于 2013-10-5 09:31:04 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2013-10-7 19:47 编辑

66楼,武装突袭3 - 老友重逢之数组篇



只要玩上了武装突袭,数组就和我们做上了朋友,它是我们第一个认识的朋友,而且是永远的知心朋友,可以说我们对它的认识比任何代码都来得深刻。但有些时候我们常会犯一些错误让它变得难堪,比如当我们想看看一个复合数组中到底有几个JOHN时可能无意间就这么写了:
  1. _array = ["John",23,"Samantha",25,"Billy",67,"Archer",45,"John",23,"John",32];
  2. _result = {_x == "John"} count _array; //这会出错
复制代码
有些时候我们得细心些,否则又要砸显示器了。
  1. _array = ["John",23,"Samantha",25,"Billy",67,"Archer",45,"John",23,"John",32];
  2. _result = {typeName _x == "STRING" && {_x == "John"}} count _array;//这样写就对了。
复制代码
还有一个我们得要留心的是虽然武装突袭3是个大大咧咧的女孩,她从来不管你的句法大小写神马的,什么SoLdIeR MoVeIn cAr神马的她都能执行,但是in和find却是区分大小写的,而且兄弟俩不认嵌套数组的,请看:
  1. _array = ["one","two","three"];
  2. _result = "TWO" in _array; //_result is false
  3. _result = _array find "TWO"; //_result is -1
  4. _array = [[1,2,3],[4,5,6]];
  5. _result = [1,2,3] in _array; //_result is false
  6. _result = _array find [1,2,3]; //_result is -1
复制代码
所以编写时还得细心些,否则出错抓狂了又要击穿显示器了。

接下来我们谈谈处理config数组时所要用到的getArray,isArray。这两个什么意思就不解释了,详情可参考biwiki,属于中级教程,资深玩家可跳过。这里我就一笔带过了:
  1. _result = isArray (configFile >> "cfgWeapons" >> "MakarovSD" >> "magazines");
  2. //_result is true
  3. _result = getArray (configFile >> "cfgWeapons" >> "MakarovSD" >> "magazines");
  4. //_result is ["8Rnd_9x18_MakarovSD","8Rnd_9x18_Makarov"]
复制代码
我们对数组的值进行变化时会用set和resize,请看:(中级篇,资深玩家可跳过)
  1. _array = [1,2,3,4,6,6,7,8,9];
  2. _array set [4,5];
  3. //_array is now [1,2,3,4,5,6,7,8,9]
  4. _array set [count _array,10];
  5. //_array is now [1,2,3,4,5,6,7,8,9,10]
  6. _array set [12,13];
  7. //_array is now [1,2,3,4,5,6,7,8,9,10,<null>,<null>,13]
  8. _array resize 3;
  9. //_array is now [1,2,3]
  10. _array resize 5;
  11. //_array is now [1,2,3,<null>,<null>]
  12. _array resize 0;
  13. //_array is now []
复制代码
这里再小贴士一下,目前我们没有办法直接将数组中的值删除,但可以将这些值取消定义,正如之前提到的nil又来了:
  1. _array = [1,2,3,4,5];
  2. _array set [2,nil];
  3. //数组现在是 [1,2,any,4,5]
复制代码
在基础教程中我们知道数组可以加减的,但是记住如果被减的值是数组本身则句法无效:
  1. _array = [[1,2],[3,4]];
  2. _array = _array - [[1,2]];
  3. //数组还是 [[1,2],[3,4]]
复制代码
回到刚才关于删除的问题,如果随着游戏的不断进行有些单位或物体死亡了或消失了,返还到数组后就成了objNull, locationNull, grpNull, taskNull, teamMemberNull, controlNull, displayNull这种东西,在下一次更新时我们想把这些玩意从数组中剔除掉,那么这里有个技巧就是先将要剔除的值改成另一个特殊的玩意儿(上面列出的这些也可以),随后再删除,请看:
  1. _array = [1,2,3,4,5];
  2. _array set [1,"deletethis"];
  3. //数组现在是 [1,"deletethis",3,4,5]
  4. _array = _array - ["deletethis"];
  5. //数组现在是[1,3,4,5]
  6. _array = _array set [3,objNull];
  7. //数组现在是[1,3,4,<NULL-object>]
  8. _array = _array - [objNull];
  9. //数组现在是[1,3,4]
复制代码
再来一个高级点的技巧,如果你想给数组加一个东西,但是你又想让你所添加进该数组的这个东西是独一无二的,在数组中是不能有重复的,那该怎么办?请看:
  1. _array = [1,2,3,4,5];
  2. _array = (_array - [6]) + [6]; //如果我们之前不知道_array有6,那么-[6]将一次性剔除所有6如果该数组有的话
  3. //数组现在是[1,2,3,4,5,6]
  4. _array = (_array - [6]) + [6];
  5. //数组现在还是[1,2,3,4,5,6]
复制代码
在这里我不得不再提一下resize,其实综上所述似乎resize的事set也能做,不就是删除或扩加数组吗?我们平时编写也很少用到resize,能用set的已经算是不错了,但是想要过渡到高级阶段,我想有必要举办一场resize和set的百米冲刺比赛,大家猜猜结果谁跑得更快,就会对这两个家伙心中有底了。规则是这样的,现在我有一个数组里面有100个值,现在我要这个数组里面的前50个值并把它们提取到一个新的数组中去,这场比赛的裁判是codePerformance(它又来了),先来看看set同学的表现:
  1. arr = [];
  2. for "_i" from 0 to 99 do {arr set [_i, 0]};
  3. arr2 = [];
  4. [
  5.     'for "_i" from 0 to 49 do {
  6.         arr2 set [_i, arr select _i]
  7.     }'
  8. ]  call BIS_fnc_codePerformance;
复制代码


Set同学表现不错,它用了0.222412ms

再来看看resize同学的表现:
  1. arr = [];
  2. for "_i" from 0 to 99 do {arr set [_i, 0]};
  3. arr2 = +arr;
  4. [
  5.     'arr2 resize 50'
  6. ] call BIS_fnc_codePerformance;
复制代码


我擦,开挂的吗?比set同学快了100倍!
从这里我们可以看到代码优化的重要性(就多了一行就会有如此大的差距,有人会说,嘿,拜托,不看看它是什么单位?毫秒啊!毫秒之间的差别有必要去优化吗?又不是伽利略望远镜内置原子钟,这种优化就是个噱头,完全没有必要。我知道这么说是在情理之中,但我在后面的教程中介绍一个更为恐怖的代码,而正是毫秒之间的差别,未经优化的代码给机器带来了诡异的表现,这是后话),所以进入了高级篇,我们有必要开始考虑代码上的优化了,我们的编写不再随心所欲,相反,将会更加缜密,更加精确!

数组篇就快要结束了,最后几个小贴士是关于防止“非受迫性失误”而导致抓狂击穿屏幕的,我们一般比较喜欢使用_NewArray = _OldArray来定义一个新的数组,不过_OldArray也不会闲着,它会跟着_NewArray一起跑,请看:
  1. _array1 = [1,2,3];
  2. _array2 = _array1;
  3. _array2 set [2,0];
  4. //_array1 现在是 [1,2,0]
  5. a = {
  6.     private ["_array2"];
  7.     _array2 = _this;
  8.     _array2 set [2,10];
  9. };
  10. _array1 call a;
  11. //_array1 现在是 [1,2,10]
复制代码
所以不要以为旧的数组会保持不变,如果我们不留心眼重新调用旧数组时就会发现它什么时候更新了?我没更新过它呀!所以为了防止发生这种事我们还是得要一些小技巧,正如前面提到的一样,使用[]+,请看:
  1. _array1 = [1,2,3];
  2. _array2 = [] + _array1;
  3. //_array2 is now [1,2,3]
  4. _array2 set [2,0];
  5. //_array1 还是 [1,2,3], _array2 现在是 [1,2,0]
  6. _array2 = _array1 + [];
  7. //_array2 is now [1,2,3]
  8. _array2 set [2,10];
  9. //_array1还是[1,2,3], _array2现在是[1,2,10]
  10. _array2 = _array1 - [];
  11. //_array2 is now [1,2,3]
  12. _array2 set [2,20];
  13. //_array1还是[1,2,3], _array2现在是[1,2,20]
  14. _array2 = +_array1;
  15. //_array2 is now [1,2,3]
  16. _array2 set [2,30];
  17. //_array1还是[1,2,3], _array2现在是[1,2,30]
复制代码
说到数组我们一定会用到forEach和那个特殊私用变量_x,那么作为结束语再介绍一下_x的兄弟_forEachIndex,在某些特殊情况下它的效率和句法比_x更有效,更实用,比如说我想知道最后一个George出现在第几个位置,请看
  1. _array = ["John","Paul","George","Ringo","John","Paul","George","Ringo"];
  2. _lastGeorgeIndex = -1;
  3. {
  4.     if (_x == "George") then {
  5.         _lastGeorgeIndex = _forEachIndex;
  6.     };  
  7. } forEach _array;
  8. //_lastGeorgeIndex 是 6
复制代码
还有一个是关于运算符之间转换的,比如说我么这里有一个字符串,”<<<Hey_man>>>”,现在我的要求是把字符串里的”<<<”这种东西全部替换掉(这里不是说我来一个_sth = “Hey_man”就好了的,意思是如果一个未知字符串里面包含像”<?>{;}”等等的操作符该怎么办)因为它们都是运算符,我们不可能case <; case ?这是不可以的,所以我们可以先把字符串转换成ASCII数字编码,再使用数字与数字的匹配兑换,最后转会成字符串就好了,所以与其说会用,不如说妙用。
  1. private ["_array","_temp","_sanitised"];
  2. _array = toArray "<<<Hey_man>>>";
  3. _temp = [];
  4. {
  5.     switch _x do {
  6.         case 60 : {
  7.             _temp = _temp + toArray "&lt;";
  8.         };
  9.         case 62 : {
  10.             _temp = _temp + toArray "&gt;";
  11.         };
  12.         default {
  13.             _temp = _temp + [_x];
  14.         };
  15.     };
  16. } forEach _array;
  17. _sanitised = toString _temp;
  18. //_sanitised 现在是 &lt;&lt;&lt;Hey_man&gt;&gt;&gt;
复制代码
好了,祝大家愉快,下一章我们开始讲数组的枝节篇-武装突袭3的字母大小写区分。

请切换至80楼继续教程,武装突袭3 - 字母大小写区分

本帖子中包含更多资源

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

x
发表于 2013-10-5 15:53:02 | 显示全部楼层
本帖最后由 qevhytpl 于 2013-10-5 15:56 编辑

版主这是要逆天了,地球人已经不能阻止版主了....
发表于 2013-10-5 16:19:57 | 显示全部楼层
FFUR2007SLX2_5 发表于 2013-10-4 17:02
56楼,武装突袭3 - 神奇的变量

唯一不懂得就是这里了~~~

{
    {
        hint str _x; //_x is 1 //为何这里是1
    } forEach _x; //_x is [1,2,3] //为何这里是123?
} forEach [[1,2,3],[4,5,6],[7,8,9]];

foreach 我只会用来做班组全体执行脚本这种用法
现在看来还有很多别的意思啊~这个命令能具体讲解一下吗?老师~~
发表于 2013-10-5 16:35:16 | 显示全部楼层
顶!!!
发表于 2013-10-5 19:52:45 来自手机 | 显示全部楼层
版主的绝世秘笈果然非凡人所能修炼的,看到这里果然萎了o(╥﹏╥)o比葵花宝典还厉害……
发表于 2013-10-5 20:25:41 | 显示全部楼层
看看!!!
发表于 2013-10-5 20:54:53 | 显示全部楼层
可以一学谢啦
发表于 2013-10-5 21:08:53 | 显示全部楼层
好东西,一定要顶一下
发表于 2013-10-5 21:21:14 | 显示全部楼层
学习一下啊
发表于 2013-10-5 21:40:57 | 显示全部楼层
赞美楼主!
发表于 2013-10-5 23:30:25 | 显示全部楼层
发表于 2013-10-6 09:04:19 | 显示全部楼层
看看什么情况
您需要登录后才可以回帖 登录 | 加入VME

本版积分规则

小黑屋|中国虚拟军事网

GMT+8, 2024-3-29 16:41

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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