Single Entry Point means one script controls your game’s startup flow. Instead of every MonoBehaviour running its own Awake() and Start(), you have one GameInitiator that orchestrates everything in order. No more racing conditions, no more “which loads first” mysteries.
This means startup becomes a linear, readable sequence. You control exactly when things happen, can easily add async waits, and know precisely when loading is “done.” For example:
- Wait for server response before spawning enemies
- Show loading screen until all assets are ready
- Initialize third-party SDKs before gameplay starts
- Display a level intro animation, then enable enemies
The Problem
Scattered initialization leads to chaos:
// Player.cs
void Start() => LoadWeapon(); // When does this run?
// EnemySpawner.cs
void Start() => SpawnAroundPlayer(); // Is player positioned yet?
// LevelUI.cs
void Awake() => ShowLevelNumber(); // Before or after spawn?You can juggle Awake vs Start, but the moment you need async waits or a specific sequence, it falls apart.
The Solution
Empty your scene. Create one GameInitiator script. All other objects become prefabs that get instantiated and initialized through this single flow.
public class GameInitiator : MonoBehaviour
{
[Header("Prefabs")]
[SerializeField] private LoadingScreen loadingScreenPrefab;
[SerializeField] private Player playerPrefab;
[SerializeField] private EnemySpawner enemySpawnerPrefab;
[SerializeField] private LevelUI levelUIPrefab;
async void Start()
{
// 1. Binding - instantiate and keep references
var loadingScreen = Instantiate(loadingScreenPrefab);
var player = Instantiate(playerPrefab);
var enemySpawner = Instantiate(enemySpawnerPrefab);
var levelUI = Instantiate(levelUIPrefab);
loadingScreen.Show();
// 2. Initialization - setup services, SDKs, etc.
await AnalyticsService.Initialize();
// 3. Creation - load heavy assets
await enemySpawner.CreateEnemies(count: 10);
// 4. Preparation - position, configure, but don't start yet
player.MoveToRandomSpawn();
player.EquipStartingWeapon();
enemySpawner.PositionAroundPoint(player.transform.position);
enemySpawner.DisableAll();
levelUI.SetLevel(GameState.CurrentLevel);
loadingScreen.Hide();
// 5. Game start - now run the actual gameplay
await levelUI.PlayIntroAnimation();
enemySpawner.EnableAll();
}
}The Steps
| Step | What Happens |
|---|---|
| Binding | Instantiate prefabs, store references |
| Initialization | Setup services (analytics, input systems, server auth) |
| Creation | Load/spawn heavy objects (addressables, pooled enemies) |
| Preparation | Position objects, set initial state, configure visuals |
| Start | Begin gameplay, enable interactions |
Side Effect: Cleaner Methods
This pattern forces you to split methods that were doing too much:
// Before: does two things
public void SpawnEnemiesAroundPlayer()
{
CreateEnemies(); // Creation
PositionAroundPlayer(); // Preparation
}
// After: separate responsibilities
public void CreateEnemies(int count) { /* ... */ }
public void PositionAroundPoint(Vector3 point) { /* ... */ }The magic: Loading screens become trivial—show at start, hide when you decide it’s ready. Async waits slot in naturally. New team members instantly see where the flow begins. And you’ll never debug another “wrong initialization order” bug.