RuntimeInitializeOnLoad 不是 运行 条件编译

RuntimeInitializeOnLoad not running with conditional compilation

概览

我熟悉使用 Platform Dependent Compilation in Unity 更改代码在 editor/standalone/webgl/etc 之间的编译方式。

我 运行 遇到了一个我无法解释的看似微不足道的奇怪案例。


问题

给定一个空的 Unity 5.6.1f1(64 位 windows)项目,其中仅包含以下脚本:

using UnityEngine;

public class TestA
{
#if UNITY_EDITOR
  [UnityEditor.InitializeOnLoadMethod] // Should be ignored for standalone
#else
  [RuntimeInitializeOnLoadMethod]
#endif
  static void OnInit() { Debug.Log("TEST A - This does NOT print in Standalone"); }
}

public class TestB
{
#if UNITY_EDITOR
  [RuntimeInitializeOnLoadMethod] // Should be ignored for standalone
#else
  [RuntimeInitializeOnLoadMethod]
#endif
  static void OnInit() { Debug.Log("TEST B - This DOES print in Standalone"); }
}

public class TestC
{
  [RuntimeInitializeOnLoadMethod]
  static void OnInit()
  {
#if UNITY_EDITOR
    Debug.Log("TEST C - UNITY_EDITOR is defined");
#else
    Debug.Log("TEST C - UNITY_EDITOR is NOT defined");
#endif
  }
}

编译后 运行 在编辑器中我得到了我期望的输出:

然而,当为 Window 独立编译时,TestA 无法打印。下面是 output_log.txt 在 运行 只包含上述脚本的独立构建之后的相关内容:

那么为什么TestATestB有区别呢?为什么 TestA 不在独立编译中打印,但 TestB 打印?

唯一值得注意的区别是 TestA 中的 #if UNITY_EDITOR 定义有 editor-only runtime init attribute,它位于一个命名空间中,甚至不应该在编辑器外部进行编译(如果它不在 #if UNITY_EDITOR).

之内,编译器确实会抱怨

换句话说,在单机编译时,编译时只需要#else结束#endif之间的部分,这两部分完全相同TestATestB


在独立编译时,我假设上面的代码应该与这段代码的行为完全相同:

using UnityEngine;

public class TestA
{
  [RuntimeInitializeOnLoadMethod]
  static void OnInit() { Debug.Log("TEST A - This does NOT print in Standalone"); }
}

public class TestB
{
  [RuntimeInitializeOnLoadMethod]
  static void OnInit() { Debug.Log("TEST B - This DOES print in Standalone"); }
}

public class TestC
{
  [RuntimeInitializeOnLoadMethod]
  static void OnInit() { Debug.Log("TEST C - UNITY_EDITOR is NOT defined"); }
}

但是在编译时我确实看到了 output_log.txt 中的所有三行:

所以一定有所不同。


这是怎么回事?为什么 TestA 不是独立的 运行ning,而 TestB 是?

暂时没有答案,但我想补充一点。我试过你的代码,得到了相同的结果。

此外,我想使用 ILSpy 查看独立构建的编译代码,奇怪的是 ILSpy 就是这样反编译的 TestA class:

using System;
using UnityEngine;

public class TestA
{
    [RuntimeInitializeOnLoadMethod]
    private static void OnInit()
    {
        Debug.Log("TEST A - This does NOT print in Standalone");
    }
}

所以,现在我比你还困惑:编译后的代码是正确的(预处理器工作正常),但它不应该 运行。

我会再考虑一下,但感觉像是一些非常奇怪的错误。

我已经提交了错误报告,我收到的回复暗示它可能会在未来的 Unity 版本中得到修复。

虽然导致问题的确切细节尚不清楚,但这是我们所知道的:

主要问题似乎是 RuntimeInitializeOnLoadMethodAttribute(无论出于何种原因)在 #define 检查中编译时无法按预期运行(因此使用 #if#else#if, 等)当 UNITY_EDITOR 为 false/undefined.

时会计算

例如,即使这样也行不通:

#if !UNITY_EDITOR
public class SomeTest
{
  [RuntimeInitializeOnLoadMethod]
  static void OnInit() { Debug.Log("Does not show up"); }
}
#endif

尽管 class 正在编译,并且该函数存在并且可以从其他 class 中调用(如果它是 public)。

为了使 [RuntimeInitializeOnLoadMethod] 工作,它似乎必须在 #define 检查之外,或者如果在内部,则在两种情况下都必须存在于相同的 fassion 中才能在 UNITY_EDITOR 计算为 false 时工作.


因此,要使原始示例中的 TestA 正常工作(使用 [InitializeOnLoadMethod] 在编辑器中使用 [RuntimeInitializeOnLoadMethod] 并独立使用 [RuntimeInitializeOnLoadMethod]),您可以执行以下操作:

public class TestA
{
#if UNITY_EDITOR
  [UnityEditor.InitializeOnLoadMethod] static void EditorInitWrapper() { OnInit(); }
  [RuntimeInitializeOnLoadMethod] static void RuntimeInitWrapper() { /* Do Nothing */ }
#else
  [RuntimeInitializeOnLoadMethod] static void RuntimeInitWrapper() { OnInit(); }
#endif

  static void OnInit() { Debug.Log("TEST A - This now prints in Standalone"); }
}

包装器在必要时调用 OnInit

需要注意的是[RuntimeInitializeOnLoadMethod]配合/* Do Nothing */命令其实是需要必须有#if#else 部分中的相同名称 才能正常工作。如果两者不匹配,那么 OnInit 将不会在独立构建中被调用。


或者,您可以通过将相同的函数定义移到 #define 检查之外并包装 OnInit() 调用本身来避免必须具有相同的函数定义两次,尽管可以说它的可读性较差:

public class TestA
{
  [RuntimeInitializeOnLoadMethod] static void RuntimeInitWrapper() 
  {
#if !UNITY_EDITOR
    OnInit();
#endif
  }

#if UNITY_EDITOR
  [UnityEditor.InitializeOnLoadMethod]
#endif
  static void OnInit() { Debug.Log("TEST A - This now prints in Standalone"); }
}

这两个都将实现原始示例脚本中 TestA 通常预期的确切行为。