- 好友
- 0
- 在线时间
- 128 小时
- 最后登录
- 2025-3-26
见习骑士

- UID
- 2758789
- 第纳尔
- 2133
- 精华
- 0
- 互助
- 21
- 荣誉
- 1
- 贡献
- 0
- 魅力
- 201
- 注册时间
- 2016-7-18
 鲜花( 23)  鸡蛋( 0)
|
本帖最后由 路过的罗格 于 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,单纯在界面上添加一串文字。完成以下代码:
- internal class WoW_MainAgentStatus : MissionGauntletBattleUIBase//需求1:继承了MissionView的类
- {
- //至少需要两个参数,一个是数据源,也就是需求2创建的类,另一个是显示层GauntletLayer。
- private WoW_MainAgentStatusVM _dataSource;
- private GauntletLayer _gauntletLayer;
- protected override void OnCreateView()//视图创建时自动调用的函数
- {//由于父类定义的OnCreateView是一个虚构类,所以不用base.OnCreateView
- this._dataSource = new WoW_MainAgentStatusVM();//调用数据源类的构造函数,暂时为空构造
- this._gauntletLayer = new GauntletLayer(this.ViewOrderPriority, "GauntletLayer", false);//new GauntletLayer的时候,可以只填入一个数字参数,我这边直接复制别处的,后两个参数实际都是默认值。数字参数会影响到多个ui在一起时的遮挡情况,没仔细测试是大数遮盖小数还是反过来。
- this._gauntletLayer.LoadMovie("WoW_AgentStatus", this._dataSource);//关键的一个点,LoadMovie的字符串参数,是xml文件的文件名,这里如果没有对应的话会出错。
- base.MissionScreen.AddLayer(this._gauntletLayer);//把xml配置的ui推送到显示画面上
- }
- protected override void OnDestroyView()//结束时调用的函数,做一下数据的清空以及显示层的释放
- {
- MissionScreen.RemoveLayer(this._gauntletLayer);
- _dataSource = null;
- _gauntletLayer = null;
- }
- }
复制代码- public class WoW_MainAgentStatusVM : ViewModel//需求2,继承了ViewModel的类,作为数据源。暂时因为只需要显示为固定文字,所以不用在这里写东西,完成一个空构造函数就行。
- {
- public WoW_MainAgentStatusVM(){}
- }
复制代码- 文件名:WoW_AgentStatus.xml。需求3,文件名注意和上面的代码对应。这边是一个类似html标签/xml标签的写法,但是是个烤肉社自创的。整体来说分为三个东西,标签、标签的属性、标签属性的值。以TextWidget那一串为例,TextWidget是标签,WidthSizePolicy、HeightSizePolicy等是属性,CoverChildren、50、114514是值。
- 标签都是在c#代码里,继承了Widget的类,属性是标签对应类里设定的属性,这两个基本只管用,不用折腾代码。值是我们写入的东西,在进行一些特殊操作后,可以和我们写的代码完成交互,根据代码改变显示的值,但现在先写一个固定的数值。
- 更多的可以看一看Mount & Blade II Bannerlord\Modules\Native\GUI\Prefabs\Mission\AgentStatus.xml之类的文件。
- <Prefab>
- <Constants>
- </Constants>
- <Window>
- <TextWidget WidthSizePolicy="CoverChildren" HeightSizePolicy="Fixed" SuggestedHeight="40" MarginLeft="50" MarginRight="50" MaxWidth="380" VerticalAlignment="Center" IntText="114514" PositionYOffset="7" Brush="MPHUD.TroopCount.Text"/>
- </Window>
- </Prefab>
复制代码
完成了上面的三组代码后,别忘记在可以AddMissionBehavior的位置,添加一下我们刚才创建的类。因为需求1的类继承了MissionView,而MissionView继承了MissionBehavior,所以这边可以直接通过AddMissionBehavior添加。
- //public class SubModule : MBSubModuleBase
- public override void OnMissionBehaviorInitialize(Mission mission)
- {
- base.OnMissionBehaviorInitialize(mission);
- mission.AddMissionBehavior(new WoW_MainAgentStatus());
- }
复制代码
现在,在启动器里加上这个mod,进入战场后不出意外的话就能在左边显示一个114514了。
然后来完成数据从后台到前端的通信,这边按代码执行顺序来添加东西。先是在需求1那个继承了MissionView的类里,添加函数OnMissionScreenTick
- public override void OnMissionScreenTick(float dt)
- {
- base.OnMissionScreenTick(dt);//父类的函数里有代码,这边base.一下
- if (_dataSource!=null)//然后在数据源不为空的前提下,调用一下数据源的更新数据的方法,这个方法下一步写。
- _dataSource?.UpdateAgentStatuses();//另一个保证参数不为空的写法,不去写if了,直接_dataSource?。因为OnMissionScreenTick的执行时间会早于OnCreateView,而OnCreateView里我们才给数据源赋值,所以这边的不为空必须加(我没试,或许可以在这边else一下,如果为空就在这边添加数据源?)
- }
复制代码
然后在数据源也就是需求2的类里完成更新的函数
- internal void UpdateAgentStatuses()
- {//这边可能出现较多的空指针异常,多整几次把各个数据保证不为空
- if (this.mission != null && this.mission.MainAgent == null)
- {
- this._Stamina = 0;
- this._Mana = 0;
- return;
- }
- //下面的代码需要替换为你自己的获取数据的办法。如果没有的话,可以暂时拿游戏内的时间做测试 this._Stamina =(int)Mission.Current.CurrentTime;
- if (this.mission != null && WoW_MissionSetting.WoW_Agents.TryGetValue(mission.MainAgent.Index, out var MainAgent))
- {
- this.Stamina = (int)MainAgent.Stamina;
- this.Mana= (int)MainAgent.Mana;
- }
- }
复制代码
- //这里因为添加了很多属性,所以需要在需求2的类里添加一些东西。
- //构造函数里添加一下Mission的获取。
- public WoW_MainAgentStatusVM(Mission mission)
- {
- this.mission = mission;
- }
- //对应的,使用这个构造函数时,也别忘了放参数this._dataSource = new WoW_MainAgentStatusVM(base.Mission)
- //在类里添加一下_Stamina、_Mana、mission 这三个属性
- private Mission mission;
- private int _Stamina;
- private int _Mana;
复制代码
然后是比较特殊的属性Stamina和Mana的添加,这两个属性将用于和xml做交互,关键点有两个,一个是属性上面加[DataSourceProperty],另一个是base.OnPropertyChangedWithValue(value, "Stamina");。完成base.OnPropertyChangedWithValue(value, "Stamina");后,当Stamina被进行set时,就会通知引擎,把那个xml里@Stamina显示的文字,修改成Stamina自己的值。
- [DataSourceProperty]
- public int Stamina
- {
- get
- {
- return this._Stamina;
- }
- set
- {
- if (value != this._Stamina)
- {
- this._Stamina = value;
- base.OnPropertyChangedWithValue(value, "Stamina");
- }
- }
- }
- [DataSourceProperty]
- public int Mana
- {
- get
- {
- return this._Mana;
- }
- set
- {
- if (value != this._Mana)
- {
- this._Mana = value;
- base.OnPropertyChangedWithValue(value, "Mana");
- }
- }
- }
复制代码
对应的,xml那边,114514需要改为 @Stamina,进入测试,应该就已经可以有一个随着时间变动的数据了
- <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配置多少有点抽象,暂时用这套东西能实现这个功能,也就是在外面多加了一个框,限制一下显示的位置
- <Prefab>
- <Window>
- <Widget Id="Canvas" WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="1500" HorizontalAlignment="Left" VerticalAlignment="Center" >
- <Children>
- <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="500" MarginRight="500" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" IntText="@Stamina" PositionYOffset="0"/>
- <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"/>
- <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="500" MarginRight="500" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" IntText="@Mana" PositionYOffset="50"/>
- <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"/>
-
- <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"/>
- <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"/>
- </Children>
- </Widget>
- </Window>
-
- </Prefab>
复制代码
- //完整代码,xml那边多加了一些条目,可以一次性显示多个数据
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using TaleWorlds.Core;
- using TaleWorlds.GauntletUI;
- using TaleWorlds.Library;
- using TaleWorlds.MountAndBlade;
- using TaleWorlds.MountAndBlade.GauntletUI;
- using TaleWorlds.MountAndBlade.GauntletUI.Mission;
- using TaleWorlds.MountAndBlade.GauntletUI.Widgets.Mission;
- using TaleWorlds.MountAndBlade.View.MissionViews;
- using TaleWorlds.MountAndBlade.View;
- using TaleWorlds.ScreenSystem;
- using TaleWorlds.MountAndBlade.ViewModelCollection;
- using TaleWorlds.Engine.GauntletUI;
- using TaleWorlds.Engine;
- namespace WoW
- {
- internal class WoW_MainAgentStatus : MissionGauntletBattleUIBase
- {
- private WoW_MainAgentStatusVM _dataSource;
- private GauntletLayer _gauntletLayer;
- protected override void OnCreateView()
- {
- this._dataSource = new WoW_MainAgentStatusVM(base.Mission, base.MissionScreen.CombatCamera, new Func<float>(base.MissionScreen.GetCameraToggleProgress));
- this._gauntletLayer = new GauntletLayer(this.ViewOrderPriority, "GauntletLayer", false);
- this._gauntletLayer.LoadMovie("WoW_AgentStatus", this._dataSource);
- base.MissionScreen.AddLayer(this._gauntletLayer);
- }
- protected override void OnDestroyView()
- {
- MissionScreen.RemoveLayer(this._gauntletLayer);
- _dataSource = null;
- _gauntletLayer = null;
- }
- public override void OnMissionScreenTick(float dt)
- {
- base.OnMissionScreenTick(dt);
- if (_dataSource!=null)
- _dataSource?.UpdateAgentStatuses();
- }
- }
- public class WoW_MainAgentStatusVM : ViewModel
- {
- private Mission mission;
- private Camera combatCamera;
- private Func<float> func;
- private int _Stamina;
- private int _Mana;
- private int _Time;
- public bool IsInDeployement { get; set; }
- public WoW_MainAgentStatusVM(Mission mission, Camera combatCamera, Func<float> func)
- {
- this.mission = mission;
- this.combatCamera = combatCamera;
- this.func = func;
-
- }
- [DataSourceProperty]
- public int Stamina
- {
- get
- {
- return this._Stamina;
- }
- set
- {
- if (value != this._Stamina)
- {
- this._Stamina = value;
- base.OnPropertyChangedWithValue(value, "Stamina");
- }
- }
- }
- [DataSourceProperty]
- public int Mana
- {
- get
- {
- return this._Mana;
- }
- set
- {
- if (value != this._Mana)
- {
- this._Mana = value;
- base.OnPropertyChangedWithValue(value, "Mana");
- }
- }
- }
- [DataSourceProperty]
- public int Time
- {
- get
- {
- return this._Time;
- }
- set
- {
- if (value != this._Time)
- {
- this._Time = value;
- base.OnPropertyChangedWithValue(value, "Time");
- }
- }
- }
- internal void UpdateAgentStatuses()
- {
- if (this.mission != null && this.mission.MainAgent == null)
- {
- this._Stamina = 0;
- this._Mana = 0;
- return;
- }
- if (this.mission != null)
- {
- this._Stamina = (int)Mission.Current.CurrentTime;
- }
- if (this.mission != null && WoW_MissionSetting.WoW_Agents.TryGetValue(mission.MainAgent.Index, out var MainAgent))
- {
- this.Stamina = (int)MainAgent.Stamina;
- this.Mana = (int)MainAgent.Mana;
- this.Time = (int)Mission.Current.CurrentTime;
- }
- }
- }
- }
复制代码- <Prefab>
- <Window>
- <Widget Id="Canvas" WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="1500" HorizontalAlignment="Left" VerticalAlignment="Center" >
- <Children>
- <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="500" MarginRight="500" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" IntText="@Stamina" PositionYOffset="0"/>
- <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"/>
- <TextWidget WidthSizePolicy="Fixed" HeightSizePolicy="Fixed" SuggestedWidth="500" SuggestedHeight="400" MarginLeft="500" MarginRight="500" MaxWidth="380" HorizontalAlignment="Left" VerticalAlignment="Top" IntText="@Mana" PositionYOffset="50"/>
- <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"/>
-
- <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"/>
- <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"/>
- </Children>
- </Widget>
- </Window>
-
- </Prefab>
复制代码
|
对129版的更新:
对继承了MissionGauntletBattleUIBase或MissionView的类,需要加一个特性。比如游戏源码那边的这段
- [OverrideView(typeof(MissionAgentStatusUIHandler))]
- public class MissionGauntletAgentStatus : MissionGauntletBattleUIBase
复制代码
给自己这边也加上类似的特性,然后完成一下特性的代码,直接复制MissionAgentStatusUIHandler的就行
- namespace WoW
- {
- [OverrideView(typeof(WoW_MissionAgentStatusUIHandler))]
- internal class WoW_MainAgentStatus : MissionGauntletBattleUIBase
- {
- private WoW_MainAgentStatusVM _dataSource;
- private GauntletLayer _gauntletLayer;
- protected override void OnCreateView()
- {
- this._dataSource = new WoW_MainAgentStatusVM(base.Mission, base.MissionScreen.CombatCamera, new Func<float>(base.MissionScreen.GetCameraToggleProgress));
- this._gauntletLayer = new GauntletLayer(this.ViewOrderPriority, "GauntletLayer", false);
- this._gauntletLayer.LoadMovie("WoW_AgentStatus", this._dataSource);
- base.MissionScreen.AddLayer(this._gauntletLayer);
- }
- protected override void OnDestroyView()
- {
- MissionScreen.RemoveLayer(this._gauntletLayer);
- _dataSource = null;
- _gauntletLayer = null;
- }
- public override void OnMissionScreenTick(float dt)
- {
- base.OnMissionScreenTick(dt);
- if (_dataSource != null)
- _dataSource?.UpdateAgentStatuses();
- }
- }
-
- public class WoW_MissionAgentStatusUIHandler : MissionAgentStatusUIHandlerBase
- {
- // Token: 0x06000329 RID: 809 RVA: 0x0001C138 File Offset: 0x0001A338
- public override void HideUI()
- {
- }
- // Token: 0x0600032A RID: 810 RVA: 0x0001C13A File Offset: 0x0001A33A
- public override void ShowUI()
- {
- }
- }
- }
复制代码
完成了以后,需要在开启战场的时候,加载一下。比如在submodel里
- public override void OnMissionBehaviorInitialize(Mission mission)
- {
- base.OnMissionBehaviorInitialize(mission);
- mission.AddMissionBehavior(new WoW_MissionSetting());
- mission.AddMissionBehavior(new WoW_MainAgentStatus());
复制代码
|
评分
-
查看全部评分
|