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

 找回密码
 加入VME

QQ登录

只需一步,快速开始

搜索
楼主: FFUR2007SLX2_5

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

    [复制链接]
 楼主| 发表于 2013-11-6 22:22:17 | 显示全部楼层
所有的rsc class name全部定义在一个RscTitles下,我们不论是使用相同的layer还是不同的layer都不可能发生显示没有调出的rsc class。

我们1 cutRsc ["layer1","plain"];就绝对不会出现"layer2"或"layer3"。

只有1 cutRsc ["layer1","plain"]; 2 cutRsc ["layer2","plain"];才会显示2个layer。

关于layer,请看 http://www.chinavme.com/forum.ph ... 6250&pid=237239

相同layer的cutrsc,最近运行的rsc覆盖之前的rsc。

最新的cutRsc我们已经不再使用数字来作为layer的覆盖顺序了,我们使用BIS_fnc_rscLayer对layer注册识别名,防止layer设计时造成混乱。

  1. ("LayerFlyMan" call BIS_fnc_rscLayer) cutRsc ["Loading1","PLAIN"];
  2. ("LayerFlyMan" call BIS_fnc_rscLayer) cutText ["","PLAIN"];
复制代码
发表于 2013-11-6 22:44:08 | 显示全部楼层
FFUR2007SLX2_5 发表于 2013-11-6 22:22
所有的rsc class name全部定义在一个RscTitles下,我们不论是使用相同的layer还是不同的layer都不可能发生 ...

但是我现在遇到的就是那么蛋疼的情况啊。。。老版~~
class RscTitles//HUD定义
{
        class loading1 {
          idd=1085;
          //movingEnable=1;
          onLoad = "uinamespace setvariable ['loading1',_this select 0]; _hud = [] execvm 'hud.sqf';";
          onUnload = "uinamespace setvariable ['loading1',nil]; "
          duration = 9999;
          fadein = 0;
          fadeout = 0;
          name = "loading1";
          class controls
          {
            class backui: RscStructuredText
                {
                        idc = 1000;
                        x = 0.35 * safezoneW + safezoneX;
                        y = 0.04 * safezoneH + safezoneY;
                        w = 0.223 * safezoneW;
                        h = 0.08 * safezoneH;
                        colorActive[] = {0.043,0.321,0.576,1};
                        colorBackground[] = {0.043,0.321,0.576,1};
                };
                class loading: RscStructuredText
                {
                        idc = 1100;
                        text = "loading"; //--- ToDo: Localize;
                        x = 0.38113 * safezoneW + safezoneX;
                        y = 0.06 * safezoneH + safezoneY;
                        w = 0 * safezoneW;
                        h = 0.0412438 * safezoneH;
                    colorBackground[] = {0.458,0.458,0.458,1};
                colorActive[] = {0.458,0.458,0.458,1};
                        colorText[] = {0.3922,0.3961,0.3882,1};
                };
      };
    };
        class usb {
          idd=1086;
          //movingEnable=1;
          //onLoad = "uinamespace setvariable ['loading1',_this select 0]; _hud = [] execvm 'hud.sqf';";
          //onUnload = "uinamespace setvariable ['loading1',nil]; "
          duration = 9999;
          fadein = 0;
          fadeout = 0;
          name = "usb";
                class controls{
                class usbui: RscPicture
                {
                        idc = 1200;
                        text = "usbb2_ca.paa";
                        x = 0.279618 * safezoneW + safezoneX;
                        y = -0.0737811 * safezoneH + safezoneY;
                        w = 0.35 * safezoneW;
                        h = 0.32 * safezoneH;
                };       
         };

        };

       
};

一调用loading1 cutRsc 就连class usb的东西都跑出来了。。。。。{:soso_e143:}
 楼主| 发表于 2013-11-6 23:24:14 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2013-11-6 23:43 编辑
下网上载 发表于 2013-11-6 22:44
但是我现在遇到的就是那么蛋疼的情况啊。。。老版~~
class RscTitles//HUD定义
{


恩这也是不可能发生的情况。cutRsc ["Loading1","plain"];在uiNameSpace中只能是1085,不可能会调出两个display。

我不能判断hud.sqf中是否有cutRsc ["usb","plain"]。或者是其他脚本有操作。

--------------------------------------2013-11-6-23-36-编辑---------------------------------------------------------------------

如果依然没有办法在sqf找到问题则到RscStructuredText检查一下并移除不需要继承的内容。
发表于 2013-11-7 19:01:29 | 显示全部楼层
FFUR2007SLX2_5 发表于 2013-11-6 23:24
恩这也是不可能发生的情况。cutRsc ["Loading1","plain"];在uiNameSpace中只能是1085,不可能会调出两 ...

我哭笑不得啊。。。。原来错误是在编辑器里我弄了三个触发器重叠一块~之前的那些没删~所以一直能一次cutrsc之前旧的图层。。。。。。还真是自己给自己蛋疼
发表于 2013-11-8 16:22:20 | 显示全部楼层
FFUR2007SLX2_5 发表于 2013-11-6 22:22
所有的rsc class name全部定义在一个RscTitles下,我们不论是使用相同的layer还是不同的layer都不可能发生 ...

这新的cutRsc命令有什么特别吗,难道相同的layer不覆盖之前的,layer数字大的不遮蔽小的吗?
 楼主| 发表于 2013-11-9 14:03:23 | 显示全部楼层
啤酒冲米二 发表于 2013-11-8 16:22
咦这新的cutRsc命令有什么特别吗,难道相同的layer不覆盖之前的,layer数字大的不遮蔽小的吗?

你说的是对的。
发表于 2013-11-26 22:58:10 | 显示全部楼层
good{:soso_e100:}
发表于 2013-11-28 15:18:28 | 显示全部楼层
要认真学习~~~
发表于 2014-1-17 11:56:00 | 显示全部楼层
啊,啊,啊。高手在民间啊。楼主能出一个类似例题介绍的么,编写一个小任务,从规划到脚本实现,到实际在游戏编辑器的使用都写写。
发表于 2014-1-17 14:49:24 | 显示全部楼层
在这里找了几天了终于找着了 感谢分享
发表于 2014-1-19 09:11:00 | 显示全部楼层
高端大气上档次
发表于 2014-1-21 22:50:42 | 显示全部楼层
感谢分享!
发表于 2014-1-31 19:06:54 | 显示全部楼层
对于新人很难搞懂啊。。。看半天都晕了
 楼主| 发表于 2014-2-10 17:05:39 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2014-2-14 20:13 编辑

209楼,《武装突袭3》——代码优化追加篇



Hi,大家好,好长时间没有出来冒泡了,作为2014年的开篇,今天我将继续从sqf的代码优化说起。作为之前优化篇的补充,我将对sqf的结构和开发目的做一个终结,一扫众脚本爱好者对sqf究竟是什么的疑问。

当然我先不回答这个问题,在说完代码优化的追加篇后,我将娓娓道来。

之前在FSM的六篇教程中我已经阐明了SQF与FSM的区别,下面的例子可以帮助您更容易的进一步了解SQF的结构。

  1. private ["_man","_car","_Unavailable"];
  2. _man = _this select 0;
  3. _car = _this select 1;
  4. _Unavailable = false;
  5. waitUntil {
  6. _man in _car;
  7. if (!canMove _car) exitWith {
  8.   _Unavailable = true;
  9. };
  10. };
  11. if (_Unavailable) then [{
  12. if (count crew _car >= 1) exitWith {
  13.    {_x action ["eject",_car];
  14.     Unassignvehicle _x;
  15.    } foreach crew _car;
  16. };
  17. },{
  18. WaitUntil {driver _car == _man};
  19. //blabla
  20. }];
复制代码


或许我们曾今或现在为了做一个任务始终在写类似于这样的脚本而乐此不疲,这没有错因为脚本最初的诞生就是为了更好的控制任务,但这也仅停留于OFP时代的思维。我们曾在BI forum或Armaholic见到过无数这样子的脚本,可惜进化到了现在我想把所有这种类型的脚本统一称为:线性思维流,而线性思维流早在ARMA1时就已经被彻彻底底的被FSM取代了,像上面的这样一坨代码我们完全可以把它变成这样:



简单明了,所以从现在开始是时候放弃线性思维流脚本了,这样做根本就没有发挥出SQF应有的效能,不然直接回到SQS时代得了。
那么SQF该以什么样的形式存在呢?函数的形式?那是肯定的,也是唯一的,不这么做那我们永远就只是脚本爱好者,根本跨不进高级的门。
下面的问题就是高级的SQF长什么样?下面就是个例子:

  1. Fn_StA = {
  2. private["_in","_i","_arr","_out"];
  3. _in=_this select 0;
  4. _arr = toArray(_in);
  5. _out=[];
  6. for "_i" from 0 to (count _arr)-1 do {
  7.           _out=_out+[toString([_arr select _i])];
  8. };
  9. _out
  10. };
  11. Fn_RD = {
  12. private ["_arr","_item"];
  13. _arr = _this;
  14.      for "_i" from 0 to ((count _arr) - 1) do {
  15.        _item = _arr select 0;
  16.            _arr = _arr - [_item];
  17.            _arr set [count _arr,_item];
  18.      };
  19. _arr
  20. };
复制代码


像这些才是我们应该要做的,当然这些只是非常简单的几个函数例子,专业的SQF必须只能包含这些东西,里面的函数以句块的形式表现,根本不允许出现什么线性流的东西,之所以这么说是因为这些自己写的这些函数是之后FSM中所要用到的各种核心内容,他们是经久不衰并且是整个FSM(任务框架)的灵魂所在。

脚本纷杂繁多,有些的好的,也有菜鸟脚本。不要认为拿来的脚本都是好的,也不要简单的以为越多越难懂的就是好脚本,其实不然,至少现在我们掌握了区别优劣的方法,通过区分到底是线性流派的,还是函数语块派。当然这也仅是第一步,在各种高级的函数SQF中,我们有必要使用之前所学过的所有知识去区分哪些函数是优化过的,哪些是没有的。
首先我们先来看第一个例子:

我看到大家很喜欢拿老外的config fnc来用,也不管到底写怎么样总之拿来就用了,随后copy不走样很多作品中都出现了相同的代码(关于这个问题我想在下一篇ARMA3 EULA中讲明白些)

先来看看这个:

  1. fn_GetAllGlasses = {
  2.    private ["_configPass","_parentListNum","_i","_list","_itemPass","_str"];
  3.    _list = [];
  4.    _configPass = configfile >> "CfgGlasses";
  5.    _parentListNum = (count _configPass) - 1;
  6.    for "_i" from 0 to _parentListNum do {
  7.           _itemPass = _configPass select _i;
  8.           _str = configName _itemPass;
  9.           _list set [count _list, _str];
  10.    };
  11.    _list
  12. };
复制代码


应该没啥问题,至少很多人在用,但却鲜有人去钻研,我们测试一下这个函数的优化度:



需要0.193506ms

今天代码优化追加篇的主要内容就是:陈述越少,优化质量越高!让我们来看看如何进行优化:
现在的代码改成了:

  1. fn_GetAllGlasses = {
  2. private ["_list","_cfg"];
  3.         _list = [];
  4.         _cfg = configFile >> "CfgGlasses";
  5.         for "_i" from 0 to count _cfg - 1 do {
  6.                 _list set [_i, configName (_cfg select _i)];
  7.         };
  8.         _list
  9. };
复制代码


看一下跑的结果:



0.107813ms!快了一倍!

有了眉目,就明白了该怎么样去写SQF,好的结构不是一撮而就的,冰冻三尺非一日之寒。
现在我们又回到了之前的老问题上,为什么要代码优化,我是土豪我怕谁?玩游戏卡我不就花钱买机子吗,还怕跑不起AA?话非如此,说实话即使我们SQF已经写得出神入化了但依旧达不到最大程度的性能优化,否则为什么OA 1.62支持的是C++而不是JAVA呢?
如果我们称JAVA为coffee based language,(SQF属于同类型的产物),他们的优点显而易见,高生产力,高效率!就这点在吸金能力上一脚就可以把C++踹飞了,不过现在面临的问题人们开始逐渐关注性能!性能就是$!电脑还是那台破电脑,手机还是那台破手机,这就是为什么说C++王者归来了。想要极致的优化我们完全可以脱离SQF,A3全面支持C++,举个例子,用SQF算出Altis所有Objects并列表需要多少时间,再有C++写一套函数去计算你就会发现区别。当然说到这里已经脱离了SQF了。

下一篇就浅谈一下EULA吧。

210楼继续教程,武装突袭3——kbAddTopic,kbTell和EULA简介

本帖子中包含更多资源

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

x
 楼主| 发表于 2014-2-10 21:31:28 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2014-2-14 20:14 编辑

210楼,武装突袭3——kbAddTopic,kbTell和EULA简介



从ARMA2开始BI的程序员引进了全新的对话系统——BIKB (Bohemia Interactive Knowledge Base)
其实这就是ARMA2和ARMA3的战役制作者Zipper5和工作室的一群配音演员为之忙碌的一套系统。在ARMA3的三章战役中我们可以发现大量的bikb文件,如果你不知道bikb的作用,那就out了,赶紧补补课吧!

我不想像BI开发者Jezuro解释的那么学术化,简单的概括来说,bikb对话系统可以完全替代到cfg sound去定义语音,我们完全不必写一套繁琐的对话逻辑FSM系统+sleep或延迟去侦测对方有没有把话说完再开口的时差判断,bikb+fsm+sqf完全就可以轻松搞定,构建你一套自己的对话将不再困难!bikb不仅可以用于AI与AI之间的随机对话,当然也可以玩家与AI,玩家与玩家之间的对话。

ARMA2开始后所有的任务或战役的对话全部由bikb+fsm+sqf的组合完成了,譬如我们所熟知的OA Alice System(BI资深设计师Miroky的得意之作)虽然这些对话非常简单,也只有短短的几句。但是只要学会了这套工作机制,想必你完全有能力制作一个大型的对话MOD,这块领域BI forum很少有人涉足。

这里有两个语音素材是接下来我们准备要用的:和,(我们把它们放在sound里面),使用bikb省去了在description.ext或h文件中去定义的步骤,识别的任务全部交给bikb来做,在我们的conversation.bikb中需要这样的定义,我先不想将其复杂化,因为在class Arguments中是可以使用变量来控制文字显示的,所以可以做到随场景的动态对话。这里先暂时不予考虑,在下一篇中我再做详解:

  1. class Sentences
  2. {

  3.     class hallo_01
  4.     {
  5.         text = "Hello Bret.";
  6.         speech[] = {"\Sound\ffur01.ogg"};
  7.         class Arguments {};
  8.         
  9.     };

  10.     class hallo_02
  11.     {
  12.         text = "Oh, hello Jemaine.";
  13.         speech[] = {"\Sound\ffur02.ogg"};
  14.         class Arguments {};
  15.         
  16.     }
  17. };
  18. class Arguments{};
  19. class Special{};
  20. startWithVocal[] = {hour};
  21. startWithConsonant[] = {europe, university};
复制代码


最后四行是程序写死的,我们必须要加上去,当然它们也有其各自的意义,暂时不解释。我们现在要做的就是记住每一个项,应为接下来的代码,fsm和sqf控制需要用到4个特殊局部变量,这些变量与我们的bikb绑定,相当于EH的传递变量。

定义好bikb后游戏是无法直接识别它的,与description.ext不同,我们可以在任何地点任何时间让游戏识别此bikb,直接通过kbAddTopic即可,它将bikb赋予执行对象同时在游戏中声明文件以供识别。没有被赋予bikb的对象则不可以使用语音文件。这里共有5个可用代码:kbRemoveTopic, kbHasTopic, kbTell,kbWasSaid和kbAddTopic.我将在随后的控制中介绍他们。

在任意脚本中我们给对象添加语音:

  1. Obj kbaddtopic [topicName, fileName.bikb, (fileName.fsm, (eventHandler))]
复制代码


topicName为任意字符串;
filename.bikb就是你要用的bikb文件名,注意路径;
下面两个可以省略但这里必须要用齐,否则你就无法做出随机对话的效果
filename.fsm这是用来控制AI回答的FSM文件,我们已知FSM两大功能是ARMA3的人工智能控制和任务框架,今天的第三个功能就是bikb的控制,如果对象是玩家则默认无效。
Eventhandler是汇编的sqf文件,用来控制玩家回答的文件。

这里解释一下为什么要两个不同的文件区别对待AI与玩家呢?首先,我们必须区别对待AI和玩家因为在MP模式下playable单位既可以是玩家也可以是AI,所以我们需要两个控制文件。第二,为什么AI一定得用FSM而玩家用sqf控制呢?因为fsm是布尔值控制行为的,AI就是靠布尔值堆出来的,所以必须fsm,而玩家不需要,我们有思考,有选择,所以只需要sqf列出选项即可。

现在假设在SP模式下玩家与一AI对话,我们给他们赋予相应的bikb和控制文件:

  1. player kbaddtopic ["Test","conversation.bikb","",{call compile preprocessFileLineNumbers "conversation.sqf"}];
  2. man kbaddtopic ["Test","conversation.bikb","conversation.fsm"];
复制代码


现在是不会对话的,因为我们还没写控制文件,先看玩家的sqf控制文件:conversation.sqf

  1. BIS_convMenu = [];
  2. switch (_sentenceId) do
  3. {
  4.         case "hallo_01":
  5.         {
  6.           _this kbTell [_from, _topic,"hallo_01"];
  7.     };
  8.     case "hallo_02":
  9.         {
  10.     BIS_convMenu = BIS_convMenu + [["hallo_01", _topic, "hallo_01", []]];
  11.     BIS_convMenu = BIS_convMenu + [["hallo_02", _topic, "hallo_02", []]];
  12.         };
  13. };
  14. BIS_convMenu
复制代码


玩家的控制文件的框架是这样的,其中的
_this: 代表被问话的人
_from: 问话者
_sentenceId: 字符串,就是在bikb中要说哪些话的项目名
_topic: 这个bikb的名称
和一个局部变量BIS_convMenu,这五个是不能变的,是程序设定的。

这是玩家的一个控制文件,就是当玩家被问话时根据问话者口述,玩家有权利选择自己的回答方式,当问话者说hallo_01时玩家也说hallo_01,当被问到hallo_02时玩家可以回答hallo_01或hallo_02。
BIS_convMenu是临时可回答库,它负责收集玩家可以说的回答。
搞清楚框架后,添油加醋就看你的了。

现在我们再来看看AI的conversation.fsm控制文件。比sqf要简单许多,少了一个全局变量,只有四个特殊私用变量_this,_from,_sentenceID,和_topic,意义和sqf完全一样:



大家可以在这里下载:

和sqf一样,这也是一个被动侦测的控制文件,为了简化理解我没有赋予AI思考的能力,其实我们完全可以做到。
万事俱备只欠东风,我们拥有了最基本的bikb+fsm+sqf系统,但进入游戏却没人说话,因为这两个控制文件都是被动侦测型的,所以我们先要让一个人主动开口说话:
先让AI和我们打声招呼:

  1. man kbtell [player,"test","hallo_02"];
复制代码


好了,他和我们说话了,等他讲完该轮到我们了,见证成就的时刻吧!



接下来我们就可以根据这种机制不断添加对话分支,再一次的进入奇妙的ARMA!

回头仔细想想bikb的方便度不言而喻,首先解决了一大难题就是cfgsound对语音时间无法把控的问题,第二是对话的连续性和全自动性,第三是方便的fsm,sqf可拓展性。比起纯脚本测试的复杂对话系统,写法上优化明显!最重要的是,思路清晰!

kbRemoveTopic用来移除topic, kbHasTopic则是布尔判断,最后一个是kbwasSaid.
这一般可以用作过场固定对话:

  1. miles kbAddTopic ["briefing", "kb\briefing.bikb", ""];
  2. shaftoe kbAddTopic ["briefing", "kb\briefing.bikb", ""];

  3. shaftoe kbTell [miles, "briefing", "shaftoe_briefing_H_1"];
  4. waitUntil {shaftoe kbWasSaid [miles, "briefing", "shaftoe_briefing_H_1", 3]};

  5. miles kbTell [shaftoe, "briefing", "shaftoe_briefing_M_1"];
  6. waitUntil {miles kbWasSaid [shaftoe, "briefing", "shaftoe_briefing_M_1", 3]};
复制代码


两个人可以直接你一句我一言,waitUntil代替sleep。

关于bikb的题外话,我们知道BIS_fnc_JukeBox是通过获取CfgMusic中Duration的时长定义来工作的,bikb使用类似于此的C++功能可以直接获取ogg文件时长,同时附加了控制文件的位置,应该是cfgsound的整合升级版。

下一篇我们将来谈谈distanceSqr,linearConversion和drawIcon3D。

211楼继续教程,《武装突袭3》——distanceSqr,linearConversion和drawIcon3D



本帖子中包含更多资源

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

x
您需要登录后才可以回帖 登录 | 加入VME

本版积分规则

小黑屋|中国虚拟军事网

GMT+8, 2024-5-3 19:10

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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