在 Unity 中,Unity 如何神奇地调用所有“接口”?

Posted

技术标签:

【中文标题】在 Unity 中,Unity 如何神奇地调用所有“接口”?【英文标题】:In Unity, how does Unity magically call all "Interfaces"? 【发布时间】:2016-03-27 18:34:52 【问题描述】:

Unity 有一个“接口”:

IPointerDownHandler(doco)

您只需实现OnPointerDown ...

public class Whoa:MonoBehaviour,IPointerDownHandler
    
    public void OnPointerDown (PointerEventData data)
         Debug.Log("whoa!"); 
    

Unity 将在任何此类 MonoBehavior 中“神奇地”调用 OnPointerDown

您不必注册它们、设置事件或做任何其他事情

您在语法上所做的只是将“IPointerDownHandler”和“public void OnPointerDown”添加到一个类中,然后您就可以神奇地获取这些消息。

(如果您不是 Unity 开发人员 - 如果您在游戏运行时突然在编辑器中添加一个,它甚至可以工作!)

他们到底是怎么做到的,我该怎么做?

所以,我想这样做:

public interface IGetNews
 
 void SomeNews(string s);
 

然后我可以将SomeNews 添加到任何 MonoBehavior

替代解决方案很明显,我想具体了解 Unity 是如何实现这种“神奇”行为的。

(顺便说一句:我觉得他们不应该将这些称为“接口”,因为它基本上 根本不像一个接口 - 它有点相反!你可以说他们神奇地创造了一种方式我猜是从多个抽象类继承。)


旁白:

如果您以前没有使用过 Unity,那么执行此操作的常规方法 - 因为我们无法使用 Unity 魔法 - 只需向您的守护程序添加一个 UnityEvent,它将发送相关消息:

public class BlahDaemon:MonoBehaviour
  
  public UnityEvent onBlah;
  
    ...
    onBlah.Invoke();

假设您有想要获取消息的 Aaa、Bbb、Ccc 类。只需连接 Unity 事件(通过在编辑器中或在代码中拖动),例如:

public class Aaa:MonoBehaviour
  
  void Awake()
    
    BlahDaemon b = Object.FindObjectOfType<BlahDaemon>();
    b.onBlah.AddListener(OnBlah);
    
  
  public void OnBlah()
    
    Debug.Log("almost as good as Unity's");
    
  

您基本上是在Awake 中“注册”您的呼叫,您确实在利用 Unity 的神奇用途——不管它是什么。但我想直接使用魔法。

【问题讨论】:

你重新打开这个是为了赏金,但问题是什么? 嘿@Everts!再次感谢您的精彩回答。在这里开始赏金以奖励出色的答案是很常见的。 (如果你浏览我的历史,我经常这样做。)再次干杯芽 【参考方案1】:

对于 XXXUpdate、OnCollisionXXX 和其他 MonoBehaviours,Unity 注册的方式并不是人们普遍认为的反射,而是一些内部编译过程。

如何调用更新

不,Unity 不会在每次需要调用时都使用 System.Reflection 来查找魔法方法。

相反,第一次访问给定类型的 MonoBehaviour 时,会通过脚本运行时检查底层脚本( Mono 或 IL2CPP)是否定义了任何魔术方法,并且 信息被缓存。如果 MonoBehaviour 有一个特定的方法,它是 添加到适当的列表中,例如,如果脚本具有 Update 方法 定义它被添加到需要更新的脚本列表中 每一帧。

在游戏过程中,Unity 只是遍历这些列表并从中执行方法——就这么简单。此外,这就是为什么它并不重要 您的 Update 方法是公开的还是私有的。

http://blogs.unity3d.com/2015/12/23/1k-update-calls/

在接口的情况下,我认为它做的更多,因为接口是必需的。否则,您只需像添加任何其他 MonoBehaviour 方法一样添加该方法。

我的假设(可能是错误的),它在这个 GameObject 上使用了一个基本的 GetComponents。然后迭代生成的数组并调用必须实现的方法,因为它来自接口。

您可以使用以下方法重现该模式:

NewsData data;
if(GetNews(out data))

    IGetNews [] getNews = data.gameObject.GetComponents<IGetNews>();
    foreach(IGetNews ign in getNews) ign.SomeNews(); 

GetNews 是一种检查是否应该向对象发送消息的方法。你可以把它想象成为 RaycastHit 赋值的 Physics.Raycast。如果该对象出于任何正当理由要接收新闻,它会在此处填充数据引用。

【讨论】:

那么您认为他们实际使用的是哪种列表?简单的数组?或列出通用等。 啊……这就是答案。谢谢你。小恶魔们确实在创建组件的层面上做到了这一点。混蛋!为什么我们不能访问它!我希望他们没有使用 c# 中的Interface 语法........因为它确实与接口无关。 (它是接口的“对立面”。) 如果我没记错的话,魔法是通过在编译之前简单地扫描脚本(基本字符串搜索)来完成的,这样编译器就可以用给定方法的实现标记该类。也许作为属性或清单,以便下次创建实例时,该属性会告诉对象正在实现的所有方法。如果您考虑 .NET 中的程序集清单,这并没有什么新鲜的,它已经告诉了您需要了解的有关类的所有信息。 @KyleDelaney - 是的,10000% 正确。这有点道理,因为从根本上讲,Unity / 游戏引擎与面向对象和普通软件无关,它们是基于框架的系统。任何与 Unity 合作的人都已经放弃了对 Unity 的任何逻辑的疑惑 :) 事情只是想知道到底发生了什么 :) Unity 不是 c# 游戏引擎。 Unity 是一个 cpp 游戏引擎,但您使用 c# 编写代码。因此,所有这些方法都由统一的 cpp 脚本运行时调用。不像 system.reflections 等。【参考方案2】:

您可以使用反射来获取实现特定接口的程序集中的所有类型,然后实例化这些类型并通过接口调用这些实例上的方法。

var types = this.GetType().Assembly.GetTypes()
                                   .Where(t=>t.GetInterfaces().Contains(typeof(IGetNews)));
foreach (var type  in types)

    var instance = (IGetNews) Activator.CreateInstance(type);
    instance.SomeNews("news");

【讨论】:

漂亮,谢谢:但是 .. 考虑一下,正如我提到的 "... 如果您在运行时突然添加其中一个,它甚至可以工作,在游戏期间。所以,一旦场景已经在运行,实例化一个新的游戏对象,并添加一个组件,例如上面的 Whoa - 它将从该帧开始工作!” Unity 肯定不会在每个引擎帧都进行反射?你认为这真的只是我们无法做到的令人讨厌的诡计吗?即他们遵循正在创建的组件,还是什么? :O 还是我高估了,确实每帧都进行这样的反思可能微不足道吗?谢谢:/ @fafase 的回答发布的链接表明 Unity 不为此使用程序集扫描 您不需要为每个帧进行反射过程,而是检查 Awake,然后将找到的方法添加到管理器对象上的容器中。我认为在这种情况下使用它会变得更加麻烦且不那么直接。此外,它需要添加一个管理器,这实际上是 Unity 对所有 MonoBehaviour 回调所做的。 HI fafase,不,如果你想模拟统一的功能,你必须每一帧都这样做,就像 Mark 解释的那样使用反射。有问题的组件可以在游戏中的任何时候创建/销毁:/(除非我以某种方式误解了你)

以上是关于在 Unity 中,Unity 如何神奇地调用所有“接口”?的主要内容,如果未能解决你的问题,请参考以下文章

使用 P/Invoke 从 Unity 高效地与非托管代码对话

unity脚本中变量在另一个脚本如何调用

Unity MonoBehavior类部分函数解析

Unity在Android和iOS中如何调用Native API

Unity如何调用Xcode中的方法

Unity在Android和iOS中如何调用Native API