This page has been translated automatically.
视频教程
界面
要领
高级
实用建议
基础
专业(SIM)
UnigineEditor
界面概述
资源工作流程
Version Control
设置和首选项
项目开发
调整节点参数
Setting Up Materials
设置属性
照明
Sandworm
使用编辑器工具执行特定任务
如何擴展編輯器功能
嵌入式节点类型
Nodes
Objects
Effects
Decals
光源
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
编程
基本原理
搭建开发环境
使用范例
C++
UnigineScript
统一的Unigine着色器语言 UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
材质和着色器
Rebuilding the Engine Tools
GUI
双精度坐标
应用程序接口(API)参考
Animations-Related Classes
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
VR-Related Classes
创建内容
内容优化
材质
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials

C#接口和抽象类

Dependency Management in C# Classes
C#类中的依赖管理#

Projects where classes create everything they need by themselves often have common architectural problems, because they handle their own dependencies directly. For example, a class might internally create services like a custom player HUD or input settings. This causes several issues:类自己创建所需的所有内容的项目通常存在常见的体系结构问题,因为它们直接处理自己的依赖关系。 例如,类可能在内部创建服务,如自定义播放器HUD或输入设置。 这会导致几个问题:

  • Tight Coupling: Using specific implementations directly within a class creates tight connections between the class and those implementations. Modifying or replacing these dependencies, such as changing an input handler from keyboard to gamepad, inevitably requires changing the dependent class itself, violating the open-closed principle.紧密耦合:直接在类中使用特定的实现会在类和这些实现之间创建紧密的连接。 修改或替换这些依赖关系,例如将输入处理程序从键盘更改为游戏手柄,不可避免地需要更改依赖类本身,违反了开放-封闭原则。
  • Poor Maintainability: Dependency setup logic often becomes duplicated across the application. If multiple components individually configure the same dependency, it becomes difficult to manage and understand the configurations, and thus harder to maintain.可维护性差:依赖项设置逻辑经常在整个应用程序中重复。 如果多个组件单独配置相同的依赖关系,则很难管理和理解配置,因此更难维护。
  • Difficulty in Unit Testing: Hard-coded dependencies severely limit testability. For example, when a class internally creates a complex dependency like an audio manager, it becomes difficult to replace it with a mock during testing, which makes unit tests less reliable.单元测试的难度:硬编码的依赖关系严重限制了可测试性。 例如,当一个类在内部创建一个像音频管理器这样的复杂依赖项时,在测试过程中很难用模拟替换它,这使得单元测试不太可靠。
  • Reduced Reusability and Modularity: Components tied to certain dependencies become less flexible. This makes it harder to reuse them in different situations. If a component is directly linked to a specific manager or subsystem, you can't easily move it or use it somewhere else.降低了可重用性和模块化:绑定到某些依赖项的组件变得不那么灵活。 这使得在不同情况下重用它们变得更加困难。 如果组件直接链接到特定的管理器或子系统,则无法轻松移动它或在其他地方使用它。

Dependency Injection
依赖注入#

Dependency Injection (DI) is a way to write code that's flexible and easy to manage. Rather than creating or directly referencing its dependencies, a class receives them from outside.Dependency Injection (DI) is a way to write code that's flexible and easy to manage. Rather than creating or directly referencing its dependencies, a class receives them from outside.

Classes depend on an abstraction (an interface or abstract class) rather than a concrete implementation. This approach adheres to the Dependency Inversion Principle (DIP), which states that high-level modules should not depend on low-level modules; both should depend on abstractions. Moreover, abstractions themselves should not depend on details – the details should depend on the abstraction.类依赖于抽象(接口或抽象类)而不是具体的实现。 这种方法遵循依赖倒置原则(DIP),该原则指出高级模块不应该依赖于低级模块;两者都应该依赖于抽象。 此外,抽象本身不应该依赖于细节-细节应该依赖于抽象。

By following DIP, you reduce direct coupling and make it easier to replace components without affecting higher-level logic.通过遵循DIP,您可以减少直接耦合,并使替换组件更容易,而不会影响更高级别的逻辑。

Abstraction diagram

To address issues above and build more flexible, testable, and maintainable systems in UNIGINE, you can use components that implement interfaces or inherit from abstract classes. C# Component System supports this approach and makes it easier to program to abstractions rather than concrete implementations and decouple components from specific dependencies.为了解决上述问题并在UNIGINE中构建更灵活,可测试和可维护的系统,您可以使用实现接口或从抽象类继承的组件C#组件系统支持这种方法,使编程到抽象而不是具体实现更容易,并将组件与特定依赖关系解耦。

Interfaces
接口#

Interfaces are a mechanism for defining contracts that classes can implement. Any class that implements an interface agrees to provide that functionality.接口是定义类可以实现的协定的机制。 任何实现接口的类都同意提供该功能。

Using interfaces for DI is a common pattern to achieve maximum decoupling. The approach to working with interfaces in UNIGINE can be summarized as follows:使用DI接口是实现最大解耦的常见模式。 在UNIGINE中使用接口的方法可以总结如下:

  1. Define an Interface.定义一个接口。

    Identify the behavior or functionality that needs to be provided, and define an interface declaring the required methods. This interface serves as a contract that any specific component must fulfill.识别需要提供的行为或功能,并定义一个声明所需方法的接口。 此接口用作任何特定组件必须履行的合同。

    源代码 (C#)
    public interface IShootable {
    	public void Shoot();
    }
    注意
    You can create an empty *.cs file right in the Asset Browser window.您可以在Asset Browser窗口中创建一个空的*.cs文件。

    Empty .cs file creation

  2. Implement the Interface in a Component.在组件中实现接口。

    Create one or more components that implement this interface. They will contain the actual code to perform the work, but from the outside they will be accessed via the interface. You can have multiple implementations of the same interface coexisting, which is a powerful way to swap behaviors. The component that implements the interface is called a service.创建一个或多个实现此接口的组件。 它们将包含执行工作的实际代码,但从外部将通过接口访问它们。 您可以共存同一接口的多个实现,这是交换行为的强大方法。 实现接口的组件称为服务

    源代码 (C#)
    public class WizardStuff : Component, IShootable
    {
    	public void Shoot()
    	{
    		Log.MessageLine("The wizard stuff shooted with fireball");
    	}
    }
    
    public class MagicWand : Component, IShootable
    {
    	public void Shoot()
    	{
    		Log.MessageLine("The magic wand shooted with electric zap");
    	}
    }
    
    public class Bow : Component, IShootable
    {
    	public void Shoot()
    	{
    	Log.MessageLine("The bow shooted with arrow");
    	}
    }

    Nodes with components

  3. Depend on the Interface in the Client.依赖于客户端中的接口。

    Any component that needs to use the special behavior should not directly instantiate concrete service class. Instead, it should rely on a reference to an interface type. The component that depends on the service is called a client.任何需要使用特殊行为的组件都不应该直接实例化具体的服务类。 相反,它应该依赖于对接口类型的引用。 依赖于服务的组件称为客户端

    源代码 (C#)
    public class Player : Component
    {
    	[ShowInEditor]
    	IShootable mainPlayerWeapon = null;
    
    	[ShowInEditor]
    	IShootable sparePlayerWeapon = null;
    
    	void Update()
    	{
    		if (Input.IsMouseButtonDown(Input.MOUSE_BUTTON.LEFT))
    		{
    			mainPlayerWeapon?.Shoot();
    		}
    
    		if (Input.IsMouseButtonDown(Input.MOUSE_BUTTON.RIGHT))
    		{
    			sparePlayerWeapon?.Shoot();
    		}
    	}
    }
  4. Inject the Concrete Implementation.注入具体实现。

    This step is where the wiring happens. The interface reference in the client must be assigned a concrete object instance that implements the interface. There are a couple of ways to do this using UNIGINE features:这一步是布线发生的地方。 必须为客户端中的接口引用分配一个实现该接口的具体对象实例。 使用UNIGINE功能有几种方法可以做到这一点:

    • Via the Editor. In UNIGINE you can expose the interface field in the component parameters. By default, parameters are displayed or hidden in the UI in accordance with access modifiers: public - displayed, otherwise - hidden. But you can show a private or protected one by specifying the corresponding visibility attribute ShowInEditor as shown above in the code sample.通过编辑器。在UNIGINE中,您可以在组件参数中公开接口字段。 默认情况下,参数根据访问修饰符在UI中显示或隐藏:public-displayed,otherly-hidden。 但是,您可以通过指定相应的可见性属性ShowInEditor来显示私有或受保护的属性,如代码示例中所示。

      Then, you simply attach a node with an attached component that implements your specific interface.然后,您只需附加一个带有附加组件的节点,该组件实现您的特定接口。

      Using interfaces in Editor

    • Via the API. Or you can locate the interface using the engine API.通过API。或者您可以使用引擎API定位接口。

      源代码 (C#)
      // Getting IShootable
      var newWeapon = obj.GetComponent<IShootable>();
      // If newWeapon is not null, it will be new main player weapon
      mainPlayerWeapon = newWeapon ?? mainPlayerWeapon;

      A common pattern is calling GetComponent<>() method in the client's code. The engine will search for a component that implements the IShootable interface, as in our example, and return a reference to it.一个常见的模式是在客户端的代码中调用GetComponent<>()方法。 引擎将搜索实现IShootable接口的组件,如我们的示例中所示,并返回对它的引用。

      The key point is that the client code does not instantiate the service directly - instead, it receives a reference or obtains it from the environment.关键点在于客户端代码不直接实例化服务-相反,它接收引用或从环境中获取它。

Abstract Classes
抽象类#

Interfaces aren't the only way to invert dependencies – abstract classes can also serve as the abstraction layer between a client and the concrete implementation. The workflow for using abstract classes is very similar to the interface approach, with a few differences:接口不是反转依赖关系的唯一方法–抽象类也可以作为客户端和具体实现之间的抽象层。 使用抽象类的工作流程与接口方法非常相似,但有一些区别:

  1. Define an Abstract Base Class.定义一个抽象基类。

    Create an abstract class that declares the necessary methods and optionally provides common functionality or default behavior shared across implementations. Since it's abstract, it can't be instantiated directly - instead, it serves as a template that concrete subclass components must follow.创建一个抽象类,该类声明必要的方法,并可选地提供跨实现共享的通用功能或默认行为。 由于它是抽象的,它不能直接实例化-相反,它作为具体子类组件必须遵循的模板。

    源代码 (C#)
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    [Component(PropertyGuid = "AUTOGENERATED_GUID")]
    public abstract class Toggleable : Component
    {
    	[ShowInEditor]
    	private bool isToggled = false;
    
    	public bool Toggled
    	{
    		get => isToggled;
    		set
    		{
    			if (value != isToggled)
    			{
    				bool ok = value ? On() : Off();
    				isToggled = isToggled ^ ok;
    			}
    		}
    	}
    
    	public bool Toggle() => isToggled = isToggled ^ (isToggled ? Off() : On());
    
    	protected abstract bool On();
    	protected abstract bool Off();
    }
  2. Create Subclasses that Inherit from the Abstract Class.创建继承自抽象类的子类。

    Implement one or more classes that extend the abstract base class. Each subclass must implement the abstract methods, providing its own behavior.实现一个或多个扩展抽象基类的类。 每个子类必须实现抽象方法,提供自己的行为。

    源代码 (C#)
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    [Component(PropertyGuid = "AUTOGENERATED_GUID")]
    public class Lamp : Toggleable
    {
    	[ParameterColor]
    	public vec4 emission_color = vec4.WHITE;
    
    	protected override bool On()
    	{
    		Log.MessageLine("Lamp::On()");
    		return SetEmissionColor(emission_color);
    	}
    
    	protected override bool Off()
    	{
    		Log.MessageLine("Lamp::Off()");
    		return SetEmissionColor(vec4.ZERO);
    	}
    
    	private bool SetEmissionColor(vec4 emission_color)
    	{
    		Object obj = (Object)node;
    		if (obj == null)
    			return false;
    
    		for (var surface = 0; surface < obj.NumSurfaces; surface += 1)
    			obj.SetMaterialParameterFloat4("emission_color", emission_color, surface);
    
    		return true;
    	}
    
    	private void Init()
    	{
    		SetEmissionColor(Toggled ? emission_color : vec4.ZERO);
    	}
    }
    源代码 (C#)
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    [Component(PropertyGuid = "AUTOGENERATED_GUID")]
    public class Fan : Toggleable
    {
    	public float rotation_speed = 120;
    	private float target_speed = 0;
    	private float actual_speed = 0;
    
    	protected override bool On()
    	{
    		Log.MessageLine("Fan::On()");
    		target_speed = rotation_speed;
    		return true;
    	}
    
    	protected override bool Off()
    	{
    		Log.MessageLine("Fan::Off()");
    		target_speed = 0;
    		return true;
    	}
    
    	private void Init()
    	{
    		target_speed = Toggled ? rotation_speed : 0;
    	}
    
    	private void Update()
    	{
    		actual_speed = MathLib.Lerp(actual_speed, target_speed, Game.IFps);
    		node.Rotate(0, 0, actual_speed * Game.IFps);
    	}
    }

    Nodes with abstract components

  3. Inject a Concrete Subclass Instance into the Client Class.将一个具体的子类实例注入到客户端类中。

    Similar to the interface case, the client that needs the functionality holds a reference of the abstract base class type. Then, because of polymorphism, you can pass specific implementations of the abstract class to the client class in two following ways:与接口情况类似,需要该功能的客户端持有抽象基类类型的引用。 然后,由于多态性,您可以通过以下两种方式将抽象类的特定实现传递给客户端类:

    • Via the Editor. Simply attach a node with the attached component, that inherits your abstract base class in a node Parameters window: 通过编辑器。只需附加一个带有附加组件的节点,该组件在节点Parameters窗口中继承您的抽象基类:

      Using abstract classes in Editor

    • Via the API. Retrieving abstract base classes via the API works in a similar way as interfaces.通过API。通过API检索抽象基类的工作方式与接口类似。

      源代码 (C#)
      public class Toggler : Component
      {
      	private void Update()
      	{
      		// Some logic for intersection with togglable object...
      		if (obj)
      		{
      			var toggleable = obj.GetComponent<Toggleable>();
      			if (toggleable)
      			{
      				toggleable.Toggle();
      			}
      		}
      	}
      }

      In this case, the client code requests a component that derives from an abstract class. The engine traverses the available components and returns one that matches the expected base type, such as Toggleable.在这种情况下,客户端代码请求从抽象类派生的组件。 引擎遍历可用组件并返回与预期基类型匹配的组件,例如Toggleable

Choosing the Right Abstraction Mechanism
选择正确的抽象机制#

Interfaces and abstract classes both define behaviour without binding to concrete implementations, but they differ in intent and capabilities:接口和抽象类都定义行为而不绑定到具体实现,但它们的意图和功能不同:

  • Interfaces are ideal for defining behaviour that can be implemented by any class, regardless of its place in the hierarchy. They also support multiple inheritance by allowing a class to implement multiple interfaces.接口是定义可以由任何类实现的行为的理想选择,无论其在层次结构中的位置如何。 它们还通过允许一个类实现多个接口来支持多重继承。
  • Abstract classes are more appropriate when there is a need to share common implementation details, such as fields or methods across a group of related types.当需要跨一组相关类型共享公共实现细节(例如字段或方法)时,抽象类更合适。

It's important to use abstractions when they bring clear benefits - such as easier testing, multiple implementations, or shared logic. But if there's only one concrete use case and no need for reuse or substitution, keeping things simple is often the better choice.当抽象带来明显的好处时,使用抽象非常重要-例如更容易测试,多个实现或共享逻辑。 但是,如果只有一个具体的用例,并且不需要重用或替换,那么保持简单通常是更好的选择。

Using External Dependency Injection Frameworks
使用外部依赖注入框架#

You can configure dependencies manually within the engine - using the editor and components to wire things together. This method gives full control over how and when dependencies are resolved. Alternatively, external Dependency Injection frameworks can be used. They allow you to:您可以在引擎中手动配置依赖关系-使用编辑器和组件将事物连接在一起。 此方法可以完全控制如何以及何时解决依赖关系。 或者,可以使用外部Dependency Injection框架。 他们允许你:

  • Use familiar tools from the .NET ecosystem.使用.NET生态系统中熟悉的工具。
  • Simplify dependency management in larger projects.简化大型项目中的依赖管理。
  • Access advanced DI features like scopes, signals, or event-based injection.访问高级DI功能,如范围、信号或基于事件的注入。

Below are examples of how to set up some popular external DI frameworks.下面是如何设置一些流行的外部DI框架的例子。

Setting up Zenject in a UNIGINE Project
在UNIGINE项目中设置Zenject#

  1. Download the non-Unity build from the official Zenject GitHub repository从官方的Zenject GitHub库下载非统一构建
  2. Extract the downloaded archived *.dll files into your project's bin folder将下载的存档*.dll文件解压缩到项目的bin文件夹中
  3. Add references to the *.dll files in your project:添加对项目中*.dll文件的引用:

    • Option 1: Using Your IDE选项1:使用IDE

      1. Open your project in your preferred IDE.在您首选的IDE中打开您的项目。
      2. Open the Reference Manager or the equivalent interface for managing project references.打开引用管理器或用于管理项目引用的等效接口。
      3. Use the Browse option to locate the required DLLs in the project's bin folder.使用Browse选项在项目的bin文件夹中找到所需的DLLs
      4. Select previously added files.选择以前添加的文件。
      5. Confirm the selection to add them as references in your project.确认选择以将它们添加为项目中的引用。
    • Option 2: Manually edit the *.csproj file选项2:手动编辑*.csproj文件

      Open your project *.csproj file and add:打开项目*.csproj文件并添加:

      源代码 (XML)
      <ItemGroup>
      	<Reference Include="Zenject">
      		<HintPath>bin\Zenject.dll</HintPath>
      	</Reference>
      	<Reference Include="Zenject-Signals">
      		<HintPath>bin\Zenject-Signals.dll</HintPath>
      	</Reference>
      	<Reference Include="Zenject-usage">
      		<HintPath>bin\Zenject-usage.dll</HintPath>
      	</Reference>
      </ItemGroup>
  4. Restart your IDE (and Editor, if needed) to ensure the changes are recognized.重新启动您的IDE(以及编辑器,如果需要),以确保识别更改。
  5. You're now ready to use the Zenject in your project. For usage details, refer to the Zenject documentation.您现在可以在项目中使用Zenject了。 有关使用详情,请参阅Zenject文档。

    源代码 (C#)
    public interface ISomeInterface
    {
    	void Send(string message);
    }
    
    
    public class SomeImplementation : Component, ISomeInterface
    {
    	public void Send (string message)
    	{
    		Log.MessageLine(message);
    	}
    }
    源代码 (C#)
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    using Zenject;
    [Component(PropertyGuid = "AUTOGENERATED_GUID")]
    public class ClientCode : Component
    {
    	void Init()
    	{
    		var container = new DiContainer();
    
    		container.Bind<ISomeInterface>().To<SomeImplementation>().AsSingle();
    
    		var foo = container.Resolve<ISomeInterface>();
    
    		foo.Send("Hello, Zenject!");
    	}
    }

Setting up Microsoft DI in a UNIGINE Project
在UNIGINE项目中设置Microsoft DI#

  1. Open your project in your preferred IDE.在首选IDE中打开项目。
  2. Install the NuGet package for Microsoft.Extensions.DependencyInjection:Microsoft.Extensions.DependencyInjection安装NuGet包:

    • Via the built-in IDE NuGet Package Manager, or通过内置的IDE0_IGT,或
    • Using the .NET CLI command:使用.NET CLI命令:

      源代码
      dotnet add package Microsoft.Extensions.DependencyInjection
  3. That's it - Microsoft's DI framework is now available in your project. You can build more complex setups using scopes, lifetimes, and modules - see the official docs for details.就是这样-Microsoft's DI框架现在在您的项目中可用。 您可以使用范围、生命周期和模块构建更复杂的设置-有关详细信息,请参阅官方文档

    源代码 (C#)
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    using Microsoft.Extensions.DependencyInjection;
    
    [Component(PropertyGuid = "AUTOGENERATED_GUID")]
    
    public class ClientCode : Component
    {
    	void Init()
    	{
    		var services = new ServiceCollection();
    
    		services.AddTransient<ISomeInterface, SomeImplementation>();
    
    		var serviceProvider = services.BuildServiceProvider();
    
    		var foo = serviceProvider.GetRequiredService<ISomeInterface>();
    
    		foo.Send("Hello, .NET DI!");
    	}
    }

本页面上的信息适用于 UNIGINE 2.20 SDK.

最新更新: 2025-04-16
Build: ()