news 2026/1/12 9:06:21

Unigine整合Myra UI Library全纪录(3):整合与优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unigine整合Myra UI Library全纪录(3):整合与优化

MyraIntegration

当Texture2DManager,MyraRenderer和MyraPlatform都实现了之后,就可以将它们整合起来了。

首先,IMyraPlatform.Renderer返回实现好的MyraRenderer,而IMyraRenderer.TextureManager则返回实现好的Texture2DManager。

接下来建立一个新的static class MyraIntegration。写一个Init()函数,这里需要设置一些状态:

MyraEnvironment.Platform = new MyraPlatform();

MyraEnvironment.EnableModalDarkening = true;

将实现好的MyraPlatform交给Myra环境。而EnableModalDarkening则表示在以ShowModal()方式显示FileDialog、ColorPickerDialog等Dialog时,会给背景加一层半透明黑色遮罩。告诉用户现在显示的是一个模态窗口。

接下来需要对IME提供支持。当你用输入法输入文字的时候,只用Input.IsKeyDown是捕捉不到的。不过Unigine提供了其他的方法:

Desktop.HasExternalTextInput = true; //告诉Myra平台有额外的文字输入

Input.EventTextPress.Connect(static text => {

if (!Input.MouseGrab && text <= char.MaxValue) {

Desktop.OnChar((char)text);

}

});

Unigine发送的是uint,如果你输入了超过UCS-2字符集的文字,那么这个值是会超过char的值上限的,而Myra无法支持这些文字,因此要过滤一下再提交给Myra。

最后需要告诉Myra在什么时候可以进行渲染。一般来说,Unigine可以在三个事件内进行手动渲染工作:Render.EventEndScreen,Render.EventEndVisualizer和Engine.EventEndPluginsGui。在Unigine提供的示例项目中三种都有出现。不过在前两个事件内要渲染的时候,需要获取当前RenderTarget(通过Renderer.RenderTarget)或者获取一个临时RenderTarget(通过Render.GetTemporaryRenderTarget()并在全部渲染结束之后调用Render.ReleaseTemporaryRenderTarget())。具体的做法ImGui.NET示例里有。而如果渲染工作放在EventEndPluginsGui则不需要RenderTarget的介入。前面在制作TextureQuadBatcher时没有添加RenderTarget支持,因此这里使用EventEndPluginsGui。代码很简单:

Engine.EventEndPluginsGui.Connect(static () => Desktop.Render());

基本的整合就已经完成了。实际上现在应用已经可以将Myra的界面正确的渲染出来,但在键盘鼠标交互上会有问题。Unigine在你点击了窗口内部之后,会自动捕获鼠标光标,并接管鼠标移动的消息处理。然而在用户操作UI控件的时候,不能让Unigine来捣乱,因此需要写一些代码来过滤掉部分事件。

和ImGui不同,Myra并没有一个简单的方法告诉你“我需要拦截发往游戏引擎的鼠标消息”,因此这里使用一个简单暴力的方法:当鼠标位置在Myra的UI控件上面,并且引擎没有捕获鼠标的时候,则不向引擎发送鼠标消息。

首先,还是在Init()函数里,加这么一堆:

Engine.EventBeginRender.Connect(static () => {

if (Desktop.IsMouseOverGUI && !Input.MouseGrab) {

Gui.GetCurrent().MouseButtons = 0;

}

});

这几行其实是为了防止和Unigine自带的Gui系统冲突。如果你不用Unigine.Gui的话,有没有这些影响不大。

Init()函数就到此为止了。接下来,有些事件需要每帧都处理一下,因此再写一个Update()函数:

if (!Input.MouseGrab) {

ControlsApp.Enabled = !Desktop.IsMouseOverGUI;

if (Desktop.IsMouseOverGUI) {

ControlsApp.MouseDX = 0;

ControlsApp.MouseDY = 0;

}

if (Desktop.IsMouseOverGUI) {

Input.MouseHandle = Input.MOUSE_HANDLE.USER;

}

else {

Input.MouseHandle = Input.MOUSE_HANDLE.GRAB;

}

}

这坨代码是从ImGui.NET整合示例里抄来的,功能就是鼠标在非捕获状态下,移动到Myra控件上方时,不管怎么点都不让引擎捕获鼠标。而移出Myra控件的范围则恢复原来的功能。

接下来还要加一坨代码:

if (Input.MouseGrab && Desktop.FocusedKeyboardWidget != null) {

Desktop.FocusedKeyboardWidget = null;

}

功能也很简单:当鼠标进入了捕获状态,但某个控件仍然有键盘的Focus状态时(比如正在某个TextBox里输入),则取消这个控件的Focus状态。避免玩家在游戏里WASD移动的时候,输入框里一堆wwwwaaaaassssddddd出来了。

到此,MyraIntegration就算做完了。接下来到Unigine自动创建的AppSystemLogic.cs里面去。在AppSystemLogic.Init()内调用MyraIntegration.Init(),在AppSystemLogic.Update()内调用MyraIntegration.Update()。整合工作就算完成了。

接下来当然是测试一下。测试用例就用Myra官方示例提供的好了。

去前面clone出来的Myra的源代码里,去到Myra/samples/Myra.Samples.Silk.NET文件夹,将AllWidgets.cs和AllWidgets.Generated.cs这两个文件复制过来。

接下来回到MyraIntegration.Init()函数,加这么一行代码:

Desktop.Root = new Myra.Samples.AllWidgets.AllWidgets();

然后大胆的运行吧。只要程序没炸,应该能看到这样一个画面:

myra

这就算成功了。点一点各个按钮,看看各种Dialog的效果,看看各种Debug显示的效果,其中将鼠标指向的控件高亮的功能可以多试试,顺便熟悉一下这个界面里Myra各个控件是怎么划分的。最后在TextBox里输入一下文字看看有没有正常显示,点到控件外面看看Unigine有没有正确拿回鼠标控制权。

由于Myra自带的字体文件是纯英文,因此输入中文会显示成空白,目前可以在前面MyraIntegration.Init()里面Input.EventTextPress的处理过程中下一个断点,看看输入的文字有没有被正确的传递进去。之后自行给TextBox换个字体就可以。

优化:新的QuadBatcher

前面在制作TextureQuadBatcher时,选择了比较简单直接的实现方式,效率不高。有一个优化方向就是将所有传递进来的Vertex先缓存起来,然后一次性全提交给MeshDynamic。之后在渲染的时候,根据不同的Scissor和Texture,每次只渲染其中的一部分。这样就省去了多次ClearVertex/SetVertexArray/FlushVertex的过程。

思路上是仿照ImGui.NET的整合示例:当调用Desktop.Render()的时候,Myra会依次调用Begin -> Scissor/DrawQuad -> End,新的QuadBatcher则修改为在这整个过程中,并不进行实际的渲染,而是记录下Scissor/Texture状态,以及在这个状态下渲染的所有Vertex。

Github仓库里面已经是最终优化后代码。这里完整的代码就不贴了,大概说一下整个改进的过程是怎么样的。

首先看QuadBatcher.cs,这里相比于旧的TextureQuadBatcher,主要是多了这么一个变量:

readonly List<(Texture? texture, Rectangle? scissor, int vertexIndex)> renderData = new(16);

每当Texture或者Scissor发生变化的时候,就记录在这个renderData里面,同时记录下来当前的Vertex序号。之后渲染的时候就能利用这个数据一段一段的将MeshDynamic里面的东西渲染出去。

QuadBatcher.NewBatch()负责将所有变量重置/清空。

QuadBatcher.DrawQuad()和QuadBatcher.SetScissorTest()可以看到将数据记录在renderData的过程。并且这里不再将顶点Flush出去,而只是在vertexCount超过了MaxVertices之后,统一渲染然后重新开始下一批次。

QuadBatcher.RenderBatch()就是改动的重点了。既然所有的渲染都会在这里统一进行,那么首先把旧TextureQuadBatcher的Begin()/End()里面的代码拿过来。Flush()里面应用顶点数据的部分也可以照搬。

重点是后面RenderSurface的部分。由于现在只渲染一部分,因此这段代码改动量最大:

int currentVertex = 0;

foreach (var (texture, scissor, vertexIndex) in renderData) {

if (vertexIndex > currentVertex) {

quadMesh.RenderSurface(MeshDynamic.MODE_TRIANGLES, 0, currentVertex / 4 * 6, vertexIndex / 4 * 6);

currentVertex = vertexIndex;

}

if (texture != null) {

RenderState.SetTexture(RenderState.BIND_FRAGMENT, 0, texture);

}

if (scissor != null) {

int y = clientRenderSize.y - (scissor.Value.Y + scissor.Value.Height);

RenderState.SetScissorTest((float)scissor.Value.X / clientRenderSize.x, (float)y / clientRenderSize.y, (float)scissor.Value.Width / clientRenderSize.x, (float)scissor.Value.Height / clientRenderSize.y);

}

}

if (currentVertex < vertexCount) {

quadMesh.RenderSurface(MeshDynamic.MODE_TRIANGLES, 0, currentVertex / 4 * 6, vertexCount / 4 * 6);

}

由于我们把每个“分段”信息都记录在了renderData,因此遍历这个数组,按照里面记录的信息进行RenderSurface和SetTexture/SetScissorTest的调用工作。由于我们保存在renderData里的vertexIndex,记录的实际上是“从这里开始,后面这一段Vertex渲染时候用的Texture和Scissor”,因此在整个数组遍历完之后,还要再调用一次RenderSurface把余下的一段都给渲染出来。

之后就要回到MyraRenderer,首先把旧的TextureQuadBatcher实例删掉。然后添加NewRender()和DrawToOutput(),并修改IMyraRenderer.Scissor和IMyraRenderer.DrawQuad()来调用QuadBatcher相应的函数,再把IMyraRenderer.Begin()和IMyraRenderer.End()的内容都删掉。

最后回到MyraIntegration,将Engine.EventEndPluginsGui的事件处理被改成这个样子:

var renderer = (MyraRenderer)MyraEnvironment.Platform.Renderer;

renderer.NewRender(); //做准备工作

Desktop.Render(); //Myra进行“渲染”工作

renderer.DrawToOutput(); //Unigine进行实际的渲染工作

这样就完成了整个优化过程。和旧的TextureQuadBatcher相比,每次渲染MeshDynamic只被更新了一次,大大提高了CPU/GPU协同的效率。

可以看出整个整合的工作并不难,主要代码量是制作一个高效率的SpriteBatch类似物,以及映射鼠标/键盘消息。剩下的就是一些框架粘合代码。

Myra把大部分琐碎的事情都自己完成了,因此将其整合到任何一个引擎里(只要这个引擎里提供了SpriteBatch类似物,或者允许你直接渲染顶点数据)都是很容易的。比如在这里做好的这个QuadBatcher,拿去改一改,就能用到别的引擎的整合工作上。例如Flax Engine,把MeshDynamic相关的内容换成FlaxEngine.Render2D就可以(Render2D能直接渲染顶点,无需创建额外的Mesh)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/8 20:11:05

行业视角下的数据库监控演进:主动预防能力何以成为刚需

凌晨三点的告警电话刺耳地响起&#xff0c;屏幕上一片飘红的性能指标让DBA&#xff08;数据库管理员&#xff09;瞬间清醒&#xff0c;又一个不眠之夜在“救火”中开始了——这种场景曾是DBA工作的日常。深夜的“救火”场景&#xff0c;本质是传统被动响应运维模式的真实写照。…

作者头像 李华
网站建设 2026/1/3 19:33:59

​当年靠这个ASP.NET电子书城系统,我的毕业设计直接拿优!(附核心源码)​

谁懂啊!当年做毕业设计时,选了个 “电子书城系统”,没想到不仅完美解决了传统购书的痛点,还靠扎实的技术实现拿了优秀!今天把这份压箱底的开发笔记分享出来,包含技术选型、核心模块实现、踩坑实录,适合.NET 初学者练手,老程序员也能追忆当年的开发情怀~ 一、项目背景…

作者头像 李华
网站建设 2026/1/4 9:32:46

极坐标波束形成数据底跟踪算法详解

极坐标波束形成数据底跟踪算法详解 一、基本概念 1.1 底跟踪的定义 底跟踪&#xff08;Bottom Tracking&#xff09;是通过声学回波信号检测和跟踪海底位置的技术&#xff0c;主要用于&#xff1a; 测量船舶相对于海底的速度确定水深辅助水下导航定位补偿多普勒计程仪测量 …

作者头像 李华
网站建设 2026/1/3 7:19:17

【技术教程】TrustFlow 授权策略是怎么实现的?

打开链接即可点亮社区Star&#xff0c;照亮技术的前进之路。 Github 地址&#xff1a;https://github.com/secretflow/trustflow/ TrustFlow提供了一套简洁易懂的语法帮助用户对数据使用行为的授权进行描述。接下来我们会详细描述这套语法&#xff0c;并结合示例进行讲解。 …

作者头像 李华
网站建设 2026/1/9 15:58:29

丐版 OI 技巧 / 杂项部分总结 + 作者学习笔记

持久化区间修改区间查询线段树&#xff1a;SP11470 TTM - To the moon点击查看代码2. 有后效性的 dpCF24D Broken robot一般用高斯消元 求解。也可以多跑几遍朴素 dp 使误差降到可接受范围内。多跑几遍的代码3. P14402 [JOISC 2016] 危险的滑冰 / Dangerous Skating图论建模。思…

作者头像 李华