骑马与砍杀中文站论坛

标题: 【骑砍2】给战场ui添加更多的数据 [打印本页]

作者: 路过的罗格    时间: 2024-2-27 15:27
标题: 【骑砍2】给战场ui添加更多的数据
本帖最后由 路过的罗格 于 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

[spoiler=完整代码]
  1. //完整代码,xml那边多加了一些条目,可以一次性显示多个数据
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using TaleWorlds.Core;
  8. using TaleWorlds.GauntletUI;
  9. using TaleWorlds.Library;
  10. using TaleWorlds.MountAndBlade;
  11. using TaleWorlds.MountAndBlade.GauntletUI;
  12. using TaleWorlds.MountAndBlade.GauntletUI.Mission;
  13. using TaleWorlds.MountAndBlade.GauntletUI.Widgets.Mission;
  14. using TaleWorlds.MountAndBlade.View.MissionViews;
  15. using TaleWorlds.MountAndBlade.View;
  16. using TaleWorlds.ScreenSystem;
  17. using TaleWorlds.MountAndBlade.ViewModelCollection;
  18. using TaleWorlds.Engine.GauntletUI;
  19. using TaleWorlds.Engine;

  20. namespace WoW
  21. {
  22.     internal class WoW_MainAgentStatus : MissionGauntletBattleUIBase
  23.     {
  24.         private WoW_MainAgentStatusVM _dataSource;
  25.         private GauntletLayer _gauntletLayer;

  26.         protected override void OnCreateView()
  27.         {
  28.             this._dataSource = new WoW_MainAgentStatusVM(base.Mission, base.MissionScreen.CombatCamera, new Func<float>(base.MissionScreen.GetCameraToggleProgress));
  29.             this._gauntletLayer = new GauntletLayer(this.ViewOrderPriority, "GauntletLayer", false);
  30.             this._gauntletLayer.LoadMovie("WoW_AgentStatus", this._dataSource);
  31.             base.MissionScreen.AddLayer(this._gauntletLayer);
  32.         }

  33.         protected override void OnDestroyView()
  34.         {
  35.             MissionScreen.RemoveLayer(this._gauntletLayer);
  36.             _dataSource = null;
  37.             _gauntletLayer = null;
  38.         }
  39.         public override void OnMissionScreenTick(float dt)
  40.         {

  41.             base.OnMissionScreenTick(dt);
  42.             if (_dataSource!=null)      
  43.                 _dataSource?.UpdateAgentStatuses();
  44.         }
  45.     }
  46.     public class WoW_MainAgentStatusVM : ViewModel
  47.     {
  48.         private Mission mission;
  49.         private Camera combatCamera;
  50.         private Func<float> func;
  51.         private int _Stamina;
  52.         private int _Mana;
  53.         private int _Time;

  54.         public bool IsInDeployement { get; set; }
  55.         public WoW_MainAgentStatusVM(Mission mission, Camera combatCamera, Func<float> func)
  56.         {
  57.             this.mission = mission;
  58.             this.combatCamera = combatCamera;
  59.             this.func = func;

  60.             
  61.         }
  62.         [DataSourceProperty]
  63.         public int Stamina
  64.         {
  65.             get
  66.             {
  67.                 return this._Stamina;
  68.             }
  69.             set
  70.             {
  71.                 if (value != this._Stamina)
  72.                 {
  73.                     this._Stamina = value;
  74.                     base.OnPropertyChangedWithValue(value, "Stamina");
  75.                 }
  76.             }
  77.         }
  78.         [DataSourceProperty]
  79.         public int Mana
  80.         {
  81.             get
  82.             {
  83.                 return this._Mana;
  84.             }
  85.             set
  86.             {
  87.                 if (value != this._Mana)
  88.                 {
  89.                     this._Mana = value;
  90.                     base.OnPropertyChangedWithValue(value, "Mana");
  91.                 }
  92.             }
  93.         }
  94.         [DataSourceProperty]
  95.         public int Time
  96.         {
  97.             get
  98.             {
  99.                 return this._Time;
  100.             }
  101.             set
  102.             {
  103.                 if (value != this._Time)
  104.                 {
  105.                     this._Time = value;
  106.                     base.OnPropertyChangedWithValue(value, "Time");
  107.                 }
  108.             }
  109.         }

  110.         internal void UpdateAgentStatuses()
  111.         {
  112.             if (this.mission != null && this.mission.MainAgent == null)
  113.             {

  114.                 this._Stamina = 0;
  115.                 this._Mana = 0;
  116.                 return;
  117.             }
  118.             if (this.mission != null)
  119.             {
  120.                 this._Stamina = (int)Mission.Current.CurrentTime;
  121.             }
  122.             if (this.mission != null && WoW_MissionSetting.WoW_Agents.TryGetValue(mission.MainAgent.Index, out var MainAgent))
  123.             {

  124.                 this.Stamina = (int)MainAgent.Stamina;
  125.                 this.Mana = (int)MainAgent.Mana;
  126.                 this.Time = (int)Mission.Current.CurrentTime;
  127.             }


  128.         }
  129.     }
  130. }
复制代码
  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>
复制代码


[/spoiler]

对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());
复制代码





作者: 蘇泊渝    时间: 2024-2-28 22:18
棒!!!感谢大佬分享
作者: 书封影    时间: 2024-3-25 12:29
为什么我没有MissionView的类,是缺了哪个dll的引用吗
作者: 路过的罗格    时间: 2024-3-25 23:24
本帖最后由 路过的罗格 于 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




欢迎光临 骑马与砍杀中文站论坛 (https://bbs.mountblade.com.cn/) Powered by Discuz! X3.4