骑马与砍杀中文站论坛

 找回密码
 注册(Register!)

QQ登录

只需一步,快速开始

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

[经验与教程] 【骑砍2】给战场ui添加更多的数据

[复制链接]

28

主题

218

回帖

179

积分

见习骑士

Rank: 3

UID
2758789
第纳尔
2118
精华
0
互助
19
荣誉
1
贡献
0
魅力
171
注册时间
2016-7-18
鲜花(18) 鸡蛋(0)
发表于 2024-2-27 15:27:37 | 显示全部楼层 |阅读模式
本帖最后由 路过的罗格 于 2024-4-20 18:16 编辑

可能会好看一点的排版:【腾讯文档】【骑砍2mod制作】在战场上添加ui以显示数据
https://docs.qq.com/doc/DVE1vTGxGbVFtV3JT

教程适用游戏版本:1.2.0和1.2.8,其他版本我没做测试      在升级到1.2.9后需要做一些修改才能继续生效

好的各位,又整明白了一个功能,继续过来写教程。
在写mod的过程中,难免会遇到需要给hud上添加新的数据的情况,比如你新增了一个法力值耐力值的设定,就有必要显示出来你控制的角色当前的这些属性的数值。所以呢,来添加ui吧。
参考视频:https://www.youtube.com/watch?v=67KRDuPG1mM
我也是看这个视频学的,不过我这边侧重地和他的不太一样,并且他那边因为是一个系列教程,所以已经有一部分代码了,直接看的时候可能会有些懵。
开始进入正题。在战场上添加ui,需要三个地方写代码。首先引入dll,尽可能多的把游戏根目录和mod文件夹里具体mod的这个路径bin\Win64_Shipping_Client中的dll引入工程。
一是需要在c#里,完成一个直接或间接继承了MissionView的类,我这边选的是继承MissionGauntletBattleUIBase类,因为MissionGauntletBattleUIBase继承了MissionView。参考视频里是直接继承MissionView,区别不大。二是需要在c#里,完成一个继承了ViewModel的类。三是需要完成一个xml文件,用于配置ui的显示,这个xml配置足够专门出一个教程,所以这次的xml只作为简单的演示。
首先,我们来完成一个最简单的ui,单纯在界面上添加一串文字。完成以下代码:
  1. internal class WoW_MainAgentStatus : MissionGauntletBattleUIBase//需求1:继承了MissionView的类
  2. {
  3.         //至少需要两个参数,一个是数据源,也就是需求2创建的类,另一个是显示层GauntletLayer。
  4.         private WoW_MainAgentStatusVM _dataSource;
  5.         private GauntletLayer _gauntletLayer;

  6.         protected override void OnCreateView()//视图创建时自动调用的函数
  7.         {//由于父类定义的OnCreateView是一个虚构类,所以不用base.OnCreateView
  8.             this._dataSource = new WoW_MainAgentStatusVM();//调用数据源类的构造函数,暂时为空构造
  9.             this._gauntletLayer = new GauntletLayer(this.ViewOrderPriority, "GauntletLayer", false);//new GauntletLayer的时候,可以只填入一个数字参数,我这边直接复制别处的,后两个参数实际都是默认值。数字参数会影响到多个ui在一起时的遮挡情况,没仔细测试是大数遮盖小数还是反过来。
  10.             this._gauntletLayer.LoadMovie("WoW_AgentStatus", this._dataSource);//关键的一个点,LoadMovie的字符串参数,是xml文件的文件名,这里如果没有对应的话会出错。
  11.             base.MissionScreen.AddLayer(this._gauntletLayer);//把xml配置的ui推送到显示画面上
  12.         }

  13.         protected override void OnDestroyView()//结束时调用的函数,做一下数据的清空以及显示层的释放
  14.         {
  15.             MissionScreen.RemoveLayer(this._gauntletLayer);
  16.             _dataSource = null;
  17.             _gauntletLayer = null;
  18.         }
  19. }
复制代码
  1.     public class WoW_MainAgentStatusVM : ViewModel//需求2,继承了ViewModel的类,作为数据源。暂时因为只需要显示为固定文字,所以不用在这里写东西,完成一个空构造函数就行。
  2. {
  3.          public WoW_MainAgentStatusVM(){}
  4. }
复制代码
  1. 文件名:WoW_AgentStatus.xml。需求3,文件名注意和上面的代码对应。这边是一个类似html标签/xml标签的写法,但是是个烤肉社自创的。整体来说分为三个东西,标签、标签的属性、标签属性的值。以TextWidget那一串为例,TextWidget是标签,WidthSizePolicy、HeightSizePolicy等是属性,CoverChildren、50、114514是值。
  2. 标签都是在c#代码里,继承了Widget的类,属性是标签对应类里设定的属性,这两个基本只管用,不用折腾代码。值是我们写入的东西,在进行一些特殊操作后,可以和我们写的代码完成交互,根据代码改变显示的值,但现在先写一个固定的数值。
  3. 更多的可以看一看Mount & Blade II Bannerlord\Modules\Native\GUI\Prefabs\Mission\AgentStatus.xml之类的文件。

  4. <Prefab>
  5.   <Constants>

  6.   </Constants>
  7.   <Window>
  8.     <TextWidget WidthSizePolicy="CoverChildren" HeightSizePolicy="Fixed" SuggestedHeight="40" MarginLeft="50" MarginRight="50" MaxWidth="380" VerticalAlignment="Center" IntText="114514" PositionYOffset="7" Brush="MPHUD.TroopCount.Text"/>


  9.   </Window>
  10. </Prefab>
复制代码

完成了上面的三组代码后,别忘记在可以AddMissionBehavior的位置,添加一下我们刚才创建的类。因为需求1的类继承了MissionView,而MissionView继承了MissionBehavior,所以这边可以直接通过AddMissionBehavior添加。
  1. //public class SubModule : MBSubModuleBase
  2.         public override void OnMissionBehaviorInitialize(Mission mission)
  3.         {
  4.             base.OnMissionBehaviorInitialize(mission);
  5.             mission.AddMissionBehavior(new WoW_MainAgentStatus());
  6.         }
复制代码

现在,在启动器里加上这个mod,进入战场后不出意外的话就能在左边显示一个114514了。


然后来完成数据从后台到前端的通信,这边按代码执行顺序来添加东西。先是在需求1那个继承了MissionView的类里,添加函数OnMissionScreenTick
  1.         public override void OnMissionScreenTick(float dt)
  2.         {
  3.             base.OnMissionScreenTick(dt);//父类的函数里有代码,这边base.一下
  4.             if (_dataSource!=null)//然后在数据源不为空的前提下,调用一下数据源的更新数据的方法,这个方法下一步写。      
  5.                 _dataSource?.UpdateAgentStatuses();//另一个保证参数不为空的写法,不去写if了,直接_dataSource?。因为OnMissionScreenTick的执行时间会早于OnCreateView,而OnCreateView里我们才给数据源赋值,所以这边的不为空必须加(我没试,或许可以在这边else一下,如果为空就在这边添加数据源?)
  6.         }
复制代码

然后在数据源也就是需求2的类里完成更新的函数
  1.         internal void UpdateAgentStatuses()
  2.         {//这边可能出现较多的空指针异常,多整几次把各个数据保证不为空
  3.             if (this.mission != null && this.mission.MainAgent == null)
  4.             {

  5.                 this._Stamina = 0;
  6.                 this._Mana = 0;
  7.                 return;
  8.             }
  9.             //下面的代码需要替换为你自己的获取数据的办法。如果没有的话,可以暂时拿游戏内的时间做测试 this._Stamina =(int)Mission.Current.CurrentTime;
  10.             if (this.mission != null && WoW_MissionSetting.WoW_Agents.TryGetValue(mission.MainAgent.Index, out var MainAgent))
  11.             {

  12.                 this.Stamina = (int)MainAgent.Stamina;
  13.                 this.Mana= (int)MainAgent.Mana;
  14.             }


  15.         }
复制代码

  1. //这里因为添加了很多属性,所以需要在需求2的类里添加一些东西。
  2. //构造函数里添加一下Mission的获取。
  3.         public WoW_MainAgentStatusVM(Mission mission)
  4.         {
  5.             this.mission = mission;
  6.         }
  7.         //对应的,使用这个构造函数时,也别忘了放参数this._dataSource = new WoW_MainAgentStatusVM(base.Mission)
  8.        //在类里添加一下_Stamina、_Mana、mission 这三个属性
  9.         private Mission mission;
  10.         private int _Stamina;
  11.         private int _Mana;
复制代码

然后是比较特殊的属性StaminaMana的添加,这两个属性将用于和xml做交互,关键点有两个,一个是属性上面加[DataSourceProperty],另一个是base.OnPropertyChangedWithValue(value, "Stamina");。完成base.OnPropertyChangedWithValue(value, "Stamina");后,当Stamina被进行set时,就会通知引擎,把那个xml里@Stamina显示的文字,修改成Stamina自己的值。
  1.         [DataSourceProperty]
  2.         public int Stamina
  3.         {
  4.             get
  5.             {
  6.                 return this._Stamina;
  7.             }
  8.             set
  9.             {
  10.                 if (value != this._Stamina)
  11.                 {
  12.                     this._Stamina = value;
  13.                     base.OnPropertyChangedWithValue(value, "Stamina");
  14.                 }
  15.             }
  16.         }
  17.         [DataSourceProperty]
  18.         public int Mana
  19.         {
  20.             get
  21.             {
  22.                 return this._Mana;
  23.             }
  24.             set
  25.             {
  26.                 if (value != this._Mana)
  27.                 {
  28.                     this._Mana = value;
  29.                     base.OnPropertyChangedWithValue(value, "Mana");
  30.                 }
  31.             }
  32.         }
复制代码

对应的,xml那边,114514需要改为 @Stamina,进入测试,应该就已经可以有一个随着时间变动的数据了
  1. <TextWidget WidthSizePolicy="CoverChildren" HeightSizePolicy="Fixed" SuggestedHeight="40" MarginLeft="50" MarginRight="50" MaxWidth="380" VerticalAlignment="Center" IntText="@Stamina" PositionYOffset="7" Brush="MPHUD.TroopCount.Text"/>
复制代码

然后是添加第二个属性Mana的xml配置,这边按说不难但是实际上给我折腾了半天,他这个xml配置多少有点抽象,暂时用这套东西能实现这个功能,也就是在外面多加了一个框,限制一下显示的位置
  1. <Prefab>
  2.   <Window>

  3.     <Widget Id="Canvas" WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="1500" HorizontalAlignment="Left" VerticalAlignment="Center" >
  4.       <Children>
  5.           <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="500" MarginRight="500" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" IntText="@Stamina" PositionYOffset="0"/>
  6.           <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="0" MarginRight="0" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" Text="Stamina:" PositionXOffset="5" PositionYOffset="0"/>

  7.           <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="500" MarginRight="500" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" IntText="@Mana" PositionYOffset="50"/>
  8.           <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="0" MarginRight="0" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" Text="Mana:" PositionXOffset="5" PositionYOffset="50"/>
  9.          
  10.           <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="500" MarginRight="500" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" IntText="@Mana" PositionXOffset="10" PositionYOffset="100"/>
  11.           <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="0" MarginRight="0" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" Text="time:" PositionXOffset="5" PositionYOffset="100"/>

  12.       </Children>
  13.     </Widget>


  14.   </Window>
  15.   
  16. </Prefab>
复制代码


图片1.png

(点击展开 / 收起)


对129版的更新:
对继承了MissionGauntletBattleUIBase或MissionView的类,需要加一个特性。比如游戏源码那边的这段


  1. [OverrideView(typeof(MissionAgentStatusUIHandler))]
  2.         public class MissionGauntletAgentStatus : MissionGauntletBattleUIBase
复制代码



给自己这边也加上类似的特性,然后完成一下特性的代码,直接复制MissionAgentStatusUIHandler的就行
  1. namespace WoW
  2. {
  3.     [OverrideView(typeof(WoW_MissionAgentStatusUIHandler))]
  4.     internal class WoW_MainAgentStatus : MissionGauntletBattleUIBase
  5.     {
  6.         private WoW_MainAgentStatusVM _dataSource;
  7.         private GauntletLayer _gauntletLayer;

  8.         protected override void OnCreateView()
  9.         {
  10.             this._dataSource = new WoW_MainAgentStatusVM(base.Mission, base.MissionScreen.CombatCamera, new Func<float>(base.MissionScreen.GetCameraToggleProgress));
  11.             this._gauntletLayer = new GauntletLayer(this.ViewOrderPriority, "GauntletLayer", false);
  12.             this._gauntletLayer.LoadMovie("WoW_AgentStatus", this._dataSource);
  13.             base.MissionScreen.AddLayer(this._gauntletLayer);
  14.         }

  15.         protected override void OnDestroyView()
  16.         {
  17.             MissionScreen.RemoveLayer(this._gauntletLayer);
  18.             _dataSource = null;
  19.             _gauntletLayer = null;
  20.         }
  21.         public override void OnMissionScreenTick(float dt)
  22.         {

  23.             base.OnMissionScreenTick(dt);
  24.             if (_dataSource != null)
  25.                 _dataSource?.UpdateAgentStatuses();
  26.         }
  27.     }
  28.    
  29.     public class WoW_MissionAgentStatusUIHandler : MissionAgentStatusUIHandlerBase
  30.     {
  31.         // Token: 0x06000329 RID: 809 RVA: 0x0001C138 File Offset: 0x0001A338
  32.         public override void HideUI()
  33.         {
  34.         }

  35.         // Token: 0x0600032A RID: 810 RVA: 0x0001C13A File Offset: 0x0001A33A
  36.         public override void ShowUI()
  37.         {
  38.         }
  39.     }
  40. }

复制代码


完成了以后,需要在开启战场的时候,加载一下。比如在submodel里
  1.         public override void OnMissionBehaviorInitialize(Mission mission)
  2.         {
  3.             base.OnMissionBehaviorInitialize(mission);
  4.             mission.AddMissionBehavior(new WoW_MissionSetting());
  5.             mission.AddMissionBehavior(new WoW_MainAgentStatus());
复制代码




评分

参与人数 1第纳尔 +20 互助 +2 魅力 +20 收起 理由
Aomine Daiki + 20 + 2 + 20 您的帖子很有价值!

查看全部评分

1

主题

20

回帖

6

积分

平民

Rank: 1

UID
3487461
第纳尔
8
精华
0
互助
0
荣誉
0
贡献
0
魅力
0
注册时间
2023-2-20
鲜花(0) 鸡蛋(0)
发表于 2024-2-28 22:18:28 | 显示全部楼层
棒!!!感谢大佬分享

9

主题

20

回帖

9

积分

平民

Rank: 1

UID
2893209
第纳尔
120
精华
0
互助
0
荣誉
0
贡献
0
魅力
0
注册时间
2017-11-5
鲜花(0) 鸡蛋(0)
发表于 2024-3-25 12:29:53 | 显示全部楼层
为什么我没有MissionView的类,是缺了哪个dll的引用吗

28

主题

218

回帖

179

积分

见习骑士

Rank: 3

UID
2758789
第纳尔
2118
精华
0
互助
19
荣誉
1
贡献
0
魅力
171
注册时间
2016-7-18
鲜花(18) 鸡蛋(0)
 楼主| 发表于 2024-3-25 23:24:55 | 显示全部楼层
本帖最后由 路过的罗格 于 2024-3-25 23:26 编辑
书封影 发表于 2024-3-25 12:29
为什么我没有MissionView的类,是缺了哪个dll的引用吗

引用是这些应该是中间那几个gui相关的

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using TaleWorlds.Core;
  7. using TaleWorlds.GauntletUI;
  8. using TaleWorlds.Library;
  9. using TaleWorlds.MountAndBlade;
  10. using TaleWorlds.MountAndBlade.GauntletUI;
  11. using TaleWorlds.MountAndBlade.GauntletUI.Mission;
  12. using TaleWorlds.MountAndBlade.GauntletUI.Widgets.Mission;
  13. using TaleWorlds.MountAndBlade.View.MissionViews;
  14. using TaleWorlds.MountAndBlade.View;
  15. using TaleWorlds.ScreenSystem;
  16. using TaleWorlds.MountAndBlade.ViewModelCollection;
  17. using TaleWorlds.Engine.GauntletUI;
  18. using TaleWorlds.Engine;
复制代码
然后dll,一部分是Mount & Blade II Bannerlord\bin\Win64_Shipping_Client路径下面的基础dll,还有mod文件夹里的比如Mount & Blade II Bannerlord\Modules\Native\bin\Win64_Shipping_Client中的dll,同理还有沙盒相关的SandBox、StoryMode等文件夹下面的dll
您需要登录后才可以回帖 登录 | 注册(Register!)

本版积分规则

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

GMT+8, 2024-4-28 14:07 , Processed in 0.131480 second(s), 27 queries , Gzip On, MemCached On.

Powered by Discuz! X3.4 Licensed

© 2001-2023 Discuz! Team.

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