在 Unity 中创建简单可靠且干净的 UI

Posted pxr007

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在 Unity 中创建简单可靠且干净的 UI相关的知识,希望对你有一定的参考价值。

无论您在 Unity 中构建的应用程序类型如何,您都很有可能会使用一些用户界面(或简称为 UI)。然而,尽管这项任务很简单,但为您的 UI 组件保持一个干净可靠的系统并非易事。事实上,一些错误的步骤会迅速破坏未来的变化,并导致数小时的过度工作和修订。

为了避免您将所有精力花在构建不当的 UI 上,本文将介绍主要步骤,了解 Unity 提供的 UI 元素,然后是一个简单的工作流程来开发您的界面结构,最后是如何使用免费资源和其他 Unity 工具来增强其视觉元素。

  • 设置环境

  • 用户界面画布

    • 画布渲染模式

    • 画布缩放器

  • UI 面板、图像、按钮和文本

    • 用户界面图像

    • 用户界面文本

    • 用户界面按钮

    • 用户界面面板

    • 其他 UI 元素

  • 矩形变换和锚点

  • 锚预设

  • 基本菜单结构

    • 用于保存特定内容的育儿面板

    • 自动布局工具

    • 到目前为止的层次结构

  • 添加另一个窗口

    • 设置窗口面板

    • 返回自动布局

    • 滑块

  • 更换资产

  • 带有后处理的额外图形

设置环境

在进入 UI 组件之前,首先调整 Unity 的布局以提高开发效率通常很有帮助。这样做时我喜欢考虑两个步骤:并排设置场景视图和游戏视图,这样我可以在看到结果时同时更改 UI 元素,并在我看到结果时将游戏视图更改为正确的分辨率工作中。

通常,默认情况下,游戏视图设置为 Free Aspect。通过单击分辨率选项,您可以更改它以匹配目标的分辨率(1366×768 或 1920×1080)或目标的纵横比(16×9 或18×9)。正如我们将看到的,这一步对于充分评估您在 UI 上工作的进度至关重要。

除了照顾 Unity 面板的组成外,新的 Unity 场景中的元素会极大地分散干净 UI 的注意力。这些主要是相机、灯光和天空盒。

从负责渲染 Game 视图的Camera 组件开始,我经常将Clear Flags更改为Solid Color(这会从 Game 视图中移除 Unity 的标准天空盒)并更改Background以匹配我想要的背景颜色。请注意,此步骤对于不需要天空盒的菜单和其他场景至关重要。除此之外,移除天空盒会改变您的应用程序美学,使其远离粗糙的原型。

此外,当专门为菜单类型的场景工作时,我经常做一些额外的步骤,无论它是否是附加场景,通过调整相机设置来进行剔除和投影。

剔除与Unity 用于优化其渲染性能的可见性剔除技术有关。Culling Mask 选择相机应该渲染或不渲染的对象。在 UI 场景中,我经常在 Culling Mask 中只保留 UI 相关的对象,以避免错误地渲染剩余的对象和其他辅助游戏对象。


超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →


遮挡剔除是 Unity 完成的一种可见性剔除技术,用于从渲染器中移除当前被其前面的其他对象隐藏的对象。对于菜单场景,我更喜欢关闭此选项,因为这不太可能发生在任何可能有益的规模上,也不适用于 2D 场景。

此外,我经常从场景中移除默认灯光,因为 UI 元素不会受到它的影响。至于天空盒,我们也可以打开Lighting选项卡并更改它,这通常是不必要的,因为我们已经使用相机选项禁用了它。

用户界面画布

Unity UI 组件必须放置在名为 Canvas 的特定游戏对象中。所有 UI 元素都是作为 Canvas 的子级放置的游戏对象。这些游戏对象在 Canvas 层次结构中的顺序很重要。

元素将按其顺序绘制:首先绘制第一个元素,然后绘制第二个,依此类推。如果两个元素在屏幕上共享同一区域,则层次结构中的最新元素将绘制在其他元素之上。

将 Canvas 游戏对象添加到场景后,一些游戏组件将自动附加到它,包括 Canvas 组件。为了避免游戏对象和脚本之间的混淆,我经常重命名我的 Canvas 游戏对象以匹配其职责,例如MenuCanvas或PauseCanvas。

只为整个 UI 使用一个 Canvas 是可能的,但必须考虑后果。每次更新其元素之一(例如突出显示按钮或移动滑块)时,Unity 都会更新并重绘 Canvas。如果您的 Canvas 很复杂并且包含许多元素,那么任何运行时更改都可能会影响您的性能。

为此,请考虑为不同的职责和更新样式设置多个 Canvas(例如,将经常动画的对象与静态对象保持在单独的 Canvas 中)。为了保护您的层次结构,也可以嵌套画布。

在向我们的画布添加内容之前,需要注意两个设置。

画布渲染模式

画布渲染模式定义了画布在场景中的渲染方式。渲染模式分为三种类型:叠加、屏幕空间和世界空间。

覆盖只是将画布渲染到游戏屏幕的顶部,绕过相机和后期处理效果。另一方面,屏幕空间根据与游戏摄像机的给定距离来渲染画布。因此,屏幕空间渲染模式看起来更像是放置在游戏世界中,元素可能显示在 UI 之上。此外,由于 Screen Space 是根据相机进行渲染的,因此会受到相机设置(例如透视和视野)以及后期处理效果的影响。

最后,World Space值得单独写一篇文章。World Space 将 Canvas 渲染为游戏世界中的 3D 元素。当我们希望在 3D 对象之上弹出 UI 元素或设计剧情界面时,通常会使用它。对于本文,我们不会进一步讨论世界空间渲染模式。

通常,对于菜单和其他 UI 画布,我们使用叠加或屏幕空间,这取决于我们是否希望 UI 受到相机和后期处理效果的影响。稍后,我们将讨论我们可能会选择其中一个而不是另一个的情况。


来自 LogRocket 的更多精彩文章:

  • 不要错过来自 LogRocket 的精选时事通讯The Replay

  • 使用 React 的 useEffect优化应用程序的性能

  • 在多个 Node 版本之间切换

  • 了解如何使用 AnimXYZ 为您的 React 应用程序制作动画

  • 探索 Tauri,一个用于构建二进制文件的新框架

  • 比较NestJS 与 Express.js

  • 发现TypeScript 领域中使用的流行 ORM


画布缩放器

Canvas Scaler不是 Canvas 游戏对象的默认组件,但值得考虑,尤其是在构建具有多种分辨率的应用程序时。给定当前分辨率,Canvas Scaler 使用参考分辨率来缩放 UI 元素并将它们与当前元素相匹配。

例如,如果 Canvas Scaler 使用 200×400 的参考分辨率和 400×800 的当前分辨率,它将缩放组件两次。如果当前分辨率与参考纵横比不匹配,则 Canvas Scaler 使用Match 属性来决定将哪个尺寸用作主要点:宽度,当它设置为 0 时,或者高度,当它设置为 1 时。在 0 和 1 之间将按比例匹配宽度和高度。

UI 面板、图像、按钮和文本

至于在 Scene 视图和 Hierarchy 视图中操作的其他 Unity 系统,Unity UI 系统也是基于游戏对象的。尽管 Unity 中的 UI 菜单下列出了不同的游戏对象,但主要有四个被重复用于制作任何类型的 UI。这些是图像、按钮、文本和面板。大多数其他的,例如滑块和输入字段,主要是这四个与附加脚本的组合。

用户界面图像

Image是 UI 中的非交互式图形元素。图像被其他组件广泛使用以具有视觉表示。Unity 还包含一个原始图像组件,它与常规图像的主要区别在于接受任何类型的纹理,而图像只接受 Sprite 资源。

通常,由于图像的特性,图像的使用频率高于原始图像。值得注意的是Preserve Aspect的特性,它强制显示的图形保持其纵横比,而不管屏幕上的 Image 属性如何,例如它的缩放。

用户界面文本

最初,Unity 有一个Text 组件,用于在屏幕上显示非交互式文本元素。根据您使用的 Unity 版本,它可能仍会显示(即使已弃用或设置为 Legacy)。从 Unity 的 2021 年起,建议的文本替代方案是使用TextMeshPro 组件。

这两个组件的核心功能非常相似,包括更改文本字体、大小、显示选项和颜色。TextMeshPro提供了更多用于显示文本的选项,但它们要求您将字体文件转换为特定的 TextMeshPro 字体资源。

用户界面按钮

按钮是 UI 上的交互式元素,单击时会调用 Unity 事件。通常,Button 有一个 Image 组件来控制 Button 的视觉转换,例如将鼠标悬停在顶部或在单击它们时更改其颜色。

但是,这不是强制性的,可以在没有 Image 组件的情况下将 Button 组件添加到 UI 元素。

向 Unity Button 添加交互非常简单,需要将新事件添加到 Button 组件下的Unity Event列表中。

这听起来可能很傻,但许多开发人员忘记了 Unity Button 可以同时用于多个事件,例如激活脚本和播放声音。您只需要将这些事件添加到 Button 的事件列表中。

用户界面面板

小组实际上更多的是一种组织工具,而不是一个组件本身。更准确地说,面板只是一个具有默认显示设置的图像。我们经常使用面板来打包和组织场景中元素的层次结构,而不是实现任何特定的功能。此外,面板非常适合放置控制脚本,因为它们通常位于其层次结构的头部。

其他 UI 元素

如前所述,还有其他相当重要的 UI 元素,但它们通常用于特定场景,例如Mask组件或Canvas Group。在本文中,我们将重点介绍一般 UI 实现中最常用的组件,但建议学习和练习使用这些其他工具,因为它们可能会派上用场,以加快您的开发过程。

矩形变换和锚点

不同于常规的 3D 游戏对象包含一个Transform组件来存储游戏对象在 3D 空间中的变换,属于 Unity UI 的对象具有一个Rect Transform组件。

Rect Transforms 可以理解为 Transform 的 2D 等效项,并且在缩放和旋转操作中的工作方式类似。

横屏壁纸大全App,免费4K、8K超清汁源,壁纸清晰度都非常高!

Rect Transform 组件控制 UI 元素在 Canvas 上的显示方式,以及它们在布局中的定位和锚定方式。锚点是一组两个 2D 位置,它们确定 Rect 变换将用来显示其内容的参考。

如上图所示,可以通过调整 X 和 Y 的最小值和最大值在组件中访问锚点。这些值是标准化的,即从 0 到 1,其中 0 表示屏幕的 0%,而 1表示屏幕的 100%。一个锚点,它从屏幕的水平 x 100% 和垂直 y 100% 的屏幕出发Min(0,0)并覆盖。Max(1,1)

通过为 Rect Transforms 提供父级,Anchors 不再指代整个屏幕,而是指代其父代的整个区域。例如,如果我的屏幕是 800×600,并且我的锚点与上图相同(Min(0,0)和Max(1,1)),那么我的组件将跨越 800 x 600 个单位。但是,如果它的父对象是水平和垂直占据一半屏幕 (400×300) 的对象,那么这些相同的锚现在将跨越 400 x 300 个单位。

锚预设

锚不一定需要控制其组件的宽度和高度。事实上,它们既可以作为内容区域的指南,也可以仅控制其内容必须对齐的参考点。通过更改 Anchors,Rect Transform 组件将更改其标签以匹配所使用的锚定类型。

为了方便我们的工作,Unity 已经有一系列锚预设,可以节省我们为所有组件手动设置锚的时间。Anchor Presets可以通过单击 Rect Transform 左上角的 Anchor 图像找到。

当最小值和最大值在 x、y 或两者中匹配时,矩形变换上的相应标签将相应更改。让我们讨论通过使用 Anchors 可以找到的三个主要变化:

    • 最小和最大 Anchor 值是相同的:矩形变换控制一个锚定位置、距该锚定位置的偏移量以及内容将在屏幕上跨越的特定宽度和高度。在Anchor Presets中,这些是中心的所有选项

  • Min Anchor 设置为 0,Max Anchor 设置为 1:矩形变换将覆盖给定轴的 100%(拉伸)。如果与前一个结合使用,它将导致锚预设中显示的横向选项之一。如果两者都使用,那么它将导致右下角预设。设置为 Stretch 的轴将具有其对应的值来表示偏移量。设置为不拉伸的轴(当 Min 和 Max 相同时)将表示另一个轴上的偏移量和大小(x 的宽度和 y 的高度)

  • 最小值和最大值的锚点设置为 0 和 1(含)之间的值:如果值设置为不同的值,例如最小值为 0.02,y 为最大值 0.73,则我们有一个自定义预设。使用这些值,矩形变换将从屏幕(或父屏幕)的 2% 开始,一直延伸到屏幕的 73%,超过 71%。注意,使用自定义预设时,始终将其他属性(左、右、上、下)保持为 0。 

结合 Anchors 来设计我们的 UI 布局有很多选择。锚定时最重要的关键点是使用锚点(和预设)来组织元素应该在屏幕上的位置以及它们应该如何随着屏幕尺寸的变化而变化。

此外,请考虑即使您的屏幕尺寸在开发过程中保持不变,您也很可能会移动元素并更改它们的尺寸。为此,正确使用 Anchors 将确保您的操作将保持预期的规则和行为。

此外,考虑到父 Rect Transforms 是一个重要的步骤,它将继承父对象的锚定和定位。如果给定的 Rect Transform 锚定在具有特定宽度和高度的位置,则其子对象会将其作为其区域,并且如果父对象也发生变化,则必然会发生变化。这首先是需要考虑和小心的一点,但后来是朝着效率迈出的一步,因为我们可以简单地从父级继承特定的位置和拉伸,并仅假设它们的本地需求来构建我们的 UI 元素。

为了更好地理解这个过程和构建 UI 的良好实践,现在让我们使用上述组件模拟游戏的主菜单。最初,我们将仅使用基本的 Unity UI 默认资源来设计组件和放置。然后我们将使用免费的 Asset Store 资源来模拟最终的 UI。

基本菜单结构

通常,我在设计菜单时采取的第一步是创建面板,为菜单的主要组件划分屏幕。由于我们正在模拟主菜单,因此我们将有一个左侧面板来保存菜单和游戏徽标。

如前所述,面板只不过是具有默认值的图像。由于我们不需要这些初始面板的图像,因此我经常禁用 Image 组件。我更喜欢禁用它而不是删除它,因为我激活它来检查重叠和视觉伪影。

使用的字体是DaFont的Sono。

作为游戏徽标的占位符,我使用了一个简单的 TextMeshPro 组件。如前所述,此组件需要您生成字体资源。只需在项目视图中右键单击字体文件,然后单击 Create > TextMeshPro > Font Asset 即可完成。

我们为菜单按钮使用组件组合,以提供更好的灵活性和更易于维护。首先,不要将它们直接放在我们创建的左侧面板上,最好为按钮创建另一个面板。

用于保存特定内容的育儿面板

通过使用父面板,我们可以更精确地同时控制所有按钮的区域,而不是一个一个地控制。如果我们需要移动所有按钮,例如,在它们下方(或上方)添加另一个组件,我们可以通过简单地移动它们的父面板来实现。

此外,无论出于何种原因,我们都应该能够在新面板中移动所有组件,而内容仍将遵循面板在屏幕上的显示和拉伸方式。

通常,将您的内容分组到特定面板并根据这些面板分类创建层次结构始终是一个好主意。由于任何更改都是必要的,因此所有父面板都将保留其关于其父母的特定锚点。因此,没有必要对任何内容进行重大更改,因为它会保持锚定。

更改锚点以实现这些操作中的任何一个都至关重要。更改其他组件不一定会在 UI 中的最终分辨率更改或更新中保持相同的行为。为此,我通常建议使用自定义 Anchors,如上所示,以便对所有组件执行相应的所有操作。

自动布局工具

使所有按钮保持一致的另一种技术是使用自动布局。更具体地说,我添加了一个Vertical Layout Group,它会自动按顺序组织子元素,彼此叠加。

通过使用Control Child Size和Child Force Expand的属性,分别控制每个元素的大小以及是否应该完全展开以填充父项,我们可以轻松获得良好的呈现效果。

此外,我们不需要对新添加的按钮应用任何更改。当我们添加一个新按钮时,它将遵循垂直布局组中指定的顺序,并且在视觉上与其他按钮一样。只要有新按钮的空间,这种方法就可以让您轻松地拥有易于扩展的菜单。

到目前为止的层次结构

按照前面的步骤,我们应该有一个类似于上面的层次结构。请注意,随着新内容的添加,会引入新面板来控制它们。此外,在组中没有视觉链接的组件,例如游戏徽标和主菜单,在层次结构中作为兄弟姐妹保存,没有父子关系。

下图显示了当我们将分辨率更改为 4:3 和 8:3 时 UI 的反应。

8:3 分辨率

4:3 分辨率

添加另一个窗口

此类菜单的一个共同特点是弹出其他窗口以显示更多信息和功能。对于我们的示例,让我们看看如何通过使用前面相同的步骤来实现如下所示的设置窗口。

设置窗口面板

由于“设置”窗口在屏幕上占据了自己的空间,因此将其与左侧菜单层次结构分开会更有意义。这样,我们可以根据整个画布设置它的锚点,而不是与左侧菜单相关。我们可以通过创建一个 Panel 作为 Canvas 的子级来做到这一点。

对于 Window 类型的 UI 元素,我经常在 Window 面板中创建一个子 Panel 来保存其内容。如果我决定更改窗口的边框或为其背景和前景使用不同的图形,这通常会很有帮助。

请记住,我们经常用于面板的经验法则是每次我们想要对具有相同意图或功能的对象进行分组时,在层次结构中创建更多面板。在这种特殊情况下,我们要创建一个新面板来保存窗口的所有内容。

在为窗口内不同类型的内容创建面板时,我们应用相同的原则。如前图所示,我们想要某种带有滑块的设置区域和带有按钮的确认区域。

返回自动布局

对于设置区域和确认区域,我们可以再次使用自动布局来快速允许它们根据需要扩展/缩小内容。更准确地说,对于设置区域,我们可以使用另一个垂直布局组来处理我们可能想要的滑块和其他可能的设置功能,而对于确认区域,我们可以使用水平布局组来水平处理按钮。

滑块

这个窗口中我们之前没有讨论过的唯一组件是Slider组件。滑块是一个 Unity 组件,让我们可以使用预定义的范围控制数值。它允许我们选择范围(不受 0 和 1 限制)并添加一个或多个 Unity 事件,这些事件在滑块更改时调用。

滑块是我们之前讨论过的其他元素的组合,例如面板和图像。它也是如何组织层次结构以实现类似结果的一个很好的例子。为了在手柄移动时实现填充效果,Slider 使用了一个有趣的 Image 属性,称为Fill。

要控制其他上下文中的图像填充,您必须将Image Type更改为Filled。这将显示特定的 Fill 属性,例如图像的填充量。虽然在此上下文中,图像的填充由 Slider 组件处理,但您可以在其他上下文中使用它。例如,制作加载栏或健康栏。

不幸的是,Slider 组件没有内置文本,例如Toggle或 Button 组件。在这种情况下,要实现滑块旁边的文本标签,使用的方法是首先创建一个新面板来保存滑块的所有内容(滑块和文本),然后将文本和滑块添加为该面板的子对象。

如果您在此菜单中反复添加新滑块,则将其设为预制件是明智之举。

下图显示了 Slider 的层次结构及其内容。

更换资产

现在我们有了一个基本菜单,很容易替换它的图像和元素来模拟一个合适的游戏 UI。下图显示了如果我们使用免费资源Dark Theme UI、2D Logo Templates和Free Galaxy Background,同样的布局,经过细微调整和添加后的样子。

请注意,所做的主要更改是将 TextMeshPro 替换为图像,因此我可以使用徽标,并在右下角为社交媒体按钮添加一个新面板。

可以对使用相同基础的其他资产完成相同的过程。接下来,您可以使用Extra Clean UI、2D Casual UI HD和KartInnka Buttons Set中的免费资源查看此 UI 的另一个版本。

带有后处理的额外图形

如果您阅读过我的任何其他帖子,您就会知道我非常喜欢使用后期处理效果来提高我的项目的视觉质量。对于这一点,它并没有什么不同。但是,让我们记住,在继续之前需要做出一个重要的特定于 UI 的决定:Canvas Renderer 类型。

如前所述,Canvas 具有特定的渲染方法,该方法会影响 Canvas 是否会受到后期处理的影响。如果您希望元素受到后期处理的影响,请将其 Canvas 设置为呈现模式 Screen Space。否则,使用覆盖。

对于这个项目,我使用了 Bloom、Vignette 和 Color Grading 来提高菜单的视觉凝聚力和质量。下面是两个 UI 之前和之后的图像。

如您所见,后处理效果有助于将颜色融合在一起,从而增强了具有凝聚力的 UI 的感觉。此外,它有助于为闪亮的元素(例如带有 Bloom 效果的游戏徽标)带来额外的风味,并让用户集中注意力(使用 Vignette 使边缘变暗)。

感谢您阅读,如果您想要更多用于快速开发和原型设计的 Unity UI 和 UI 组件策略,请告诉我。

LogRocket主动显示和诊断您的应用程序和网站中最重要的问题

成千上万的工程和产品团队使用LogRocket来减少了解技术和可用性问题的根本原因所需的时间。使用 LogRocket,您将减少与客户来回对话的时间,并消除无休止的故障排除过程。LogRocket 让您可以花更多时间构建新事物,而减少修复错误的时间。

使用 opc ua .net 库在 Unity3d 中创建一个非常简单的 OPC 客户端

【中文标题】使用 opc ua .net 库在 Unity3d 中创建一个非常简单的 OPC 客户端【英文标题】:Create a very simple OPC client in Unity3d with opc ua .net library 【发布时间】:2019-04-06 03:21:09 【问题描述】:

我在尝试使用 Unity3D 在 .Net 中实现一个简单的 OPC 客户端时遇到这些错误。这些错误在 Visual Studio 中:

Severity    Code    Description Project File    Line    Suppression State
Error   CS0012  The type 'X509CertificateValidator' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.ServiceModel.Primitives, Version=4.5.0.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Test    C:\Users\hidethepain\Documents\opc\Assets\main.cs   27  

Severity    Code    Description Project File    Line    Suppression State
Error   CS0012  The type 'Enum' is defined in an assembly that is not referenced. You must add a reference to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.    Test, Test.Player   C:\Users\hidethepain\Documents\opc\Assets\main.cs   18  Active

Severity    Code    Description Project File    Line    Suppression State
Error   CS0012  The type 'Task<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.  Test, Test.Player   C:\Users\hidethepain\Documents\opc\Assets\main.cs   31  Active

还有其他 49 个类似的错误。

Unity3D 向我显示此错误:

Assets/main.cs(67,40): error CS0012: The type `System.Object' is defined in an assembly that is not referenced. Consider adding a reference to assembly `netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'

Unity3D 使用脚本运行时版本 .Net 3.5 我将其更改为 .NET 4.6 以便能够使用 OPC Library。

这是我的代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Opc.Ua; 
using Opc.Ua.Client;
using Opc.Ua.Configuration;

public class main : MonoBehaviour


    private async void Start()
    
        Console.WriteLine("Step 1 - Create a config.");
        var config = new ApplicationConfiguration()
        
            ApplicationName = "test-opc",
            ApplicationType = ApplicationType.Client,
            SecurityConfiguration = new SecurityConfiguration  ApplicationCertificate = new CertificateIdentifier() ,
            TransportConfigurations = new TransportConfigurationCollection(),
            TransportQuotas = new TransportQuotas  OperationTimeout = 15000 ,
            ClientConfiguration = new ClientConfiguration  DefaultSessionTimeout = 60000 
        ;
        await config.Validate(ApplicationType.Client);
        if (config.SecurityConfiguration.AutoAcceptUntrustedCertificates)
        
            config.CertificateValidator.CertificateValidation += (s, e) =>  e.Accept = (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted); ;
        

        Console.WriteLine("Step 2 - Create a session with your server.");
        using (var session = await Session.Create(config, new ConfiguredEndpoint(null, new EndpointDescription("opc.tcp://localhost:4841")), true, "", 60000, null, null))
        
            Console.WriteLine("Step 3 - Browse the server namespace.");
            ReferenceDescriptionCollection refs;
            byte[] cp;
            session.Browse(null, null, ObjectIds.ObjectsFolder, 0u, BrowseDirection.Forward, ReferenceTypeIds.HierarchicalReferences, true, (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, out cp, out refs);
            Console.WriteLine("DisplayName: BrowseName, NodeClass");
            foreach (var rd in refs)
            
                Console.WriteLine(rd.DisplayName + ": " + rd.BrowseName + ", " + rd.NodeClass);
                ReferenceDescriptionCollection nextRefs;
                byte[] nextCp;
                session.Browse(null, null, ExpandedNodeId.ToNodeId(rd.NodeId, session.NamespaceUris), 0u, BrowseDirection.Forward, ReferenceTypeIds.HierarchicalReferences, true, (uint)NodeClass.Variable | (uint)NodeClass.Object | (uint)NodeClass.Method, out nextCp, out nextRefs);
                foreach (var nextRd in nextRefs)
                
                    Console.WriteLine("+ " + nextRd.DisplayName + ": " + nextRd.BrowseName + ", " + nextRd.NodeClass);
                
            

            Console.WriteLine("Step 4 - Create a subscription. Set a faster publishing interval if you wish.");
            var subscription = new Subscription(session.DefaultSubscription)  PublishingInterval = 1000 ;

            Console.WriteLine("Step 5 - Add a list of items you wish to monitor to the subscription.");
            var list = new List<MonitoredItem> 
                new MonitoredItem(subscription.DefaultItem)  DisplayName = "aaatime", StartNodeId = "i=10004"  ;
            list.ForEach(i => i.Notification += OnNotification);
            subscription.AddItems(list);

            Console.WriteLine("Step 6 - Add the subscription to the session.");
            session.AddSubscription(subscription);
            subscription.Create();

            Console.WriteLine("Finished client initialization");
        
    

    private static void OnNotification(MonitoredItem item, MonitoredItemNotificationEventArgs e)
    
        foreach (var value in item.DequeueValues())
        
            Console.WriteLine("0: 1, 2, 3", item.DisplayName, value.Value, value.SourceTimestamp, value.StatusCode);
        
    

代码从.Net Core example 移植到 Unity3d。 我该如何解决这些错误?

【问题讨论】:

【参考方案1】:

GitHub OPC Unified Architecture .NET Standard 的文档说最低要求是 .NET Standard 2.0。 根据 Unity 论坛What .NET stack for UWP/HoloLens? 的说法,只要在 Player Settings 中选择 .NET 作为 Scripting Backend,开发人员就只能使用 .NET Standard 1.4 版。

我没有测试这个特定的库,但我认为当你切换到 IL2CPP 时它应该可以工作,因为它支持 2.0。

从 2018.2 版本开始,Unity 将 .NET 标记为已弃用,因此我认为他们将来不会大力改变这种情况,而是会专注于 IL2CPP。 真可惜,我喜欢 .NET 的短构建周期

【讨论】:

以上是关于在 Unity 中创建简单可靠且干净的 UI的主要内容,如果未能解决你的问题,请参考以下文章

如何在unity3d中创建最简单的按钮

在 Unity 中创建后引用实例化对象

如何在 php 中创建干净的 url 从 mysql 获取数据?

什么是循环销售数据的JSON以在Unity中创建图形的最佳方法?

如何在 Java 中创建临时目录/文件夹?

如何在 Unity 中创建数组的独立副本?