Bootstrap and Loading
Game initialization, loading screens, and async scene management for VR.
Why Bootstrap Matters
In VR, any frame drop causes:
- Tracking freeze
- Disorientation
- Motion sickness
Bootstrap ensures all systems initialize before gameplay, with loading screens covering any heavy operations.
Scene Structure
Build Index 0: Bootstrapper
- GameBootstrap (events, databases)
- Persistent managers
- Loading screen
Build Index 1: Hub
- Player spawn
- Shop/armory
- Arena portals
Build Index 2+: Arenas
- Loaded additively
- Unloaded on exit
Initialization Sequence
Phase 1: Core Systems (Synchronous)
- Create singleton managers
- Initialize event channels (GameEvents)
- Load databases (GameDatabases)
- Initialize audio system
Phase 2: User Data (May be Async)
- Load player settings
- Apply graphics/audio settings
- Load or create save file
- Initialize player attributes
Phase 3: Pre-loading (Async with Loading Screen)
- Pre-warm object pools
- Load common assets
- Initialize XR systems
Phase 4: Ready
- Hide loading screen
- Enable input
- Start gameplay
GameBootstrap
Initializes all static references on startup.
public class GameBootstrap : MonoBehaviour
{
[SerializeField] private VoidEventChannel onGameSaved;
[SerializeField] private IntEventChannel onGoldChanged;
[SerializeField] private WeaponDatabase weaponDatabase;
[SerializeField] private EnemyDatabase enemyDatabase;
public void Initialize()
{
// Assign to static classes
GameEvents.OnGameSaved = onGameSaved;
GameEvents.OnGoldChanged = onGoldChanged;
GameDatabases.WeaponDatabase = weaponDatabase;
GameDatabases.EnemyDatabase = enemyDatabase;
// Build lookup dictionaries
weaponDatabase.BuildLookup();
enemyDatabase.BuildLookup();
}
}
Loading Screen Requirements
Must Have
- Always visible (never black screen)
- Static or minimal movement
- Progress indicator
- Comfortable viewing distance
Should Have
- Fade transitions
- Loading tips
- Cancel option for long loads
Avoid
- Complex 3D scenes
- Rapid animations
- Small text
Async Scene Loading
Basic Pattern
public IEnumerator LoadSceneAsync(string sceneName)
{
loadingScreen.Show();
loadingScreen.UpdateProgress(0, "Loading...");
yield return SceneManager.UnloadSceneAsync(currentScene);
yield return Resources.UnloadUnusedAssets();
var operation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
operation.allowSceneActivation = false;
while (operation.progress < 0.9f)
{
loadingScreen.UpdateProgress(operation.progress, "Loading...");
yield return null;
}
operation.allowSceneActivation = true;
yield return operation;
loadingScreen.UpdateProgress(1, "Ready!");
yield return loadingScreen.FadeOut(0.3f);
}
Key Points
- Never block main thread
- Use allowSceneActivation for controlled activation
- Yield between heavy operations
- Show progress to player
Pool Pre-warming
Instantiate pooled objects during loading, not gameplay.
public IEnumerator PrewarmPools()
{
int batchSize = 5;
int instantiated = 0;
foreach (var config in poolConfigs)
{
for (int i = 0; i < config.count; i++)
{
Instantiate(config.prefab).SetActive(false);
instantiated++;
if (instantiated >= batchSize)
{
instantiated = 0;
yield return null;
}
}
}
}
Persistent Objects
Objects that survive scene loads:
Typical persistent objects:
- GameBootstrap
- AudioManager
- SaveDataManager
- XR Rig
- LoadingScreen
Best Practices
| Practice | Reason |
|---|---|
| Show loading for operations over 100ms | Player knows game is responding |
| Pre-warm pools during loading | No instantiation spikes in gameplay |
| Fade transitions | Hide loading artifacts |
| Keep XR Rig persistent | Never lose tracking |
| Test on device | Editor performance differs |