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#脚本运行时,分为了mono和il2cpp,这里就是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() 孰先孰后一目了然。