Unity引擎源码解析(伪) - 1 引擎初始化

Windows:

我们先看WindowsPlayer平台,在Unity编辑器中将应用发布到Windows后的APP的启动:

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
    return UnityMain(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
}
int UnityMain(HINSTANCE hInst, HINSTANCE hPrev, LPWSTR szCmdLine, int nCmdShow)
{
    int returnCode = UnityMainImpl(hInst, hPrev, szCmdLine, nCmdShow);
    TerminateProcess(GetCurrentProcess(), returnCode);
    return returnCode;
}
int UnityMainImpl(HINSTANCE hInst, HINSTANCE hPrev, LPWSTR szCmdLine, int nCmdShow);
略...

wWinMain() 函数,很明显就是winAPI下的窗口应用程序的标准入口,其中的参数就不解释了,请自行查看微软的文档。其他平台自然有各自系统的入口。

Android:

在unity下的安卓平台主入口由一个java文件启动 UnityPlayerActivity.java

@Override protected void onCreate(Bundle savedInstanceState)
{
    ...
    mUnityPlayer = new UnityPlayer(this, this);
    setContentView(mUnityPlayer);
    mUnityPlayer.requestFocus();
    ...
}

UnityPlayer 这个java类在构造函数中有

public class UnityPlayer extends FrameLayout implements IUnityPlayerServices, IUnityPlayerLifecycleEvents
{
    ...
    public UnityPlayer(Context context, IUnityPlayerLifecycleEvents lifecycleEventListener)
    {
        super(context);
        ...
        initJni(context);
        ...
    }
    ...
    //原生态方法,对应的实现不在当前文件下,而是在其他语言(如C和C++)实现的文件中。
    private final native void initJni (Context context);
    private final native boolean nativeRender ();
    private final native void nativeSetInputArea (int x, int y, int width, int height);
    private final native void nativeSetKeyboardIsVisible (boolean isVisible);
    private final native void nativeSetInputString (String inputString);
    private final native void nativeSetInputSelection (int start, int length);
    private final native void nativeSoftInputCanceled();
    private final native void nativeSoftInputLostFocus();
    private final native void nativeReportKeyboardConfigChanged();
    private final native boolean nativePause();
    private final native void nativeResume();
    private final native void nativeLowMemory();
    private final native void nativeApplicationUnload();
    private final native void nativeFocusChanged(boolean hasFocus);
    private final native void nativeRecreateGfxState(int index, Surface surface);
    private final native void nativeSendSurfaceChangedEvent();
    private final native boolean nativeDone ();
    private final native void nativeSoftInputClosed ();
    private final native boolean nativeInjectEvent(InputEvent event);
    private final native boolean nativeIsAutorotationOn();
    private final native void nativeMuteMasterAudio(boolean muteAudio);
    private final native void nativeRestartActivityIndicator();
    private final native void nativeSetLaunchURL(String url);
    private final native void nativeOrientationChanged (int naturalOrientation, int angle);
    ...
}

可以看到APP中的各种生命周期函数最终会调到C++中:

DECLARE_JNI_METHODS(UnityPlayer, "com/unity3d/player/UnityPlayer",
{
    JNI_METHOD(initJni, "(Landroid/content/Context;)V"),
    JNI_METHOD(nativeDone, "()Z"),
    JNI_METHOD(nativePause, "()Z"),
    JNI_METHOD(nativeRecreateGfxState, "(ILandroid/view/Surface;)V"),
    JNI_METHOD(nativeSendSurfaceChangedEvent, "()V"),
    JNI_METHOD(nativeRender, "()Z"),
    JNI_METHOD(nativeResume, "()V"),
    JNI_METHOD(nativeLowMemory, "()V"),
    JNI_METHOD(nativeApplicationUnload, "()V"),
    JNI_METHOD(nativeFocusChanged, "(Z)V"),
    JNI_METHOD(nativeSetInputArea, "(IIII)V"),
    JNI_METHOD(nativeSetKeyboardIsVisible, "(Z)V"),
    JNI_METHOD(nativeSetInputString, "(Ljava/lang/String;)V"),
    JNI_METHOD(nativeSetInputSelection, "(II)V"),
    JNI_METHOD(nativeSoftInputClosed, "()V"),
    JNI_METHOD(nativeSoftInputCanceled, "()V"),
    JNI_METHOD(nativeReportKeyboardConfigChanged, "()V"),
    JNI_METHOD(nativeSoftInputLostFocus, "()V"),
    JNI_METHOD(nativeInjectEvent, "(Landroid/view/InputEvent;)Z"),
    JNI_METHOD(nativeUnitySendMessage, "(Ljava/lang/String;Ljava/lang/String;[B)V"),
    JNI_METHOD(nativeIsAutorotationOn, "()Z"),
    JNI_METHOD(nativeMuteMasterAudio, "(Z)V"),
    JNI_METHOD(nativeRestartActivityIndicator, "()V"),
    JNI_METHOD(nativeSetLaunchURL, "(Ljava/lang/String;)V"),
    JNI_METHOD(nativeOrientationChanged, "(II)V"),
});

...

/* Call to render the next GL frame */
bool nativeRender(JNIEnv* env, jobject thiz)
{
    JNI_METHOD_IMPL(bool);
    return UnityPlayerLoop();
}

bool UnityPlayerLoop()
{
    ...
    if (!m_Initialized)
    {
        UnityInitApplication();
        return true;
    }
    ...
    PlayerLoop(); //全平台通用主循环,每次循环执行一次
    ...
}

最主要的其实这个在C++中的 nativeRender() 函数,初始化和主循环都在 UnityPlayerLoop()函数中,里面更多细节就不展开讲了,还是回到windows下面的初始化流程吧。

Window平台详细流程:

int UnityMainImpl(HINSTANCE hInst, HINSTANCE hPrev, LPWSTR szCmdLine, int nCmdShow)
{
    //为崩溃设置配置
    //设置命令行参数
    //启动选项配置
    //解析命令行参数执行,可更改子系统的默认行为
    ...
    const bool batchMode = IsBatchmode(); //batchmode 模式下只有基本逻辑,没有画面显示,没有输入等
    BatchModeWindow *batchModeWindow = NULL;
    ...
    LoadScriptingRuntime(dataFolder, crashHandler); //初始化 mono 和 il2cpp
    if (!PlayerInitEngineNoGraphics(dataFolder, dataFolder)) //初始化引擎各类管理器
        winutils::DisplayErrorMessagesAndQuit("Failed to initialize player");
        
    //内部注册HINSTANCE实例,标准写法 winAPI的 RegisterClassExW()
    winutils::RegisterWindowClass(kWindowClassName, PlayerMainWndProc, CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS);
    if (batchMode)
    {
        ...
    }
    else
    {
        InitializeMainWindow(); //创建主窗口对象,获得 HWND gMainWindow; 也是winAPI的通用写法
    }
    if (!PlayerInitEngineGraphics()) //初始化 图形引擎
        winutils::DisplayErrorMessagesAndQuit("Failed to initialize player");
    Assert(IsGfxDevice());
    ...
    if (batchMode || !GetShouldShowSplashScreen())
    {
        ...
    }
    else
    {
        //显示启动屏幕并异步加载第一个场景
        g_FirstSceneLoadingOperation = PlayerLoadFirstScene(true);
        if (g_FirstSceneLoadingOperation)
        {
            g_FirstSceneLoadingOperation->SetAllowSceneActivation(false);
            g_ShowingSplashScreen = true;
            BeginSplashScreen(); //unity 程序刚打开时的画面就这儿开启的
        }
        else
        {
            g_ShowingSplashScreen = false;
        }
    }
    ...
    if (batchMode)
    {
        ...
    }
    else
    {
        ...
        ShowWindow(gMainWindow, nCmdShow); //winApi 正式显示主窗口 
        exitCode = MainMessageLoop(); //主逻辑大循环,只要不关窗口,就一直卡在这里
    }

    //当应用程序结束时,后面的代码才会执行 ↓↓↓
    PlayerPrefs::Sync();

#if SUPPORT_MULTIPLE_DISPLAYS
    extern void UnityDisplayManager_Terminate();
    UnityDisplayManager_Terminate();
#endif
    winutils::UninstallInternalCrashHandler();
    // exit, always unlock cursor and show it!
    ::ClipCursor(NULL);
    ::ShowCursor(TRUE);
    Shutdown();
#if ENABLE_MONO
    UnloadMono();
#endif

    return exitCode;
}

由此windows下的初始化流程基本如此,现在做一个总结:

1、初始化引擎配置。

2、初始化mono环境。

3、初始化引擎各类管理器。

4、初始化图形引擎。

5、加载初始场景并显示,开启大循环。

一些比较重要的模块初始化:

一、加载脚本运行环境

void LoadScriptingRuntime(const core::string& dataFolder, winutils::CrashHandler& crashHandler)
{
//mono脚本后处理
#if ENABLE_MONO
    ...
    bool monook = LoadAndInitializeMono(monoPaths, monoConfigPath, dataPath, monoDllPath, GetArgc(), GetArgv());
    if (!monook)
        winutils::DisplayErrorMessagesAndQuit("Failed to load mono");
    crashHandler.SetMonoDllPath(monoDllPath.c_str());
#endif
//IL2CPP后处理
#if ENABLE_IL2CPP
    if (!LoadIl2Cpp("GameAssembly.dll"))
        winutils::DisplayErrorMessagesAndQuit("Failed to load il2cpp");
    core::string il2cppDataPath = AppendPathName(dataFolder, "il2cpp_data");
    bool il2cppInitialized = InitializeIl2CppFromMain(AppendPathName(il2cppDataPath, "etc"), il2cppDataPath, GetArgc(), GetArgv(), false);
    if (!il2cppInitialized)
        winutils::DisplayErrorMessagesAndQuit("Failed to initialize IL2CPP");
#endif
}

C#脚本运行时,分为了monoil2cpp,这里就是Unity编辑器中下拉框中的那两个选项。

二、初始化引擎各类管理器

初始化逻辑模块,只挑一个重点讲,即 InitPlayerLoopCallbacks() 见名知意,就是大循环中的各分类回调注册。

unity引擎内部其实将大循环分为了7个先后顺序,分别是:Initialization、EarlyUpdate、FixedUpdate、PreUpdate、Update、PreLateUpdate、PostLateUpdate

想想unity脚本中三个重要的循环方法 Update()、FixedUpdate()、LateUpdate() 是不是好像对上了,没错 Update()在Update部分,FixedUpdate()在FixedUpdate部分,LateUpdate()在PreLateUpdate部分,另外说一句协程的处理其实也在 Update部分,只不过比Update()晚注册一步。

void InitPlayerLoopCallbacks()
{
    REGISTER_PLAYERLOOP_CALL(Initialization, PlayerUpdateTime,
    {
        UpdateTime(); //第一个就是更新时间步长,内部会根据 Application.targetFrameRate 动态调整大循环频率,进行短暂的停止
    });
    REGISTER_PLAYERLOOP_CALL(Initialization, AsyncUploadTimeSlicedUpdate,
    {
        GetAsyncUploadManager().TimeSlicedUpdate();
    });
    
    #if UNITY_EDITOR
    REGISTER_PLAYERLOOP_CALL(EarlyUpdate, UpdateInputManager,
    {
        extern bool RemoteIsConnected();
        extern bool RemoteShouldOverrideLocalJoystickInput();
        if (RemoteIsConnected() && RemoteShouldOverrideLocalJoystickInput())
            return;

        if (::IsWorldPlayingThisFrame())
            GetInputManager().ProcessInput();
    });
    #else
    REGISTER_PLAYERLOOP_CALL(EarlyUpdate, UpdateInputManager,
    {
        if (::IsWorldPlayingThisFrame())
            GetInputManager().ProcessInput(); //处理输入
    });
    #endif
    
    REGISTER_PLAYERLOOP_CALL(FixedUpdate, ScriptRunBehaviourFixedUpdate,
    {
        if (::IsWorldPlayingThisFrame())
        {
            GetFixedBehaviourManager().Update(); //处理当前收集到的所有MonoBehaviour的 FixedUpdate()
        }
    });
    REGISTER_PLAYERLOOP_CALL(FixedUpdate, ScriptRunDelayedFixedFrameRate,
    {
        if (::IsWorldPlayingThisFrame())
        {
            GetDelayedCallManager().Update(DelayedCallManager::kRunFixedFrameRate); //延迟调用管理,协程会在里面注册消息
        }
    });
    
    static bool s_oldTextFocus = false;
    REGISTER_PLAYERLOOP_CALL(PreUpdate, CheckTexFieldInput,
    {
        s_oldTextFocus = GetInputManager().GetTextFieldInput();
    });
    
    REGISTER_PLAYERLOOP_CALL(Update, ScriptRunBehaviourUpdate,
    {
        GetBehaviourManager().Update(); //处理当前收集到的所有MonoBehaviour的 Update()
    });
    REGISTER_PLAYERLOOP_CALL(Update, ScriptRunDelayedDynamicFrameRate,
    {
        GetDelayedCallManager().Update(DelayedCallManager::kRunDynamicFrameRate); //延迟调用管理,协程会在里面注册消息
    });
    
    REGISTER_PLAYERLOOP_CALL(PreLateUpdate, ScriptRunBehaviourLateUpdate,
    {
        GetLateBehaviourManager().Update(); //处理当前收集到的所有MonoBehaviour的 LateUpdate()
    });
    
    REGISTER_PLAYERLOOP_CALL(PostLateUpdate, PlayerUpdateCanvases,
    {
        PlayerUpdateCanvases();
    });
    REGISTER_PLAYERLOOP_CALL(PostLateUpdate, UpdateAllRenderers,
    {
        RenderManager::UpdateAllRenderers();
    });
}

以上只是选取了几个具有代表性的模块循环。注释都写的十分清晰了。

三、MainMessageLoop() 是开启大循环逻辑

static int MainMessageLoop()
{
    bool gotMsg;
    MSG msg;
    msg.message = WM_NULL;
    PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
    HANDLE hevent = (HANDLE)CreateEvent(NULL, FALSE, FALSE, NULL);
    timeBeginPeriod(1); //winAPI 设置计时器的最低分辨率为 1毫秒
    while (msg.message != WM_QUIT) //只要没收到退出消息,就一直循环
    {
        bool dontWaitForMessages = gAppActive || (!gAlreadyClosing && GetPlayerShouldRunInBackground()) || (kPlayerPausing == GetPlayerPause());
        if (dontWaitForMessages)
            gotMsg = (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) != 0);
        else
            gotMsg = (GetMessage(&msg, NULL, 0U, 0U) != 0);

        if (gotMsg)
        {
            //有窗口消息,就优先处理
            TranslateAndDispatchFilteredMessage(msg);
        }
        else
        {
            if (s_TitleBarUpdateTimer != 0)
            {
                BOOL result = KillTimer(NULL, s_TitleBarUpdateTimer);
                AssertFormatMsg(result, "Failed to kill TitleBar update timer, id = %d, result = %d.", s_TitleBarUpdateTimer, result);
                s_TitleBarUpdateTimer = 0;
            }
            //执行主循环
            PerformMainLoop();
        }            
    }
    timeEndPeriod(1); //winAPI 设置计时器的最低分辨率为 默认值
    CloseHandle(hevent);
    return (INT)msg.wParam;
}
//执行的主循环
static void PerformMainLoop()
{
    ...
    if (!g_ShowingSplashScreen)
    {
        // Normal game loop
        if (kPlayerPaused == GetPlayerPause())
        {
            INVOKE_GLOBAL_CALLBACK(whilePaused);
            return;
        }
        if (kPlayerPausing == GetPlayerPause())
        {
            SetPlayerPause(kPlayerPaused);
        }
        InputProcess();
        PlayerLoop(); //全平台通用主循环,每次循环执行一次。这里就跟上面安卓的循环逻辑合一了
        InputPostprocess();
    }
    else
    {
        //unity启动场景的逻辑
    }
    ...
}

那么 PlayerLoop() 内部主要就是各个循环系统的初始化和执行了

void PlayerLoop()
{
    ...
    if (s_defaultLoop.empty())
        InitDefaultPlayerLoop();
        
    ExecutePlayerLoop(s_currentLoop);
    ...
}
//初始化循环系统,static dynamic_array<NativePlayerLoopSystem> s_defaultLoop; 就这个变量了
static void InitDefaultPlayerLoop()
{
    PROFILER_AUTO(gInitDefaultPlayerLoop);
    s_defaultLoop.resize_initialized(1 + (PlayerLoopCallbacks::PLAYER_LOOP_Initialization_COUNT + 1) +
        (PlayerLoopCallbacks::PLAYER_LOOP_EarlyUpdate_COUNT + 1) + (PlayerLoopCallbacks::PLAYER_LOOP_FixedUpdate_COUNT + 1) +
        (PlayerLoopCallbacks::PLAYER_LOOP_PreUpdate_COUNT + 1) + (PlayerLoopCallbacks::PLAYER_LOOP_Update_COUNT + 1) +
        (PlayerLoopCallbacks::PLAYER_LOOP_PreLateUpdate_COUNT + 1) + (PlayerLoopCallbacks::PLAYER_LOOP_PostLateUpdate_COUNT + 1));
    UpdateDefaultPlayerLoop();
    s_currentLoop = s_defaultLoop.data();
    // TODO: should this have a special "stop player loop" callback instead?
#if USE_MONO_DOMAINS
    GlobalCallbacks::Get().beforeDomainUnload.Register(ResetDefaultPlayerLoop);
#else
    GlobalCallbacks::Get().playerQuit.Register(ResetDefaultPlayerLoop);
#endif
}
static void UpdateDefaultPlayerLoop()
{
    //Initialization
    int currentGroupIndex = 1;
    s_defaultLoop[0].numSubSystems = 1;
    s_defaultLoop[currentGroupIndex].type = GetCoreScriptingClasses().initialization;
    s_defaultLoop[currentGroupIndex].numSubSystems = 0;
    DebugAssert(PlayerLoopCallbacks::PLAYER_LOOP_Initialization_COUNT == PLAYER_LOOP_ARRAY_LEN(gPlayerLoopCallbacks.InitializationTypes));
    DebugAssert(PlayerLoopCallbacks::PLAYER_LOOP_Initialization_COUNT == PLAYER_LOOP_ARRAY_LEN(gPlayerLoopCallbacks.RegisteredInitializationFunctions));
    for (int i = 0; i < PlayerLoopCallbacks::PLAYER_LOOP_Initialization_COUNT; ++i)
    {
        s_defaultLoop[currentGroupIndex + i + 1].type = gPlayerLoopCallbacks.InitializationTypes[i];
        s_defaultLoop[currentGroupIndex + i + 1].updateFunction = gPlayerLoopCallbacks.RegisteredInitializationFunctions + i;
        ++s_defaultLoop[0].numSubSystems;
        ++s_defaultLoop[currentGroupIndex].numSubSystems;
    }
    currentGroupIndex += PlayerLoopCallbacks::PLAYER_LOOP_Initialization_COUNT + 1;
    //EarlyUpdate
    ...
    //FixedUpdate
    ...
    //PreUpdate
    ...
    //Update
    ...
    //PreLateUpdate
    ...
    //PostLateUpdate
    ...
}

到此引擎的基本初始逻辑脉络已经很清晰了,至于循环逻辑暂略过。

MonoBehaviour 的初始化生命周期:

C#脚本中的MonoBehaviour其实在引擎的C++中也有定义,他们是配套实例化的。如

class MonoBehaviour : public Behaviour, public IManagedObjectHost {}

class EXPORT_COREMODULE Behaviour : public Unity::Component {}

当场景中加载一个C#脚本时,会触发MonoBehaviour中的 void AwakeFromLoad(AwakeFromLoadMode awakeMode) 函数

void MonoBehaviour::AwakeFromLoad(AwakeFromLoadMode awakeMode)
{
    GameObject* const gameObject = GetGameObjectPtr();
    if (gameObject)
    {
        gameObject->SetSupportedMessagesDirty();
    }
    ...
    bool willCallAddToManager = ShouldRunBehaviour() && GetEnabled() && IsActive();
    if (willCallAddToManager)
    {
        Super::AwakeFromLoad(awakeMode); //接下方
        return;
    }
    ...
}
void Behaviour::AwakeFromLoad(AwakeFromLoadMode awakeMode)
{
    Super::AwakeFromLoad(awakeMode);
    UpdateEnabledState(IsActive());
}
void Behaviour::UpdateEnabledState(bool active)
{
    bool shouldBeAdded = active && m_Enabled;
    if (shouldBeAdded == (bool)m_IsAdded)
        return;
        
    if (shouldBeAdded)
    {
        m_IsAdded = true;
        AddToManager(); //重新回到 MonoBehaviour 中调用 AddToManager
    }
    else
    {
        m_IsAdded = false;
        RemoveFromManager();
    }
}
void MonoBehaviour::AddToManager()
{
    ScriptingObjectPtr instance = GetInstance();
    if (instance == SCRIPTING_NULL || !ShouldRunBehaviour())
        return;
    ...
    AddBehaviourCallbacksToManagers();
    if (!m_DidAwake) //标志位,只能调一次
    {
        CallAwake();
        RETURN_IF_DESTROYED_OR_DISABLED
    }
    if (!GetMethod(MonoScriptCache::kAddToManagerInternal).IsNull())
    {
        CallMethodIfAvailable(MonoScriptCache::kAddToManagerInternal);
    }
    if (!GetMethod(MonoScriptCache::kAddToManager).IsNull()) //这里其实是当前C#脚本中的 OnEnable() 方法
    {
        CallMethodIfAvailable(MonoScriptCache::kAddToManager);
        RETURN_IF_DESTROYED_OR_DISABLED
    }
    AddExternalDependencyCallbacksToManagers();
}
void MonoBehaviour::CallAwake()
{
    m_DidAwake = true; //设置 Awake 标志位
    ScriptingMethodPtr internalAwakeMethod = GetMethod(MonoScriptCache::kInternalAwake);
    if (!internalAwakeMethod.IsNull())
        CallMethodInactive(internalAwakeMethod);

    ScriptingMethodPtr awakeMethod = GetMethod(MonoScriptCache::kAwake); //获取当前C#脚本中的 Awake() 方法
    if (!awakeMethod.IsNull())
    {
        ScriptingObjectPtr instance = GetInstance();
        if (!CallMethodInactive(awakeMethod)) //正式调用当前C#脚本中的 Awake() 方法
        {
            if (!::IsInstanceValid(instance))
                return;

            SetEnabled(false);
        }
    }
}

可以看到C#脚本中的 Awake()OnEnable() 孰先孰后一目了然。

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值