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

 找回密码
 加入VME

QQ登录

只需一步,快速开始

搜索
楼主: FFUR2007SLX2_5

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

    [复制链接]
发表于 2014-2-27 15:43:59 来自手机 | 显示全部楼层
一如既往地支持
 楼主| 发表于 2014-2-28 15:24:54 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2014-3-12 15:31 编辑

230楼,《武装突袭3——再见MP》



有了专用服务器,有了专业的函数编写技能,接下来就看Dwarden的了,自从V1被破解后V2与pbohider之间的战斗又开始了,反作弊的第一步在V2,第二步是BE,最后是我们的服务器端脚本。在我们的server.cfg中我们有必要使用最新的verifySignatures=2; 首先这是确保了防止作弊者使用与本服务器无关的插件进入游戏。但这并不意味着这道门可以拒所有作弊者于门外,一些强悍的黑客们也在不断升级他们的pbohider直到有一天彻底攻破V2的防线,管理员在选择插件是也必须谨慎,即使选择了信任度极高的插件作为服务器信任插件但我们没有办法保证原作者私用签名外泄而导致黑客重新修改插件混入服务器。

如果说管理员既不打开BE也不打开V2,那么只有两种可能,1、盗版服务器。2、如果只打开BE却没有V2,事实是BE根本就不检查addon,V2才会。

作为MP教程的终结篇,我想说为什么这是一片“毫无逻辑可言的雷池”,第一,涉及MP必须要有扎实的SP基本功;第二,设计MP不仅要重新了解所有SP代码的四属性(AL,AG,EL,EG)、PV的运用、带宽优化所需要建立的函数调用框架、服务器的建立、反作弊和服务器端代码、函数调用框架安全性的考虑、ARMA本身存在的bug、代码测试在适当的环境下(即dedicated模式,非server)和一些基本常识。

所以这确实让足够一批数量的玩家不断受挫最终放弃脚本的原因。这就好比看似操作简单的iPhone其实其内芯的复杂程度是常人无法想象的,好比当我们开心的玩着BattleField时却永远无法想象Devs为了达到完美要进行如何复杂的设计!

这也就印证了这个理论,精英的排他性,注定众多终端的使用客户将沦为未来IT发展的最底层人。

言归正传,作为MP的最后一篇教程,我先来介绍一下服务器端代码,这类代码独立编译,虽然它们可以与引擎脚本sqf共同使用但是目前暂不支持运算和其他类型功能,是仅限于服务器端的踢人,禁止IP或作弊检查等功能。这些服务器端代码需要与server.cfg的eh配合来产生效果,我们先来细讲一下这些eh,一般的玩家还没有注意到这些功能,在本篇中我将介绍一下:

  1. doubleIdDetected = "command";
  2. onUserConnected = "command";
  3. onUserDisconnected = "command";
  4. onHackedData = "command";
  5. onDifferentData = "command";
  6. onUnsignedData = "command";
  7. regularCheck = "command";
复制代码


在这7个server.cfg eh中都包含特殊私用变量_this.

而配合这些变量的可用代码为users,ban,kick,checkFile和numberOfFiles。
Users指令返还数组【id,name】,玩家#userlist中的id和名称。Ban为将玩家禁止,kick为踢出玩家,0 checkFile [id, index]检查制定客户端上的签名文件,1为深度检查。numberOfFiles id为检查指定客户端所挂载插件的数量。

onHackedData,onDifferentData,onUnsignedData和regularCheck中的变量_this包含_this select 0玩家id和_this select 1签名文件;而doubleIdDetected,onUserConnected和onUserDisconnected中_this只包含_this select 0玩家id。因此在配对使用时我们可以这样做:

doubleIdDetected = "ban _this"; //禁止使用相同id号的那个玩家
onUserConnected = "users _this";//检查连入玩家的信息
onHackedData = "0 checkFile _this"; //检查篡改addon者的文件信息
onUnsignedData = "kick (_this select 0)";//踢出使用未签名addon的玩家


当然服务器端的命令也就此结束了,在server.cfg的eh里我们没有办法是用其他脚本代码,比如
  1. onUnsignedData = "(_this select 0) globalchat """download unsigned addon from unknowncheat.com""" ";
复制代码


Wiki解释我们可以在这里找到:https://community.bistudio.com/wiki/ArmA:_Server_Side_Scripting
接下来我想讲一下MP编写的一些小贴士,虽然我们在MP的编写上已经没有障碍了,但是了解这些小技巧往往能使我们显得更为专业:除了我之前所讲过的MP脚本编写的基本步骤外,还有那些东西会是代码员头疼。

1、        关于常量的变化,在SP中常量是可控的但是在MP中却不同,并不是每个变量在每个客户端上都是一样的,比如说实体单位,位置,矢量等等。这就是为什么我们需要PV。
2、        每个客户端分配到的含有延时代码的脚本也会发生不同步,sleep后导致各个客户端的time不同。
3、        EH存在AL和EL问题,需要进行local检查和PV。
4、        BIS_fnc_random造成个客户端随机运行的结果不同,导致这类脚本需要服务器PV而非客户端PV。
5、        其他可能出现的问题,譬如玩家夺取旗帜等等。


我们知道如果我们只以SP的思考方式赋值变量并运行脚本,那么一旦分配到每个客户端就会发生各种意想不到结果,这些例子我已经讲过了。

所以在你开始磨拳霍霍准备对准MP大干一场时,我想将你们可能会遇到的问题集中做一个最后的示范,为各位今后的MP脚本编写之路铺平一条道路,不受阻碍!

GameLogic
我们知道所有SQM上的GameLogic全部local于服务器,换言之如果是客户端脚本创建出的GameLogic是非Local于服务器的,所以我们可以通过判断GameLogic是否Local来判断脚本是否应该由服务器执行,等同于isServer
  1. If (local GameLogic) then {call fn};
复制代码


思路
服务器所需要运行的脚本与客户端所需运行的脚本是不一样的,相对于服务器,客户端没有必要全程脚本监控所有任务的变量,客户端更多的往往是静候指令的姿态,服务器则需纵览全局适时PV变量让客户端执行。就好象任务开始时服务器要发给每个客户端各种各样的篮子(我们称他们为需要调用的函数),随后服务器开始不断的扔苹果,扔香蕉(我们称之为PV),客户端要用服务器发给他们的各种篮子去接住各种水果(我们称之为调用)。这样的MP就不会出错而且有条不紊。

多数人出错的地方往往在,不给客户端发篮子(从来不写函数),结果是客户端徒手接消耗体能(带宽利用率低);或者是服务器发不来苹果(不会PV),结果是服务器自娱自乐(变量发不出去);或者是服务器二大爷的,发筐子时附带送香蕉苹果的(不应该由客户端PV的变量却让客户端做了),真正发苹果是发现怎么客户端筐里有那么多水果?(变量失控);又或者是服务器二奶奶的,发筐子时自己都不知道里面有没有猕猴桃儿什么的(EL和EG混淆),等到发水果时发现怎么每个客户端筐里的水果都不一样而且五花八门(变量失控造成各客户端不同步)。

布尔值
和SP一样,在MP中任何未被定义的变量默认为false。
https://community.bistudio.com/wiki/Multiplayer_scripting

MP的测试环境
正如我之前讲过的isdedicated,相对于普通client server,dedicated的测试环境更加稳定,而且兼容普通客户端服务器,并且大多是公共服务器都是dedicated,所以MP的测试环境一般选择为专用服务器和至少一台客户端。(相信看到这里你一台机子也能搞定)

JIP玩家
JIP玩家的允许与否取决于description.ext对respawn的定义和管理员对disableAI的设置。如果服务器允许JIP那么任何玩家都可以在任何时候退出或重回游戏,重生,中途加入。在这点上我们不必过于纠结,因为前面的教程中我已近详述了OPC和OPD的AddStackedEH,BIS_fnc_MP对JIP的兼容,所以,这只是浮云。

MPEH
有非常多的国内玩家问我关于复活的问题,各种帖子以至于我都懒的回答了,倘若以后再有这种脑残问题,请直接引用这里的回答:
如果还有人在使用EH “Killed”,”Respawn”遇到问题,我只想说回家睡去吧,EH “killed”在MP中的表现为AL+EL,EH “Respawn”在MP的表现为AG+EL。所以,请全部使用MPEH “MPKilled”,”MPHit”和”MPRespawn”,只有它们才是AG+EG!

onPlayerKilled.sqf和onPlayerRespawn.sqf
它们同是引擎内设的默认文件,拥有各自的特殊私用变量。
onPlayerKilled.sqf将会与当客户端被杀掉的那一刻时触发,相当于一个MPEH “onPlayerKilled”, onPlayerRespawn.sqf同理,和MPEH “MPkilled”和”MPRespawn”不同的是它们都含有重生方式和重生延迟的参数设定,相当于玩家免去了写description.ext和MPEH的步骤。
这里wiki拥有详细的解释以及它们各自支持何种模式的重生:https://community.bistudio.com/w ... 661&oldid=76185
它们本身不需要进行参数的传递,在脚本中的私用变量就可以直接使用:

  1. //onPlayerKilled.sqf
  2. Private [“_type”];
  3. _type = [_this,2] call bis_fnc_param;
  4. _dely = [_this,3] call bis_fnc_param;
  5. _type = 3;
  6. _dely = 10;
复制代码


阵营复活的预设函数
MP复活是众多玩家的硬伤,不是因为它真的难,而是因为中国玩家的“懒”。各种关于复活的脑残求助帖我已近不想多提了,还有人将各种低级的复活脚本当宝似的用于自己的服务器或做为回复,什么VAS,什么norrin的复活脚本。估计玩了ARMA那么多年还未闻Karel的大名,在此如果再遇到这种低级问题可以引用这里最为最后的回复:
我怎么样让不同的阵营采用不同的复活方式?我想说请回到description.ext

  1. respawnTemplatesWest[] = {};
  2. respawnTemplatesEast[] = {};
  3. respawnTemplatesGuer[] = {};
  4. respawnTemplatesCiv[] = {};
  5. respawnTemplatesLogic[] = {};
复制代码


在这里你可以选择自己想要的阵营复活方式,karel预设的复活函数我在此不想多讲,请翻看function viewer >> Respawn或 https://community.bistudio.com/w ... 5#Respawn_Templates

关于限制复活次数
这又是一个提问的重灾区,如果还有这种问题,这里将给出“关门”回复:
请使用 https://community.bistudio.com/wiki/BIS_fnc_respawnTickets BIS_fnc_RespawnTickets 。首先我们得配合
  1. respawnTemplates[] = {"Tickets"};
复制代码
  1. respawnTemplates[] = {“EndMission”};
复制代码
由于BIS_fnc_RespawnTickets拥有EG属性所以我们可以不必PV并且可以不断覆盖或更新之前的Tickets。当Tickets耗完后玩家就再也没有办法重生了。其强大的功能作用于所有可复活的单位身上,无论AI还是玩家,也可以设置组队的复活次数,配合respawnTemplates[]完全可以达到阵营间不同的复活次数或在任务进行中更改。

给所有CAST玩家提供5次重生次数
  1. [east, 5] call BIS_fnc_respawnTickets;
复制代码


返还所有玩家复活次数
  1. [missionNamespace] call BIS_fnc_respawnTickets;
复制代码


返还玩家剩余复活次数
  1. [player,nil,true] call BIS_fnc_respawnTickets;
复制代码


设置玩家的复活次数为10
  1. [player,10] call BIS_fnc_respawnTickets;
复制代码


除了使用BIS_fnc_RespawnTickets,我们还可以用更加简单的respawnVehicle https://community.bistudio.com/wiki/respawnVehicle 来设置车辆的重生延迟和重生次数,但这里只限于车辆,而且其所有重生地点仅限于SQM中的特殊marker,重生间隔无法篡改,所以BI肯定会随着发展的需要将该代码扩展VehicleRespawnTime, setVehicleRespawnTime等等

关于复活显示倒计时
确保respawnTemplates[]含有counter计时器
  1. respawnTemplates[] = {" EndMission "," Counter "};
复制代码
随后使用BIS_fnc_respawnCounter对计时器进行设置:

倒计时显示Respawn Countdown,第二个参数为字体颜色,为0,1,2
  1. [“Respawn Countdown”,2] call BIS_fnc_respawnCounter;
复制代码


这里我们有 playerRespawnTime https://community.bistudio.com/wiki/playerRespawnTime 来返回玩家复活剩余时间,BIS_fnc_respawnCounter当然是对playerRespawnTime的可视化表达,我们还有setPlayerRespawnTime https://community.bistudio.com/wiki/setPlayerRespawnTime 作为一个EL代码会不会对函数造成影响?当然会,同时它也可以覆盖description.ext中RespawnDely的内容。

关于观察员视角的开启与限制
首先确保respawnTemplates[]含有Spectator观察员,关于观察员的控制在这里:
https://community.bistudio.com/wiki/Spectator_Mode

那如果我们要限制哪些队可以被观察,哪些不可以则:

讲可观察对象限制在Mygrp0和Mygrp1中
Missionnamespace setvariable [“RscSpectator_allowedGroups”,[Mygrp0,Mygrp1]];

限制自由摄像机的使用而只能使用人称切换观察:
Missionnamespace setvariable [“RscSpectator_allowFreeCam”,false];

关于让玩家自由选择复活地点,添加删除和移动复活地点的方法:
又是一个提问重灾区,如果再看到类似提问可以直接拿该贴枪毙。有些人以为description.ext都知道就开始吹牛皮了,我想说回井里待去吧。


首先确保你有MenuPosition,这不稀奇,关键看你会不会控制这些复活点,有些任务随着战场的不断移动复活点也随之改变,如果要添加一个可供玩家选择的复活点请使用BIS_fnc_addRespawnPosition https://community.bistudio.com/wiki/BIS_fnc_addRespawnPosition 这是个EG的函数。该函数同样区分对待不同的被复活对象:

给所有玩家新建一个可选择的复活点,位置为markerpos:
  1. MyArena = [missionNamespace,"arena"] call BIS_fnc_addRespawnPosition;
复制代码


给BLUFOR玩家新建一个可选择复活点,位置在一辆APC上:
  1. [west, APC] call BIS_fnc_addRespawnPosition;
复制代码


移除可选择复活点请使用BIS_fnc_removeRespawnPosition https://community.bistudio.com/wiki/BIS_fnc_removeRespawnPosition

移除刚才新建的MyArena可选择式复活点:
  1. myArena call BIS_fnc_removeRespawnPosition;
复制代码


接着是如何将一个单位强制移动到新建可选择式复活点上请使用:BIS_fnc_moveToRespawnPosition
  1. [player,APC] call BIS_fnc_moveToRespawnPosition;
复制代码


关于复活后装备问题:



又是一个提问重灾区,如果有人还在用VAS等等第三方插件,好吧,菜鸟一个。首先确保你有MenuInventory,这没什么,关键是你会不会控制它们。还有些人想要知道如何将现存武器继续留到复活后使用,这类属于初级脚本编写,不予答复。我们现在要看的是如何让玩家复活后进入可视化武器选择菜单进行挑选后再复活,我们要在description.ext中包含类似定义:
class CfgRespawnInventory
{
        class WEST1
        {
                displayName = "Light"; // Name visible in the menu
                icon = "\A3\Ui_f\data\GUI\Cfg\Ranks\sergeant_gs.paa"; // 玩家姓名旁显示的标记图案

                // 这里使用与CfgVehicles相同结构的武器定义
                weapons[] = {
                        "arifle_MXC_F",
                        "Binocular"
                };
                magazines[] = {
                        "30Rnd_65x39_caseless_mag",
                        "30Rnd_65x39_caseless_mag",
                        "SmokeShell"
                };
                items[] = {
                        "FirstAidKit"
                };
                linkedItems[] = {
                        "V_Chestrig_khk",
                        "H_Watchcap_blk",
                        "optic_Aco",
                        "acc_flashlight",
                        "ItemMap",
                        "ItemCompass",
                        "ItemWatch",
                        "ItemRadio"
                };
                uniformClass = "U_B_CombatUniform_mcam_tshirt";
                backpack = "B_AssaultPack_mcamo";
        };
        class WEST2
        {
                // 第二套可选装备,以此类推
                vehicle = "B_soldier_AR_F"
        };
};

现在我们如果给玩家使用这套功能需要BIS_fnc_addRespawnInventory https://community.bistudio.com/wiki/BIS_fnc_addRespawnInventory 将配置添加给所需单位,作为EG的函数同样有区分对待的功能:

给BLUFOR添加WEST1配置的可视化武器选择菜单
  1. Myequip = [west, "WEST1"] call BIS_fnc_addRespawnInventory;
复制代码


删除则使用 https://community.bistudio.com/w ... oveRespawnInventory BIS_fnc_removeRespawnInventory

  1. Myequip call BIS_fnc_removeRespawnInventory;
复制代码


Time与diag_tickTime
时间是MP中必须改过来的一个习惯,正如我之前所提到过的,由于存在JIP所以每个客户端的time肯定不同,所以_time = time; waituntil {time - _time > 1};肯定会造成JIP的不同步,所以我们必须要使用_time = diag_tickTime; waituntil { diag_tickTime - _time > 1};来替代time。

CreateVehicle
CreateVehicle作为一个EG属性的代码,一定保证其在服务器端代码执行,或者单独一个客户端上执行,否则一台64人的服务器将同时产生64个单位而非一个单位。

随机
在任何一台客户端上运行的BIS_fnc_random都会造成网络的无法同步,所以请确保随机变量全部由服务器端负责PV而非客户端运行。

触发器
触发器完全可以另设篇幅新开一篇以颠覆大家对触发器浅显的认识,在MP中,SQM触发器是公共资源,所有客户端与服务器共享。触发器可以由客户端后续生成但不建议这么做,关于这个我会另外讲。

再见MP
随着MP教程的结束,我想说的是一条踏上MP的道路就此开始,欢迎来到武装突袭3的MP世界,我就送到这里,祝各位一路走好!



233楼继续教程,《武装突袭3——Intersect,MEH及其他》

本帖子中包含更多资源

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

x
发表于 2014-3-1 15:41:39 | 显示全部楼层
来学习的
发表于 2014-3-1 21:45:06 | 显示全部楼层
看看!!!
发表于 2014-3-2 18:41:36 | 显示全部楼层
本帖最后由 烈焰碧空 于 2014-3-2 19:47 编辑

版主大好人呐,看了教程我豁然开朗了,原来MP任务是酱紫编出来的~~
我也去编一个{:soso_e130:}
发表于 2014-3-5 20:01:15 | 显示全部楼层
爱死了
发表于 2014-3-6 19:02:51 | 显示全部楼层
支持斑竹,感谢版主,期待触发器专刊{:soso_e113:}
 楼主| 发表于 2014-3-12 15:17:58 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2014-3-16 23:13 编辑

233楼,《武装突袭3——Intersect,MEH及其他》



在本片开始之前,先表达本人对于此次马航失联航班的深切关注,以及目前复杂国际形势的担忧之情。 - ffur

正篇: https://community.bistudio.com/wiki/intersect Intersect 家族或许我们对它们比较生疏,那今天我们就来看看其具体表现。先看ArmA 于2005年引进的Intersect,如果wiki的解释始终看不明白那么请看这里的解释:先明白建模时的Lod层,如果在ARMA中建立过自己模型的朋友都应该知道Lod层,这些Lod层的作用是用来供引擎识别,比如说Geom层是模型的碰撞层,它并没有视觉效果但没有碰撞层物体就可以穿过;Mass是用来设定物体的重量,没有它物体则会飞起来;贴图层提供最直观的视觉效果,没有它物体则是隐形的;fire层是物体承受物理伤害的层,没有它子弹则会直接穿过并不造成杀伤。



所有的这些层都是三维建模,也就是说我可以画任何一根直线随后穿过这些Lod层,Interesct判断一旦在游戏环境下模型的Lod被两个3D坐标所画出的直线穿过便会触发并返还被穿透部分的细节内容。
在ArmA和OA的时候想要做成这个实验还是相当的困难,因为这时的引擎并不支持可视3D线,但在ArmA3就不同了,我们通过DrawLine3D来完成这个实验。(DrawLine3D和DrawIcon3D都需要FPH,这里我就偷懒用FPH,其实MEH也可以)

  1. Fn_DrawLine = {
  2. oneachframe {drawLine3D [[position m0 select 0,position m0 select 1,1.75],[position m1 select 0,position m1 select 1,1.75],[0,1,1,1]];};
  3. };
  4. Fn_Call = {
  5. [player,"fire"] intersect [[position m0 select 0,position m0 select 1,1.75],[position m1 select 0,position m1 select 1,1.75]];
  6. };
复制代码




这样当我们把身体的任何一个部位传入这条线便可以触发Intersect并返回数组:
[["head",2.63243]];这里我们可以看到头被穿了。当然你可以做更多的实验并且Intersect会返还不同的结果。通过这个代码我们又可以充分发挥我们的创造力了,比如判断一群人是不是排成直线了?两点之间是否被某个物体挡住了?等等。

接着我们再来看看lineIntersects,作为OA于2010年引进的代码,lineIntersects并不会返回被穿透的具体Lod位置,而是返还是否有物体作为遮挡物出现,返还布尔值。https://community.bistudio.com/wiki/lineIntersects 与Intersect的坐标截取不同,lineIntersects需要海拔坐标,即GetPosASL,EyePos及AimPos,这些代码返还的都是海拔高度,海拔高度的作用不是用来判断水平面上是否有物体遮挡了,而是判断线上是否有物体遮挡,看下实际效果,只要线上拥有物体遮挡,LineIntersects便会返回true。(在这里按照ASL画出的线是在空中的,这就意味着Intersect与LineIntersects坐标处理上的不同,这也就为什么引进了ATLtoASL和ASLtoATL的转换代码)



了解完LineIntersects,我们再来看看LineIntersectsWith,作为OA 2010年引进的代码,它与前两个有什么不同:https://community.bistudio.com/wiki/lineIntersectsWith 和LineInterescts相似,该代码依旧使用ASL坐标用于线上遮蔽的物体,但并不返还布尔值而是直接返还遮蔽物体。Wiki上描述了5个参数,我们看一下其实际效果:



我们通过ATL画线:

  1. oneachframe {drawLine3D [getpos player,screenToWorld [0.5,0.5],[0,1,1,1]];}
复制代码


随后使用LineIntersectsWith判断,凡是穿过的物体都会返回:

  1. lineIntersectsWith [eyePos player, ATLtoASL screenToWorld [0.5,0.5]]
复制代码


返回:[B Alpha 1-1:2]
接下来是terrainIntersect和terrainIntersectASL,它们的不同之处在于BI直接提供了两套坐标,这样我们就不必转换了,相同的是它们都用来判断线上是否穿透地表,返还布尔值:
terrainIntersect [position player, position car0] 返还true



最后一个则是lineIntersectsObjs,它是BI于2014年新增的一个ZEUS代码,采用ASL坐标,与lineIntersectsWith相似不过新增flag参数允许筛选。https://community.bistudio.com/wiki/lineIntersectsObjs 首先我们先画条线,这次用MEH:

  1. addMissionEventHandler ["draw3d",{drawLine3D [(ASLToATL (eyepos player)),screenToWorld [0.5,0.5],[1,0,0,1]];}];
复制代码


接着直接上代码:

  1. addMissionEventHandler ["draw3d",{hintSilent format ["%1",lineIntersectsObjs [(eyepos player),ATLToASL (screenToWorld [0.5,0.5]),objNull,objNull,false,2]];}];
复制代码


这也是一个相当强悍的功能,比起只能返还static_obj的cursortarget强多了,在这个例子中你可以看到视线所指向的第一个物体。



依次介绍完它们后我们看看在实际中能有哪些作用,这里有无限可能就看你能否想得到了,比如我们可以来一个特工任务,好莱坞里博物馆的激光探测器是不是很带感?



  1. private ["_b","_bpa","_pa","_z","_l"];
  2. Globle_Arr = [];
  3. _b = [_this,0,objNull,[objNull]] call BIS_fnc_param;
  4. _l = [_this,1,1,[0]] call bis_fnc_param;
  5. _bpa = _b buildingpos 0;
  6. if (_bpa distance [0,0,0] > 0) exitwith {
  7. _pa = _b call BIS_fnc_boundingBoxCorner;
  8. _z = ((boundingboxreal _b) select 1) select 2;
  9. for "_i" from 0 to ((floor _l) - 1) do {
  10.   private ["_begP","_endP"];
  11.   _begP = _pa call bis_fnc_selectrandom;
  12.   _endP = _pa call bis_fnc_selectrandom;
  13.   _begP = [_begP select 0,_begP select 1, random _z];
  14.   _endP = [_endP select 0,_endP select 1, random _z];
  15.   Globle_Arr set [_i,[_begP,_endP]];
  16. };
  17. addmissioneventhandler ["draw3d",
  18.   {
  19.    {
  20.     drawline3d [_x select 0,_x select 1,[1,0,0,1]];
  21.    } foreach Globle_Arr;
  22.   }
  23. ];
  24. waitUntil {
  25.   (
  26.   {
  27.   lineintersects [_x select 0,_x select 1,_b];
  28.   } count Globle_Arr
  29.   ) >= 1
  30. };
  31. hint "Activated!";
  32. };
复制代码


小贴士:BIS_fnc_boundingBoxCorner可以返还obj的四个点坐标,当然我认为Moricky还有改进的空间让计算更精确;BoundingBox和BoundingBoxReal之间我选择了后者,因为前者已经淘汰;这里的DrawLine3D我选用MEH载体来代替FPH,Draw3D也是一个每帧类的,和FPH或FSM相同,举个例子:addMissionEventHandler ["draw3d",{hintSilent str diag_tickTime}];(相当强大的一个命令,某种意义上可以代替addStackedEH OnEachFrame并且支持分离码,只不过在特定事件上的移除没有addStackedEH来的方便,所以函数部分以得以保存下来并且在Dev1.12中得到了改进);最后是所有EH都不支持私用变量的传递,它们只支持全局变量。

再来一个实例,如何通过Intersect找到物体所有的LOD层?A3依旧有许多模型是加密并且我们不可能打开它们看到其所有MLOD层,不过至少通过Intersect家族我们能了解MLOD层的名称。
开始前我先介绍一下 https://community.bistudio.com/wiki/selectionPosition selectionPosition 它可以很方便的定位LOD层在模型中的位置:



  1. vehicle player spawn {
  2.     _box = boundingBoxReal _this;
  3.     {
  4.         _sel = []; _nsel = [];
  5.         for "_i"
  6.         from (_box select 0 select 2)
  7.         to (_box select 1 select 2)
  8.         step 0.1 do {
  9.             for "_j"
  10.             from (_box select 0 select 0)
  11.             to (_box select 1 select 0)
  12.             step 0.1 do {   
  13.                 {
  14.                     _s = _x select 0;
  15.                     if (
  16.                         _this selectionPosition _s distance [0,0,0] > 0
  17.                     ) then {
  18.                         _sel = (_sel - [_s]) + [_s];
  19.                     } else {
  20.                         _nsel = (_nsel - [_s]) + [_s];
  21.                     };
  22.                 } forEach call {
  23.                     _p1 = _this modeltoWorld [_j,_box select 0 select 1,_i];
  24.                     _p2 = _this modelToWorld [_j,_box select 1 select 1,_i];
  25.                     drawLine3D [_p1, _p2, [1,0,0,1]];
  26.                     [_this, _x] intersect [_p1, _p2]
  27.                 };
  28.             };
  29.         };
  30.                 {diag_log_x;} foreach [_sel,_nsel];
  31.     } forEach ["VIEW","GEOM","FIRE"];
  32. };
复制代码


显然我们可以得到物体所有的MLOD层名称。

242楼继续教程,《武装突袭3——刷军营与selectbestplaces》

本帖子中包含更多资源

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

x
发表于 2014-3-12 16:06:54 | 显示全部楼层
FFUR2007SLX2_5 发表于 2014-3-12 15:17
233楼,《武装突袭3——Intersect,MEH及其他》

看完了版主最新的代码 特地提问一下~版主可否用简单的一句代码来说明一下如何实现如下效果?

判断两个人之间 是否有障碍物(不管是物体还是地形) 返还布尔值
发表于 2014-3-12 16:53:51 | 显示全部楼层
本帖最后由 下网上载 于 2014-3-12 17:25 编辑


版主桑~感觉这个判断不太准确呢~


addMissionEventHandler ["draw3d",{drawLine3D [getposATL player,getposATL p1,[1,0,0,1]];}];  
addMissionEventHandler ["draw3d",{hintSilent format ["%1",terrainIntersect [position player, position p1]];}];  

这么写~

玩家和P1在这个时候 中间没有相隔的东西但是返还出fasle。。。。

大概想做出那种潜入类的效果~~修改一下AI的发现机制~~让躲在障碍物里的玩家不被AI敌人发现
怎么获取AI头部所向角度是否能观察到玩家呢~有时候AI会穿过掩体直接看到人。。。。

本帖子中包含更多资源

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

x

点评

嗯,关于AI头部可以试下lineIntersects配合eyepos,接着配合bis_fnc_relativeDirTo与knowsAbout模拟AI头部,当然AI自己的转头属于animation,AI自己无意识。  发表于 2014-3-12 17:44
恩哼,是这样的,这个terrainIntersect在平地其实就是false,你试试跑到有山丘的地方,或凹凸的地面看看。  发表于 2014-3-12 17:25
发表于 2014-3-12 16:57:43 | 显示全部楼层
希望楼主将帖子整合一下,搞个完整版的教程,跪谢
发表于 2014-3-12 19:58:57 | 显示全部楼层
斑竹好人,支持斑竹!
发表于 2014-3-12 20:31:29 | 显示全部楼层
斑竹你好,请教个问题哈
一个步兵班的成员,让他们从一个阵地乘坐步战车到另一个阵地下车,然后在脚本中给每个兵setPos,位置坐标都没有问题。
但是所有兵下车后,只有班长跑到指定的位置,其他人都按照默认队形跑到班长两侧(好像叫三角队形)。(经测试,如果兵和兵之间没有编组关系,可以跑到指定的位置)

请问有什么方法让这些兵下车后跑到指定的位置吗?
谢谢
 楼主| 发表于 2014-3-16 22:59:06 | 显示全部楼层
本帖最后由 FFUR2007SLX2_5 于 2014-3-21 19:17 编辑

242楼,《武装突袭3——刷军营与selectbestplaces》



还记得曾经我在ARMA2 F7模组教程中刷军营(FOB)的一篇帖子吗?也有不少玩家要求修复损坏的样板文件,坏消息是,很抱歉由于年久失修那个教程我已经彻底打算将其报废了,所以不再做更新了;好消息是,今天我把刷军营带到了A3,大家又可以重新燃起激情了!

何为造军营?就是自己在地图上一个一个创建帐篷,围栏,沙袋等等,随后不断调试,移位最后建成了自己满意的军营,就像这样:



随后就成这样了:



“额,老子花了一整天终于把这个破玩意儿给捣鼓出来了,累死爹了。”相信99%的朋友都有类似的经历并且在做任务时也是这么操作的。
剩下1%的玩家说:干嘛这么累,看我的脚本!于是花了一星期写了脚本可以“刷兵营”了,干得不错,上了一个层次并且可以在任务中随时调用生成。不过问题又来了,脚本刷建筑无非两种:预设位置与随机位置,预设位的缺点在于局限性,随机位在不确定性。一个精心设计的布局随机脚本无法达到该质量,而全部转换成脚本的对应坐标则费时费力,wiki上有专门的教程但看得人一头雾水:https://community.bistudio.com/w ... 789&oldid=69265 更糟的是还不支持A3。不过现在好了,在此篇教程中我将为大家介绍两位新朋友:BIS_fnc_ObjectsGrabber和BIS_fnc_ObjectsMapper。这两个函数的作者是Joris-Jan van 't Land(就是在希腊被兜进去的那个,出来后现在混到A3的项目负责人了)

先来看看它们派什么用:BIS_fnc_ObjectsGrabber用来复制你的的军营,就是说你得先自己在地图上搞个军营,虽然累点但是必须的,而且可以搞细致点,因为这次BIS_fnc_ObjectsGrabber可以把你的军营布局给永久的保存起来,以后就再也不用重新做了,MP任务可以直接丢进服务器脚本让脚本自己去刷。
BIS_fnc_ObjectsMapper就是刷军营的脚本,你可以将复制过来的军营以随意角度,任意地点,数量随意调用,非常方便。

现在看看怎么用,回到第一张图,我们先复制布局:

  1. [position player,100] call BIS_fnc_ObjectsGrabber
复制代码


具体参数解释可以在函数库中了解,在此不介绍。
回到记事本得到数组:

  1. /*
  2. Grab data:
  3. Mission: Unnamed
  4. World: Stratis
  5. Anchor position: [1626.65, 5273.64]
  6. Area size: 100
  7. Using orientation of objects: no
  8. */

  9. [
  10.         ["Land_BagFence_Long_F",[-2.85596,1.99854,0],137,1,0,[],"","",true,false],
  11.         ["Land_BagFence_Long_F",[-4.9906,2.22754,0],47.8449,1,0,[],"","",true,false],
  12.         ["Land_BagFence_Long_F",[1.37378,6.21191,0],137,1,0,[],"","",true,false],
  13.         ["Land_BarrelWater_F",[0.496948,7.20605,5.67436e-005],227.833,1,0,[],"","",true,false],
  14.         ["Land_BagFence_Long_F",[-7.03015,4.48242,0],47.8449,1,0,[],"","",true,false],
  15.         ["Land_BagFence_Long_F",[1.47778,8.23877,0],47.8449,1,0,[],"","",true,false],
  16.         ["Land_BagFence_Long_F",[-0.562744,10.4814,0],47.8449,1,0,[],"","",true,false],
  17.         ["Land_BagBunker_Small_F",[-10.7648,5.86182,0],47.8449,1,0,[],"","",true,false],
  18.         ["Land_BagBunker_Small_F",[-1.53735,14.3711,0],227.845,1,0,[],"","",true,false],
  19.         ["Land_BagFence_Long_F",[-11.7985,9.74609,0],47.8449,1,0,[],"","",true,false],
  20.         ["Land_BagFence_Long_F",[-5.33801,15.7549,0],47.8449,1,0,[],"","",true,false],
  21.         ["Land_WaterTank_F",[-10.0859,14.7197,-8.58307e-006],227.845,1,0,[],"","",true,false],
  22.         ["Land_BarrelWater_F",[-11.7946,13.7856,5.72205e-005],227.834,1,0,[],"","",true,false],
  23.         ["Land_WaterTank_F",[-8.97791,15.7915,-4.76837e-006],227.845,1,0,[],"","",true,false],
  24.         ["Land_BagFence_Long_F",[-13.756,11.9941,0],47.8449,1,0,[],"","",true,false],
  25.         ["Land_WaterTank_F",[-7.82532,16.9185,-4.76837e-006],227.845,1,0,[],"","",true,false],
  26.         ["Land_BagFence_Long_F",[-7.29175,18.0049,0],47.8449,1,0,[],"","",true,false],
  27.         ["Land_BagFence_Long_F",[-13.7778,13.9873,0],137,1,0,[],"","",true,false],
  28.         ["Land_BagFence_Long_F",[-11.692,15.9463,0],137,1,0,[],"","",true,false],
  29.         ["Land_BagFence_Long_F",[-9.44739,17.9038,0],137,1,0,[],"","",true,false],
  30.         ["Snake_random_F",[33.8726,37.1655,0.00838757],269.029,1,0,[],"","",true,false]
  31. ]
复制代码


这就是我们的军营布局,A3总是会把蛇给一起复制进来,你可以把它删掉。

接下来我们在任意地点刷军营:

  1. [position player,88,[   ["Land_BagFence_Long_F",[-2.85596,1.99854,0],137,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-4.9906,2.22754,0],47.8449,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[1.37378,6.21191,0],137,1,0,[],"","",true,false],    ["Land_BarrelWater_F",[0.496948,7.20605,5.67436e-005],227.833,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-7.03015,4.48242,0],47.8449,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[1.47778,8.23877,0],47.8449,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-0.562744,10.4814,0],47.8449,1,0,[],"","",true,false],    ["Land_BagBunker_Small_F",[-10.7648,5.86182,0],47.8449,1,0,[],"","",true,false],    ["Land_BagBunker_Small_F",[-1.53735,14.3711,0],227.845,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-11.7985,9.74609,0],47.8449,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-5.33801,15.7549,0],47.8449,1,0,[],"","",true,false],    ["Land_WaterTank_F",[-10.0859,14.7197,-8.58307e-006],227.845,1,0,[],"","",true,false],    ["Land_BarrelWater_F",[-11.7946,13.7856,5.72205e-005],227.834,1,0,[],"","",true,false],    ["Land_WaterTank_F",[-8.97791,15.7915,-4.76837e-006],227.845,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-13.756,11.9941,0],47.8449,1,0,[],"","",true,false],    ["Land_WaterTank_F",[-7.82532,16.9185,-4.76837e-006],227.845,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-7.29175,18.0049,0],47.8449,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-13.7778,13.9873,0],137,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-11.692,15.9463,0],137,1,0,[],"","",true,false],    ["Land_BagFence_Long_F",[-9.44739,17.9038,0],137,1,0,[],"","",true,false],    ["Snake_random_F",[33.8726,37.1655,0.00838757],269.029,1,0,[],"","",true,false]  ]  ] call BIS_fnc_ObjectsMapper
复制代码


随便你刷几个兵营都不在话下:



当然在实际使用中我们不会把那么一大串数组直接使用,我们可以将其汇编:

  1. [positionWhereYouWantToSpawnYourComposition, azimutOfYourComposition, call (compile (preprocessFileLineNumbers "yourComposition.sqf")),Propertion] call BIS_fnc_ObjectsMapper;
复制代码


现在我们来看看成果:





FOB已经搞定了,现在就来看看如何选择合适的地点来刷FOB,我们知道如果在非常陡峭的地方军营刷出来是无法判断法线角所以会斜插入地面,这样很不美观,所以我们需要找到合适的地方来生成FOB。
selectBestPlace https://community.bistudio.com/wiki/selectBestPlaces 是于2009年引入的一个代码,作为一个非常强劲的代码家族许多人却只知道其皮毛,在高级篇中我将着重介绍并且结合BIS_fnc_ObjectsMapper创造出极具动态效果的任务,selectbestplace拥有强大的参数组合 https://community.bistudio.com/wiki/Ambient_Parameters 譬如说寻找整张地图的最高点或最低点,将FOB生成在陆地上而非水里,将动物生成在草地里而非沙漠上,将人生成于城镇中而非荒野中等等。
Rube在这里有比较详细的介绍 http://forums.bistudio.com/showthread.php?93897-selectBestPlaces-(do-it-yourself-documentation)
我们来着重分析一下selectbestplace的计算表述:

1、        如何找到整张图的最高点?幸运的是BI的程序员们已经帮我们写好了,所以我们不必再用C++或sqf自己去写了:
  1. 0 = [] spawn {my_pos = ((selectBestPlaces [position player, 5000, "(1+hills)", 1, 1]) select 0 select 0); player setPosATL [my_pos select 0,my_pos select 1,0];}
复制代码


2、        那么最低点呢?
  1. 0 = [] spawn {my_pos = ((selectBestPlaces [position player, 5000, "(1-hills)", 1, 1]) select 0 select 0); player setPosATL [my_pos select 0,my_pos select 1,0];}
复制代码
你直接进海沟里了。

3、        找到树林里的房子:
  1. 0 = [] spawn {my_pos = ((selectBestPlaces [position player, 5000, "(1 + forest) * (1 + houses)", 1, 1]) select 0 select 0); player setPosATL [my_pos select 0,my_pos select 1,0];}
复制代码


4、        找到有树林的,风大的地方:
  1. 0 = [] spawn {my_pos = ((selectBestPlaces [position player, 5000, "(1 + forest) * (1 + windy)", 1, 1]) select 0 select 0); player setPosATL [my_pos select 0,my_pos select 1,0];}
复制代码
等等,你可以进行任何搭配找到你所需要的位置,其基本原理就是组合使用乘法,全有为1+,全无为1-,那么一半一半呢?则可以是0.5*,当然你还可以使用更加复杂的算法12 - 8 * hills等等,各种配合可以通过简单的测试来观察效果。
Selectbestplace的最后两个参数,除数和返回数量。除数代表多点选取的间距,除数越小当然点数越多,返回数量则是从最佳到最差逐步筛选的坐标数组。因此我们可以通过selectbestplace轻松得到所需区域的关键点。

【新增A3DEV112】在升级到dev 1.12后selectbestplace共支持12种表述(感谢BI Dev DarkDruid与2014年3月13日做出的wiki更新):
rain [0-1] – 雨的稠密度
night [0-1] –夜间暗度
meadow [0-1] – 是否像草地
trees [0-1] – 有多少树木包围
hills [0-1] – 0 + hills为海拔高度大于160m; 1 + hills为海拔高度大于400m
houses [0-1] – 周围有多少房子
windy [0-1] – 区域风强度
forest [0-1] – 是否在树林中
deadBody [0-1] – 1 + deadBody离尸体很近,1 - deadBody离尸体很远
sea [0-1] – 1 + sea在海里,1 – sea在陆地上
waterDepth – 水深,1 + waterDepth最大水深
camDepth – 水中照相机深度,1 + camDepth最大水深

【新增A3DEV112】 (感谢BI Dev DarkDruid与2014年3月13日提供的强劲表述)



还记得A2时代的Ambient Animal吗?那些个动物是如何选取位置的?BI程序员不可能把羊群弄到森林里,到了A3,海龟,鱼这种东西不可能刷到陆地上来,而且即使在水中也有各自的生成点,至少要符合自然规律,所以在这里我新增了更复杂的表述:
https://community.bistudio.com/wiki/Simple_Expression 我们引入新概念“简易计算”
  1. 0 = [] spawn {my_pos = ((selectBestPlaces [position player, 5000, "((sea * (1 - night)) + (2 * houses * sea)) * (1 - night)", 1, 1]) select 0 select 0); player setPosATL [my_pos select 0,my_pos select 1,0];}
复制代码

这个位置是海鸟的生成点,我们可以看到玩家直接来到了码头,来分析一下:((sea * (1 - night)) + (2 * houses * sea)) * (1 - night)就是(入海有亮光的地方)+(两倍入海靠房有亮光的地方)



  1. 0 = [] spawn {my_pos = ((selectBestPlaces [position player, 2000, "(20 * (0.1 - houses)) * (1 - sea)", 1, 1]) select 0 select 0); player setPosATL [my_pos select 0,my_pos select 1,0];}
复制代码
这个是兔子常去的地方,(离房子略远的陆地上)



  1. 0 = [] spawn {my_pos = ((selectBestPlaces [position player, 2000, "(4 * (WaterDepth interpolate [1,30,0,1]))", 1, 1]) select 0 select 0); player setPosATL [my_pos select 0,my_pos select 1,0];}
复制代码
这是深海动物常去的地方,这里的interpolate, factor和randomGen是A3的算法,想必我们知道加减乘除却不知道还有这3个,顺便科普一下。Interpolate是于2009年引入的算法,这里就相当于:

WaterDepth interpolate [p,q,r,s] =
WaterDepth <=p:r
WaterDepth >=q:s
p< WaterDepth <q: [(WaterDepth -p)/(q-p)]*(s-r) +r


这个就是一个加法运算,只不过再加上了因数和加数相成的结果,如果原始数超出范围则直接取r或s的值(上高数课时在睡觉?)。

A randomGen则直接取0~A的随机浮点数,强悍的算法,以后别忘了。

A factor [p,q] =
p< WaterDepth <q: [(WaterDepth -p)/(q-p)]


算出因数,以后做算术不用写长方程了。由于目前这三个只支持定义表达和selectbestplace,脚本中的算法并不支持,所以还有局限性,不过如果需要BI会将其纳入代码块。



  1. 0 = [] spawn {my_pos = ((selectBestPlaces [position player, 500, "(2 * (waterDepth interpolate [1,16,0,1]) * ((1-houses) * (1-houses)))", 1, 1]) select 0 select 0); player setPosATL [my_pos select 0,my_pos select 1,0];}
复制代码
这种应该算是沿岸海域,海龟活动的地方。现在开始发挥想象力了,重新认识selectbestplace后又可以开动你们的战争机器了!



【小贴士】:当然有些时候我们的随机定位脚本并不需要大量的selectbestplace运算以牺牲速度,所以surfaceType也是不错的选择用来筛选地表类型。

接着是 findEmptyPosition https://community.bistudio.com/wiki/findEmptyPosition 同时于2009年引进的代码,主要作用是搜索空地,当然包括法线角倾斜得厉害的地形,貌似比较鸡肋但至少我们还是能够找到适合停直升机的一块场地,先不看是否平整但必须足够大,我们的FOB也一样,必须得容得下我们的建筑:

  1. position player findEmptyPosition [0,150,"Land_City2_8mD_F"]
复制代码


这里一笔带过,最后来看isFlatEmpty https://community.bistudio.com/wiki/isFlatEmpty 2009年的代码,一串数组,配合其家族代码,负责判断地形是否是空地,为我们接下来的FOB选址做铺垫。

  1. oneachframe {hintsilent str (position player isFlatEmpty [10, 0, 0])};
复制代码


其实我们不需要那么多参数,三个就够了,isFlatEmpty仅仅判断是否空地!而并不判断是否平整,feedBack已经有人反映了,就看1.14能否解决了。

万事俱备只欠东风,又开始开动脑筋的时刻了,在这里我写了一个简单的函数来返还可用空地给我们来建FOB。

  1. /*
  2. fn_findflatpos
  3. author: ffur
  4. param: 0 - number range, 1 - string expression, 2 - number sourcecount, 3 - number mindistance
  5. */
  6. private ["_range","_expression","_count","_array","_Narray","_flat","_flatArray"];
  7. _Narray = [];
  8. _flatArray = [];
  9. _range = [_this,0,0,[0]] call bis_fnc_param;
  10. _expression = [_this,1,"",["",1]] call bis_fnc_param;
  11. _count = [_this,2,1,[1]] call bis_fnc_param;
  12. _flat = [_this,3,0,[0]] call bis_fnc_param;
  13. _array = selectbestplaces [position player,_range,_expression,1,_count];
  14. {
  15.    _Narray set [_foreachindex,_x select 0];
  16. } foreach _array;
  17. {
  18.    if ((count (_x isflatempty [_flat,0,0])) > 0) then {
  19.       _flatArray set [count _flatArray, asltoatl (_x isflatempty [_flat,0,0])];
  20.    };
  21. } foreach _Narray;
  22. _flatArray
复制代码


使用时[200,"1 + meadow",20,10] call fn_findflatpos即可直接找到可用的空点,随后我们随机选取一个地点来生成我们的FOB
  1. [(([200,"1 + meadow",20,10] call fn_findflatpos) call bis_fnc_selectrandom),random 360, call (compile (preprocessFileLineNumbers "yourComposition.sqf"))] call bis_fnc_ ObjectsMapper;
复制代码
当然你还可以做更多异想天开的事,僵尸搜索尸体地点,平民随机生成系统等等。

245楼继续教程,《武装突袭3——HandleDamage与cfg函数》


本帖子中包含更多资源

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

x
发表于 2014-3-17 16:24:28 | 显示全部楼层
真是没有做不到,只有想不到啊,强烈支持!
您需要登录后才可以回帖 登录 | 加入VME

本版积分规则

小黑屋|中国虚拟军事网

GMT+8, 2024-5-3 20:03

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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