当前位置: 首页 > news >正文

Unigine整合Myra UI Library全纪录(1)

什么是Myra?

这个是Myra:https://github.com/rds1983/Myra

熟悉WPF/Avalonia/Silverlight/UWP开发的朋友肯定一眼就能看出来这个UI库用的是什么佐料了。它当然不是完整的WPF实现,对于游戏开发而言也没必要用完整的WPF,太重了。

Myra有着和WPF非常类似的Layout系统,运行效率也不错,同时还支持XML声明(被称为MML,Myra Markup Language),既可以实时加载XML也可以用MyraPad将这些XML转换成C#代码。整合进引擎里也不麻烦。这就够了。

这玩意儿甚至FileDialog和ColorPickerDialog都给你做了一份,省了大事了🤣

第一步:先学再用

本文写于Myra 1.5.9版本。

首先建议把Github上Myra的Wiki都看一遍,大概了解一下Myra是怎么玩的。之后我建议找个空文件夹git clone https://github.com/rds1983/Myra.git,然后打开Myra\build\Myra.PlatformAgnostic.sln,你会看到几个项目,其中在Samples文件夹下面有这三个示例项目:

  • Samples/Myra.Samples.PlatformAgnostic:用MonoGame手动整合,不使用自带的整合方式,而是手动从接口继承并实现所有接口功能。如果你要整合的目标引擎里面有Xna的SpriteBatch类似物,那么直接照着这个例子去做就够了。
  • Samples/Myra.Samples.Silk.NET:用Silk.NET + OpenGL整合,是最底层的整合方式。虽然窗口框架还是Silk.NET提供的,不过其负责渲染的QuadBatch.cs完全是手写。如果你要整合的目标引擎里面只提供了最基础的Mesh渲染方式(比如Unigine),那么就要参考这个例子来做。
  • Samples/Myra.Samples.Silk.NET.TrippyGL:仍然是Silk.NET + OpenGL整合,但使用TrippyGL简化了代码。TrippyGL提供了一个SpriteBatch类似物,叫TextureBatcher,因此这个整合过程和手动MonoGame整合很类似。其实可以跳过不看。

最后是Myra.PlatformAgnostic,Myra主项目,代码虽然很多但整理的很有序,想看可以钻进去看,但目前先不走那么深。

接下来建议去这里:https://github.com/unigine-engine/unigine-imgui-csharp-integration-sample

这个例子是Unigine整合ImGui.NET的例子,其中ImGuiImpl.cs是整个实现过程。别被这看上去乱七八糟的文件吓到,其实它内容还是挺简单的,只是把几个不同的模块全写在了一个类里面。好孩子不要这么做哦!

向Unigine整合新的GUI系统,绝大部分内容都可以参考这个ImGui.NET的实现方式。后文我也会多次提到这个东西。

第二步:准备工作

Myra自带有MonoGame、FNA和Stride的整合,同时还有PlatformAgnostic包用来应付其他的情况。我们当然要用这个包,给项目加上Myra支持很简单,毕竟这是Unigine😁:

dotnet add package Myra.PlatformAgnostic

然后在source文件夹下建个新的文件夹,就叫MyraIntegration好了。

整合Myra到Unigine,包括整合到其他所有引擎,大概有这么几步工作:

  1. 实现接口ITexture2DManager,实现对纹理的创建和属性获取。
  2. 实现接口IMyraRenderer,实现窗口Scissor的设置和纹理的绘制
  3. 实现接口IMyraPlatform,向Myra提供窗口、键盘、鼠标和触屏的信息。目前我们暂时不管触屏。
  4. 最后,将上述实现提供给MyraEnvironment.Platform,再创建一个Myra.Graphics2D.UI.Desktop对象,将Desktop.Root设置成UI控件的实现,最后通过Desktop.Render()渲染出结果。

那么,接下来一个一个的处理:

ITexture2DManager

Unigine创建纹理要分两步走,注意创建为RGBA8格式,Usage要加上Dynamic,并且设置为Point Filter:

object ITexture2DManager.CreateTexture(int width, int height)
{var texture = new Texture();texture.Create2D(width, height, Texture.FORMAT_RGBA8, Texture.FORMAT_USAGE_DYNAMIC | Texture.SAMPLER_FILTER_POINT);return texture;
}

Myra有个功能是Smooth Font,需要将纹理Filter设置为Bilinear。这个功能并不是指定渲染的字体是否有抗锯齿(抗锯齿是一直启用的),而是在UI发生缩放的时候是否对渲染出来的文字做平滑化。目前我们先不管这个。

之后要告诉Myra纹理的尺寸,毕竟传给Myra的是一个object而没有其他的信息:

Point ITexture2DManager.GetTextureSize(object obj)
{var texture = (Texture)obj;return new Point(texture.GetWidth(), texture.GetHeight());
}

接下来要将图像数据传递给纹理:

void ITexture2DManager.SetTextureData(object obj, Rectangle bounds, byte[] data)
{using var image = new Image();image.Create2D(bounds.Width, bounds.Height, Image.FORMAT_RGBA8, 1, false);image.SetPixels(data);var texture = (Texture)obj;texture.SetImage2D(image, bounds.X, bounds.Y);image.SetPixels((byte[])null!);
}

Unigine没有类似OpenGL的glTexSubImage2D,不能直接往纹理上写数据,需要创建一个Image对象然后拷贝过去。
创建的Image对象也得是RGBA8格式,和纹理保持一致。不需要Mipmap,并将clear参数设置成false,毕竟马上就要用数据写满整个Image。

后面就很好理解了,将Image传递给Texture进行数据上传。接下来这一行image.SetPixels((byte[])null!)不是C#里常见的操作:将Image的缓冲区设置为null。这一点和Unigine的C++底层实现有关,它的C++底层会直接拿data的指针去用,而不进行数据拷贝。因此在最后Image.Dispose的时候会报错。因此这里要设置为空。

这个古怪的设计卡了我好一段时间,直到我仔细翻阅了ImGui.NET的实现才搞明白。你可以在ImGuiImpl.cs的create_font_texture()函数里找到类似的东西。示例里使用了一个Blob进行中转,因为示例从ImGui获取的数据是RGBA32格式的,需要多一个步骤转换成RGBA8。Myra这边数据格式是相同的因此可以省略这一步。

IMyraPlatform

Renderer牵扯的东西多一些,先来搞Platform。

Myra需要知道渲染窗口的大小,也就是Unigine的ClientRenderSize:

Point IMyraPlatform.ViewSize
{get {var clientRenderSize = WindowManager.MainWindow.ClientRenderSize;return new Point(clientRenderSize.x, clientRenderSize.y);}
}

之后实现向Myra提供鼠标信息的接口:

int mouseWheelValue;MouseInfo IMyraPlatform.GetMouseInfo()
{var position = Input.MousePosition - WindowManager.MainWindow.ClientPosition;mouseWheelValue += Input.MouseWheel;return new MouseInfo {IsLeftButtonDown = Input.IsMouseButtonPressed(Input.MOUSE_BUTTON.LEFT),IsRightButtonDown = Input.IsMouseButtonPressed(Input.MOUSE_BUTTON.RIGHT),IsMiddleButtonDown = Input.IsMouseButtonPressed(Input.MOUSE_BUTTON.MIDDLE),Position = new Point(position.x, position.y),Wheel = mouseWheelValue,};
}

有两点要注意。第一点是这里要使用Input.IsMouseButtonPressed而不是Input.IsMouseButtonDown,后者返回的是当前帧内鼠标按键是否有被按下过。另一点是鼠标滚轮数据,Myra需要的是累计后的绝对值(Xna的处理方式)而不是常见的相对值,因此这里定义了一个mouseWheelValue变量将历史数据累加起来再传递给Myra。

接下来需要向Myra提供键盘信息。由于Myra的Keys值和Unigine的不一样(Myra用的是Xna的值,也就是Windows平台的值,Unigine使用了一套自己的东西),因此需要创建一个映射表:

readonly Keys[] UnigineToMyraKeyMap = new Keys[(int)Input.KEY.NUM_KEYS];void GenerateMyraKeyMap()
{UnigineToMyraKeyMap[(int)Input.KEY.ESC] = Keys.Escape;UnigineToMyraKeyMap[(int)Input.KEY.F1] = Keys.F1;UnigineToMyraKeyMap[(int)Input.KEY.F2] = Keys.F2;UnigineToMyraKeyMap[(int)Input.KEY.F3] = Keys.F3;UnigineToMyraKeyMap[(int)Input.KEY.F4] = Keys.F4;UnigineToMyraKeyMap[(int)Input.KEY.F5] = Keys.F5;UnigineToMyraKeyMap[(int)Input.KEY.F6] = Keys.F6;UnigineToMyraKeyMap[(int)Input.KEY.F7] = Keys.F7;UnigineToMyraKeyMap[(int)Input.KEY.F8] = Keys.F8;UnigineToMyraKeyMap[(int)Input.KEY.F9] = Keys.F9;UnigineToMyraKeyMap[(int)Input.KEY.F10] = Keys.F10;UnigineToMyraKeyMap[(int)Input.KEY.F11] = Keys.F11;UnigineToMyraKeyMap[(int)Input.KEY.F12] = Keys.F12;UnigineToMyraKeyMap[(int)Input.KEY.PRINTSCREEN] = Keys.PrintScreen;UnigineToMyraKeyMap[(int)Input.KEY.SCROLL_LOCK] = Keys.Scroll;UnigineToMyraKeyMap[(int)Input.KEY.PAUSE] = Keys.Pause;UnigineToMyraKeyMap[(int)Input.KEY.BACK_QUOTE] = Keys.OemTilde;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_1] = Keys.D1;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_2] = Keys.D2;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_3] = Keys.D3;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_4] = Keys.D4;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_5] = Keys.D5;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_6] = Keys.D6;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_7] = Keys.D7;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_8] = Keys.D8;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_9] = Keys.D9;UnigineToMyraKeyMap[(int)Input.KEY.DIGIT_0] = Keys.D0;UnigineToMyraKeyMap[(int)Input.KEY.MINUS] = Keys.OemMinus;UnigineToMyraKeyMap[(int)Input.KEY.EQUALS] = Keys.OemPlus;UnigineToMyraKeyMap[(int)Input.KEY.BACKSPACE] = Keys.Back;UnigineToMyraKeyMap[(int)Input.KEY.TAB] = Keys.Tab;UnigineToMyraKeyMap[(int)Input.KEY.Q] = Keys.Q;UnigineToMyraKeyMap[(int)Input.KEY.W] = Keys.W;UnigineToMyraKeyMap[(int)Input.KEY.E] = Keys.E;UnigineToMyraKeyMap[(int)Input.KEY.R] = Keys.R;UnigineToMyraKeyMap[(int)Input.KEY.T] = Keys.T;UnigineToMyraKeyMap[(int)Input.KEY.Y] = Keys.Y;UnigineToMyraKeyMap[(int)Input.KEY.U] = Keys.U;UnigineToMyraKeyMap[(int)Input.KEY.I] = Keys.I;UnigineToMyraKeyMap[(int)Input.KEY.O] = Keys.O;UnigineToMyraKeyMap[(int)Input.KEY.P] = Keys.P;UnigineToMyraKeyMap[(int)Input.KEY.LEFT_BRACKET] = Keys.OemOpenBrackets;UnigineToMyraKeyMap[(int)Input.KEY.RIGHT_BRACKET] = Keys.OemCloseBrackets;UnigineToMyraKeyMap[(int)Input.KEY.ENTER] = Keys.Enter;UnigineToMyraKeyMap[(int)Input.KEY.CAPS_LOCK] = Keys.CapsLock;UnigineToMyraKeyMap[(int)Input.KEY.A] = Keys.A;UnigineToMyraKeyMap[(int)Input.KEY.S] = Keys.S;UnigineToMyraKeyMap[(int)Input.KEY.D] = Keys.D;UnigineToMyraKeyMap[(int)Input.KEY.F] = Keys.F;UnigineToMyraKeyMap[(int)Input.KEY.G] = Keys.G;UnigineToMyraKeyMap[(int)Input.KEY.H] = Keys.H;UnigineToMyraKeyMap[(int)Input.KEY.J] = Keys.J;UnigineToMyraKeyMap[(int)Input.KEY.K] = Keys.K;UnigineToMyraKeyMap[(int)Input.KEY.L] = Keys.L;UnigineToMyraKeyMap[(int)Input.KEY.SEMICOLON] = Keys.OemSemicolon;UnigineToMyraKeyMap[(int)Input.KEY.QUOTE] = Keys.OemQuotes;UnigineToMyraKeyMap[(int)Input.KEY.BACK_SLASH] = Keys.OemBackslash;UnigineToMyraKeyMap[(int)Input.KEY.LEFT_SHIFT] = Keys.LeftShift;UnigineToMyraKeyMap[(int)Input.KEY.LESS] = Keys.Apps;UnigineToMyraKeyMap[(int)Input.KEY.Z] = Keys.Z;UnigineToMyraKeyMap[(int)Input.KEY.X] = Keys.X;UnigineToMyraKeyMap[(int)Input.KEY.C] = Keys.C;UnigineToMyraKeyMap[(int)Input.KEY.V] = Keys.V;UnigineToMyraKeyMap[(int)Input.KEY.B] = Keys.B;UnigineToMyraKeyMap[(int)Input.KEY.N] = Keys.N;UnigineToMyraKeyMap[(int)Input.KEY.M] = Keys.M;UnigineToMyraKeyMap[(int)Input.KEY.COMMA] = Keys.OemComma;UnigineToMyraKeyMap[(int)Input.KEY.DOT] = Keys.OemPeriod;UnigineToMyraKeyMap[(int)Input.KEY.SLASH] = Keys.OemQuestion;UnigineToMyraKeyMap[(int)Input.KEY.RIGHT_SHIFT] = Keys.RightShift;UnigineToMyraKeyMap[(int)Input.KEY.LEFT_CTRL] = Keys.LeftControl;UnigineToMyraKeyMap[(int)Input.KEY.LEFT_CMD] = Keys.LeftWindows;UnigineToMyraKeyMap[(int)Input.KEY.LEFT_ALT] = Keys.LeftAlt;UnigineToMyraKeyMap[(int)Input.KEY.SPACE] = Keys.Space;UnigineToMyraKeyMap[(int)Input.KEY.RIGHT_ALT] = Keys.RightAlt;UnigineToMyraKeyMap[(int)Input.KEY.RIGHT_CMD] = Keys.RightWindows;UnigineToMyraKeyMap[(int)Input.KEY.MENU] = Keys.None;UnigineToMyraKeyMap[(int)Input.KEY.RIGHT_CTRL] = Keys.RightControl;UnigineToMyraKeyMap[(int)Input.KEY.INSERT] = Keys.Insert;UnigineToMyraKeyMap[(int)Input.KEY.DELETE] = Keys.Delete;UnigineToMyraKeyMap[(int)Input.KEY.HOME] = Keys.Home;UnigineToMyraKeyMap[(int)Input.KEY.END] = Keys.End;UnigineToMyraKeyMap[(int)Input.KEY.PGUP] = Keys.PageUp;UnigineToMyraKeyMap[(int)Input.KEY.PGDOWN] = Keys.PageDown;UnigineToMyraKeyMap[(int)Input.KEY.UP] = Keys.Up;UnigineToMyraKeyMap[(int)Input.KEY.LEFT] = Keys.Left;UnigineToMyraKeyMap[(int)Input.KEY.DOWN] = Keys.Down;UnigineToMyraKeyMap[(int)Input.KEY.RIGHT] = Keys.Right;UnigineToMyraKeyMap[(int)Input.KEY.NUM_LOCK] = Keys.NumLock;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIVIDE] = Keys.Divide;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_MULTIPLY] = Keys.Multiply;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_MINUS] = Keys.Subtract;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_7] = Keys.NumPad7;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_8] = Keys.NumPad8;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_9] = Keys.NumPad9;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_PLUS] = Keys.Add;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_4] = Keys.NumPad4;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_5] = Keys.NumPad5;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_6] = Keys.NumPad6;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_1] = Keys.NumPad1;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_2] = Keys.NumPad2;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_3] = Keys.NumPad3;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_ENTER] = Keys.Enter;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DIGIT_0] = Keys.NumPad0;UnigineToMyraKeyMap[(int)Input.KEY.NUMPAD_DOT] = Keys.Decimal;
}

有了这个映射表之后,向Myra提供键盘信息就很简单了:

void IMyraPlatform.SetKeysDown(bool[] keys)
{for (int key = 0; key < (int)Input.KEY.NUM_KEYS; ++key) {var myraKey = UnigineToMyraKeyMap[key];keys[(int)myraKey] = Input.IsKeyDown((Input.KEY)key);}
}

和鼠标那边不同,长按一个键盘按键的时候,Input.IsKeyDown会多次触发,因此可以实现长按按键连续输入的效果。

剩下的两个:

void IMyraPlatform.SetMouseCursorType(MouseCursorType mouseCursorType)
{//TODO: Use game's custom cursor with Input.SetMouseCursorCustom()
}TouchCollection IMyraPlatform.GetTouchState()
{return TouchCollection.Empty;
}

SetMouseCursorType这里,根据传进来的MouseCursorType,用Input.SetMouseCursorCustom()设置成游戏自定义的光标即可。目前先略过。

GetTouchState这里,可以先忽略。Unigine是有触控处理的API的,就在Input里面,想实现也可以实现,不过目前Unigine不支持移动平台,忽略掉也不会有太大的问题。

http://www.hskmm.com/?act=detail&tid=16915

相关文章:

  • new 为数组开辟内容空间的时候,数组大小这个额外的信息是如何存储的? int * p = new int[5]; 指针p 指向的的int 数据地址还是数组大小的地址?
  • 欧拉函数学习笔记
  • PDF论文文字公式提取,翻译与对照代码(自用)
  • Lambda表达式 - AlgosEng
  • ABAP 调用HTTP上传附件中文乱码
  • PDF入参以及模板对应签章图踩坑点 JAR版本为 iText5
  • 从 0 到 1 精通 SkyWalking:分布式系统的 “透视镜“ 技巧全解析
  • 系统调用brk 和 mmap 有什么不同?
  • 雷达系统杂波设计与仿真
  • 国标GB28181视频平台EasyCVR一体化加油站安防视频监控方案与实践
  • JavaScript 沙箱
  • PDF入参以及模板对应签章图踩坑点
  • 高性能PCIe 3.0软核,x1~x16,支持EP/RC,AXI4接口,内置DMA控制器,适用ASIC和FPGA
  • 使用git clone 批量下载huggingface模型文件
  • Python 换进安装GDAL
  • sync(同步本地文件到OSS)
  • MyBatisPlus 会默认设置 mybatis 的 scanPackages 为当前 BeanFactory 的 auto-configuration 的 base packages
  • 工程实践 使用本地包开发python项目
  • 详细介绍:Python + Flask + API Gateway + Lambda + EKS 实战
  • 日记4
  • P2042 [NOI2005] 维护数列 题解
  • 达梦数据库查询字段类型为Date 修改为DateTime
  • C++ new 操作符在操作系统层执行了什么操作?
  • [ABC422F-G] 题解
  • 别再靠 “关设备” 减碳!EMS 的 “预测性控能”,让企业满产也能达标双碳
  • LAMP 架构说明及部署实践 - 教程
  • MyEMS 深度解析:核心功能模块、数据流转逻辑与工业能源优化落地路径
  • kettle插件-国产数据库金仓插件,助力国产数据库腾飞
  • 制造业碳足迹追踪:开源能源管理系统如何助力企业实现“碳数据可视化”?
  • iframe安全盲区:支付信息窃取攻击的新温床 - 教程