骑马与砍杀中文站论坛

 找回密码
 注册(Register!)

QQ登录

只需一步,快速开始

搜索
购买CDKEY 小黑盒加速器
查看: 4127|回复: 4

[经验与教程] 骑砍2多重箭,AddCustomMissile生产投射物方法的使用

[复制链接]

30

主题

226

回帖

192

积分

见习骑士

Rank: 3

UID
2758789
第纳尔
2148
精华
0
互助
21
荣誉
1
贡献
0
魅力
201
注册时间
2016-7-18
鲜花(22) 鸡蛋(0)
发表于 2023-12-21 10:49:10 | 显示全部楼层 |阅读模式
本帖最后由 路过的罗格 于 2024-4-20 15:23 编辑

好的啊各位,感觉差不多摸清楚了,来整个教程,对于在骑砍2里面,怎么在战场上生成投射物。
咱们的教程会照顾和我一样刚开始整mod的兄弟,会加上一些基本没有必要说的东西,但首先假设你已经最起码有了一个“hello world”模组,就教程里那种在开始界面添加一个按钮,点击以后跳字的东西,代表最起码能完成c#模组的创建。模组的创建我以后会出一个教程,但是先让我把这段时间折腾的AddCustomMissile写好再说。
首先说一下代码的调用顺序,或者说骑砍2是怎么获取到你新写的代码。
Mod的入口,都是那个SubModule.xml,这是所有mod都会有的东西。其中<SubModules>标签中,是用来配置你的c#代码的入口。比如你的SubModule.xml中配置了以下内容
(点击展开 / 收起)

如果有不太清楚的概念,可以直接丢去文心一言之类的东西里,虽然现在指望他直接给我输出代码没什么希望,但是问东西还能凑合。
(点击展开 / 收起)



现在咱们就已经算是完成了c#代码和骑砍2的链接,可以开始写代码了。
先在继承自MBSubModuleBase的类中,重写一下OnMissionBehaviorInitialize方法。重写相关的概念自行百度,接着刚才的代码完成后如下,代码的意思我直接用注释写代码后面
(点击展开 / 收起)

到这里,我们约等于完成了在骑砍1中打开module_mission_templates.py开始写东西的操作……
然后调用mission的AddMissionBehavior方法,来给所有的任务/战场添加一个自己写的类,比如mission.AddMissionBehavior(new NewMissionLogic());
如果需要限定只在某些战场触发,那就自己去写if,暂时我还没去细看怎么限定场景。同时,也去完成一下自己的NewMissionLogic类的声明,这个类需要继承自MissionLogic类。现在的代码如下
(点击展开 / 收起)


正常来说,Mission中的AddMissionBehavior方法,接收的类型应改为MissionBehavior,而不是NewMissionLogic继承的MissionLogic类。但是实际上MissionLogic继承自MissionBehavior,所以MissionBehavior子类的子类还算MissionBehavior的子类,因此这个类可以当做AddMissionBehavior的参数。同样,因为我们的NewMissionLogic间接继承了MissionBehavior,所以MissionBehavior中的各个可重写的方法,我们也能拿来改。比如OnMissileHit当投射物命中、OnAgentCreated当agent生成、OnMissionTick战斗中每帧、等等,更多的可以自己点开MissionBehavior.cs的反编译文件看。
现在,咱们来完成一个简单的功能:人物射击时,额外的生成n个箭矢。用这个功能来介绍AddCustomMissile方法。
首先,相较于1代需要自己写动作判断,2代可以直接通过重写OnAgentShootMissile方法来完成获取人物射击的时机。这个方法,游戏引擎会在人物将新的投射物生成时进行调用,他的参数分别是
  • Agent类型的射击者
  • EquipmentIndex类型的武器索引,可以用来获取投射物的信息
  • vec3类型的投射物生成位置(飞机的飞行起点)
  • vec3类型的投射物飞行方向和速度,以速度向量的形式封装在一起(飞机的飞行航线)
  • mat3类型的投射物飞行姿态(飞机是头朝前飞还是屁股朝前飞)
  • 布尔类型的是否为刚体-概念自行百度
  • int类型的对投射物索引的操作

这些参数,会自动在游戏引擎调用OnAgentShootMissile时传递过来,在自己的代码里咱们只管拿着用就可以了。然后别忘记顺便base.一下,调用一遍重写前的代码。
(点击展开 / 收起)



我们再来看一下用来添加投射物的AddCustomMissile方法,他需要的参数分别是
  • Agent类型的射击者
  • MissionWeapon类型的投射物武器
  • vec3类型的投射物生成位置(飞机的飞行起点)
  • vec3类型的投射物飞行方向(飞机的飞行航线),可以从velocity中抠出来
  • mat3类型的投射物飞行姿态(飞机是头朝前飞还是屁股朝前飞)
  • 浮点数类型的基础速度,如果是投掷武器,保持和最终速度一致。如果是弓弩,伤害有问题,这个问题以后再解决。
  • 浮点数类型的速度,实际投射物速度,可以从武器的投射物速度上获取
  • 布尔类型的是否为刚体-概念自行百度
  • MissionObject类型的忽略的目标,避免投射物命中shooterAgent。可以直接填null
  • int类型的对投射物索引的操作,通过设定这个值并且加以记录,可以实现对自己创建的每根投射物的控制。但是如果设置不好,就是难以解决的字典key冲突。建议用默认的-1。

反编译源码:    public void AddCustomMissile(Agent shooterAgent, MissionWeapon missileWeapon, Vec3 position, Vec3 direction, Mat3 orientation, float baseSpeed, float speed, bool addRigidBody, MissionObject missionObjectToIgnore, int forcedMissileIndex = -1)

可以看到,OnAgentShootMissile的参数基本和AddCustomMissile的参数是一致的,大部分可以直接拿来用。不同的是武器,可以通过以下操作来完成转换
  1. // 从Agent的装备中获取主手的装备元素

  2. MissionWeapon mainHandEquipmentElement = agent.Equipment[weaponIndex];
复制代码


至此,我们就可以使用AddCustomMissile来添加一个投射物了,并且呢,我们给他的射击角度写一个循环,让他完成多重投掷。先说一些可以了解一下的知识点,AddCustomMissile是Mission类中的一个方法,虽然可以直接Mission.AddCustomMissile完成对AddCustomMissile的调用,不过还是加一下.Current,进行一个当前Mission实例的获取。在不少类中,都有.Current方法,可以用来获取当前活动的对应类。
(点击展开 / 收起)


现在,我们做好了投掷物的多重射击。但如果有哪个倒霉孩子偷偷摸摸取用弓弩进行了这个代码的尝试,肯定已经被跳出出来了。原因也很简单,请自行脑补9mm手枪射击弹道凝胶.glf和复合弓的正确使用方式.glf,对于弓弩,missileWeapon应该是他们的弹药,而不是他们本身。弓弩的弹药可以用
MissionWeapon ammo = shooterAgent.Equipment[weaponIndex].AmmoWeapon;
来完成获取。别忘记自行写一个if去判定武器是弓弩还是投掷物(这段代码可以直接抄OnAgentShootMissile的反编译源码)
(点击展开 / 收起)


        好了,现在在OnAgentShootMissile中的多重箭基本就完成了,以后可以把两个for循环改成一个for循环限制发射数量,里面两个random来随机发射角度。
(点击展开 / 收起)


但是,很显然很多时候我们并没有OnAgentShootMissile中直接获取到这么多参数的条件,比如OnMissionTick中,他传递进来的参数干脆没啥用,所以下面是AddCustomMissile各个参数的获取。
  • Agent shooterAgent:Mission.Current.Agents中包含了当前mission中所有的agent,所以foreach (Agent agent in Mission.Current.Agents)可以遍历所有的agent,这个操作类似于1代的try for agents。
  • MissionWeapon missileWeapon:

(点击展开 / 收起)


  • Vec3 position:比较难获得准确的和原游戏一样的射击位置,用头部坐标代替。或者自定义一个起始点pos,比如整一个王之宝库
(点击展开 / 收起)

(点击展开 / 收起)

  • Vec3 direction:比较难获得准确的和原游戏一样的射击角度,简单的使用agent的目视方向agent.LookDirection
  • Mat3 orientation:比较难获得准确的和原游戏一样的射击角度,简单的使用agent的目视方向agent.LookRotation
  • float baseSpeed:直接获取武器的投射物速度(float)mainHandEquipmentElement.GetModifiedMissileSpeedForCurrentUsage();
  • float speed:同上
  • bool addRigidBody:刚体,写个true或者false都行
  • MissionObject missionObjectToIgnore:写个null,新建的投射物砸到自己算倒霉
  • int forcedMissileIndex = -1:默认填-1,不建议动

到此为止,教程部分就算完了,知道参数怎么获取后,基本可以做到比较随心所欲的写效果了。

遗留的问题:现在这个AddCustomMissile方法,对于投掷武器的伤害还算凑合,但对于弓弩,他的伤害是只有弹药的伤害,而没有加上弓弩的伤害。虽然可以通过把basespeed的数值降低来让他的伤害提高,但是太不可控。所以还需要在别的地方处理。

一些意料之外的坑点
手上的武器,在进行装填时(比如弓已经射出了一根箭还没有搭上下一根箭时)是无法获取到他的ammo的,因此需要额外重新写一个代码。(OnAgentShootMissile回调函数的调用是在消耗ammo之前,所以直接获取ammo不会有问题)
(点击展开 / 收起)



更新:
两个speed参数现在搞明白是怎么回事了
第一个basespeed是武器面板的投射物速度数值,这个数值可以直接用很多方法简单的获取到,比如(float)agent.Equipment[agent.GetWieldedItemIndex(Agent.HandIndex.MainHand)].GetModifiedMissileSpeedForCurrentUsage();
第二个speed参数就比较蛋疼了,这个是投射物的实际速度,但是想获取的话没什么好办法,可能简单的处理方法就是在OnAgentShootMissile里写一个获取的代码。
这两个参数会在计算伤害时影响伤害,这也就导致了自定义模式下和战役模式下伤害的差异。自定义模式下两个speed基本一致,但是战役模式下,实际投射物速度会比面板投射物速度低很多。



相关源码见我发出来的mod,里面放了整个工程





30

主题

226

回帖

192

积分

见习骑士

Rank: 3

UID
2758789
第纳尔
2148
精华
0
互助
21
荣誉
1
贡献
0
魅力
201
注册时间
2016-7-18
鲜花(22) 鸡蛋(0)
 楼主| 发表于 2023-12-21 17:30:10 | 显示全部楼层
本帖最后由 路过的罗格 于 2024-1-31 14:51 编辑

此楼只作为可选方案的记录,更好的处理方法在三楼。
(点击展开 / 收起)

30

主题

226

回帖

192

积分

见习骑士

Rank: 3

UID
2758789
第纳尔
2148
精华
0
互助
21
荣誉
1
贡献
0
魅力
201
注册时间
2016-7-18
鲜花(22) 鸡蛋(0)
 楼主| 发表于 2024-1-22 15:46:02 | 显示全部楼层
本帖最后由 路过的罗格 于 2024-1-24 15:37 编辑

好的,我们继续来处理这个倒霉的多重箭。现在有了一个更好的方法,这个方法既能修复伤害计算时数值的错误,又能在输出战斗日志的继续使用游戏原有的流程。
首先来看一下我整理出来的战斗数值的计算流程,这个是游戏源码里各个函数中比较重要的功能函数,按照从上到下的顺序进行执行。
https://docs.qq.com/doc/DVGdkVVZTUkV3ZHhy?u=b3470a1d8f984475a1b6dad9e35fbea6
可以看到,执行到CalculateStrikeMagnitudeForMissile函数时,就调用了错误的mtd值用于计算伤害,同时,这个函数是一个可以进行复写的函数。这就意味着我们可以新建一个函数复写这个错误的函数并完成修复,然后把这个新建的数值正确的函数交给引擎调用,这样就完成了对这个错误点的修复。
首先进行这个函数的复写。在游戏的源代码中,有两个地方对CalculateStrikeMagnitudeForMissile函数进行了实现,
一个是在DefaultStrikeMagnitudeModel类里,作为默认代码进行触发,如果没有别的地方对CalculateStrikeMagnitudeForMissile方法进行复写,就会调用DefaultStrikeMagnitudeModel类中的这个方法。
另一个是在SandboxStrikeMagnitudeModel类中,如果你进入战役模式(game.GameType is Campaign),那么就将调用这个类中的CalculateStrikeMagnitudeForMissile函数,而不去调用默认的。
(当前其实还有第三四个位置会调用,但是那些是多人游戏的,我们不管)
为了偷懒,我们可以只复写沙盒类里的这个函数,虽然有风险,但是体感上来说没啥问题。复写时,整个将SandboxStrikeMagnitudeModel类中的CalculateStrikeMagnitudeForMissile函数的代码复制出来,然后对mtd值进行修改。当前这只是实现方法之一,其他同效果的方法不过多赘述。
这种修复的关键是,在代码中if一下本身的mtd值是不是等于我们人物手上弓弩和箭矢的伤害之和,如果现在手上武器的伤害之和,不等于引擎里传入的mtd值,则把mtd值修改为当前手上武器的伤害之和。(当然了,这里依旧有一些bug,比如生成箭矢后切换当前手持的武器,也会导致mtd值与手上武器伤害对应不上,可以写更多的if去解决这个问题。)代码中使用的GetAgentCurrentMissionWeaponDamage函数见2楼的解决方案。
  1.     public class WOW_StrikeMagnitudeModel : SandboxStrikeMagnitudeModel
  2.     {
  3.         public override float CalculateStrikeMagnitudeForMissile(in AttackInformation attackInformation, in AttackCollisionData collisionData, in MissionWeapon weapon, float missileSpeed)
  4.         {
  5.             WoW_Scripts woW_Scripts = new WoW_Scripts();
  6.             BasicCharacterObject attackerAgentCharacter = attackInformation.AttackerAgentCharacter;
  7.             MissionWeapon missionWeapon = weapon;
  8.             WeaponComponentData currentUsageItem = missionWeapon.CurrentUsageItem;
  9.             AttackCollisionData attackCollisionData = collisionData;
  10.             float missileTotalDamage = attackCollisionData.MissileTotalDamage;
  11.             if (missileTotalDamage != woW_Scripts.GetAgentCurrentMissionWeaponDamage(attackInformation.AttackerAgent, out missionWeapon))
  12.             {
  13.                 missileTotalDamage = woW_Scripts.GetAgentCurrentMissionWeaponDamage(attackInformation.AttackerAgent, out missionWeapon);
  14.             }
  15.             attackCollisionData = collisionData;
  16.             float missileStartingBaseSpeed = attackCollisionData.MissileStartingBaseSpeed;
  17.             float num = missileSpeed;
  18.             float num2 = missileSpeed - missileStartingBaseSpeed;
  19.             if (num2 > 0f)
  20.             {
  21.                 ExplainedNumber explainedNumber = new ExplainedNumber(0f, false, null);
  22.                 CharacterObject characterObject = attackerAgentCharacter as CharacterObject;
  23.                 if (characterObject != null && characterObject.IsHero)
  24.                 {
  25.                     WeaponClass ammoClass = currentUsageItem.AmmoClass;
  26.                     if (ammoClass == WeaponClass.Stone || ammoClass == WeaponClass.ThrowingAxe || ammoClass == WeaponClass.ThrowingKnife || ammoClass == WeaponClass.Javelin)
  27.                     {
  28.                         PerkHelper.AddPerkBonusForCharacter(DefaultPerks.Throwing.RunningThrow, characterObject, true, ref explainedNumber);
  29.                     }
  30.                 }
  31.                 num += num2 * explainedNumber.ResultNumber;
  32.             }
  33.             num /= missileStartingBaseSpeed;
  34.             return num * num * missileTotalDamage;
  35.            
  36.         }
  37.     }
复制代码
完成了修复后,接下来就要让游戏引擎去调用我们修复后的代码,而不是继续用错误的。这里直接参考游戏源码。在public class SubModule : MBSubModuleBase中,完成以下代码。
  1.         protected override void InitializeGameStarter(Game game, IGameStarter gameStarterObject)
  2.         {
  3.             //可以按需添加if条件,比如只在战役模式中修复,就if (game.GameType is Campaign)才执行
  4.             //推荐添加if,因为我们的代码是拿沙盒战役代码改的,放在非沙盒战役中会出什么问题那是未知的。不过添加if后,会导致在快速战斗中不触发这段代码,而是使用默认的   
  5.             gameStarterObject.AddModel(new WOW_StrikeMagnitudeModel());               
  6.             
  7.         }
复制代码
至此,修复完毕,这个修复思路比二楼的要好很多,不过需要点编程知识才能知道能这么改,本来我还想说这要会个基础的顺序结构选择结构循环结构什么的就能写mod,但现在真不好这么说了。

0

主题

1

回帖

0

积分

平民

Rank: 1

UID
3131640
第纳尔
90
精华
0
互助
0
荣誉
0
贡献
0
魅力
0
注册时间
2020-1-29
鲜花(0) 鸡蛋(0)
发表于 2024-3-18 16:44:24 | 显示全部楼层
灌下水。
知道大佬说的是啥,但是看不懂,嘿嘿。
我小白一个,说点带多重箭功能mod武器的感受吧。如果地球很危险,我就回火星。。。
最早接触的多重箭是军火商里的子母飞刀,但是母飞刀可以吃到投掷天赋的弹速加成但是子飞刀吃不到,子飞刀随母飞刀发射时平行于屏幕上下边缘垂直于屏幕左右边缘的平面,以母飞刀中心点左右均匀分布的线形散步,很稳定(加嗜血mod还没来得及评测,之前三四十支母飞刀直接给我干跳出了。)。
然后是嗜血mod,每支投射物都能吃到天赋加成,也是散步形状也是线形,但是散步线的角度很奇怪,尽管都是线状散步但是与屏幕上下边缘的各种角度都有,特别是投射物多的时候,有时候能射出垂直于屏幕上下边缘平面的散布线,这时候就要在战场里找位置了,有个射击方向一定能射出‘’平行散步‘’(知道我的意思就行了,我几何就没学好,不知道该怎么说),我相信大家遇到过这种情况,就不细说了。
我希望嗜血mod的作者大佬能出一个设定散步角度的功能,能做圆型散布那就最好了,三四十支投射物都快180度散布了,准星处还是一只箭,没有重点打击效果,另外散步角度太广误伤队友也非常严重.

30

主题

226

回帖

192

积分

见习骑士

Rank: 3

UID
2758789
第纳尔
2148
精华
0
互助
21
荣誉
1
贡献
0
魅力
201
注册时间
2016-7-18
鲜花(22) 鸡蛋(0)
 楼主| 发表于 2024-3-18 18:26:30 | 显示全部楼层
lapi00 发表于 2024-3-18 16:44
灌下水。
知道大佬说的是啥,但是看不懂,嘿嘿。
我小白一个,说点带多重箭功能mod武器的感受吧。如果地球 ...

要让子飞刀也吃到perk加成,需要加几句代码.因为现在这个代码是直接获取了武器面板的投射物速度数值后就没管了,所以还需要在去获取一下1.人物对应技能等级;2.技能加成数值,把这个数值给他也加到获取的面版数值上就行了.当然也别忘了判定一下人物有没有那个技能.
然后多重箭散步比较怪的问题,可能和我一样偷懒直接用vec3当方向向量来用导致的.vec3里面的旋转角度,感觉上是以世界为基准进行的旋转,不是以当前人物的本地坐标做旋转.解决这个问题不难,我后续的代码里也都改了下.简单来说就是不去用agent.LookDirection,而是用agent.LookRotation来获取一个mat3格式的视野矩阵,拿这个矩阵做RotateAboutUp上下偏移和RotateAboutSide左右偏移,就是以本地坐标系进行旋转了.处理完旋转后,用mat3.f来获取一个vec3格式的方向向量,扔到生成投射物的函数里
您需要登录后才可以回帖 登录 | 注册(Register!)

本版积分规则

Archiver|手机版|小黑屋|骑马与砍杀中文站

GMT+8, 2024-11-19 08:31 , Processed in 0.145703 second(s), 22 queries , Gzip On, MemCached On.

Powered by Discuz! X3.4 Licensed

© 2001-2023 Discuz! Team.

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