DI Lifecycle Design
This guide covers the full DI-driven flow for scene transitions, popups, data serving, and in-game logic in AchEngine.
Overall Structure
1. Bootstrap Scene - Register Global Services
Register services that live for the entire lifetime of the app in the Bootstrap scene.
// GlobalInstaller.cs
public class GlobalInstaller : AchEngineInstaller
{
[SerializeField] private GameConfig _config;
public override void Install(IServiceBuilder builder)
{
builder
// Configuration data
.RegisterInstance<IGameConfig>(_config)
// Table data service
.Register<ITableService, TableService>()
// UI service (auto-registered, but explicit registration is also fine)
.Register<IUIService, UIService>()
// Other global services such as audio and networking
.Register<IAudioService, AudioService>()
.Register<INetworkService, NetworkService>();
}
}[Bootstrap Scene]
└── [AchEngineScope] Installers: [GlobalInstaller]
└── [UIRoot]2. Scene Transition - Lobby to InGame
Create a separate AchEngineScope per scene to register and dispose scene-specific services.
// SceneService.cs - Global service registered in the Bootstrap scene
public class SceneService : ISceneService
{
public async UniTask LoadLobby()
{
// Unload the current game scene
await UnloadCurrentGameScene();
// Load the Lobby scene additively
await SceneManager.LoadSceneAsync("Lobby", LoadSceneMode.Additive);
// Show the lobby entry UI
ServiceLocator.Resolve<IUIService>().Show<LobbyView>();
}
public async UniTask LoadInGame(int stageId)
{
// Close the lobby UI
ServiceLocator.Resolve<IUIService>().CloseAll();
// Unload the lobby scene and load the InGame scene
await SceneManager.UnloadSceneAsync("Lobby");
await SceneManager.LoadSceneAsync("InGame", LoadSceneMode.Additive);
// Start gameplay
ServiceLocator.Resolve<IGameService>().StartStage(stageId);
}
}// LobbyInstaller.cs - Lobby scene only
public class LobbyInstaller : AchEngineInstaller
{
public override void Install(IServiceBuilder builder)
{
builder
.Register<IShopService, ShopService>()
.Register<IFriendService, FriendService>();
}
}// GameInstaller.cs - InGame scene only
public class GameInstaller : AchEngineInstaller
{
public override void Install(IServiceBuilder builder)
{
builder
.Register<IGameService, GameService>()
.Register<IEnemySpawner, EnemySpawner>()
.Register<IStageService, StageService>();
}
}Scene Scope Lifetime
When a scene is unloaded, AchEngineScope.OnDestroy() is called and the services for that scene are automatically released from the container.
3. Popup Creation - Passing Data
Popups inherit from UIView, and data is injected through the callback passed to Show().
// ItemDetailPopup.cs
public class ItemDetailPopup : UIView
{
[SerializeField] private Text _nameText;
[SerializeField] private Text _descText;
[SerializeField] private Text _priceText;
[SerializeField] private Image _iconImage;
private ItemData _item;
public override UILayerId Layer => UILayerId.Popup;
protected override void OnInitialize()
{
// Set up close buttons and other one-time wiring
}
// Data injection from the outside (called through the Show callback)
public void SetItem(ItemData item, Sprite icon)
{
_item = item;
_nameText.text = LocalizationManager.Get(L.Item.Name(item.Id));
_descText.text = LocalizationManager.Get(L.Item.Desc(item.Id));
_priceText.text = $"{item.Price:N0} G";
_iconImage.sprite = icon;
}
protected override void OnClosed()
{
_item = null;
_iconImage.sprite = null;
}
}// Open the popup (for example, from an inventory screen)
var ui = ServiceLocator.Resolve<IUIService>();
var icon = await AddressableManager.LoadAsync<Sprite>($"icon_{item.Id}");
ui.Show<ItemDetailPopup>(popup => popup.SetItem(item, icon.Result));4. Data Serving - Table to Service
TableService wraps baked data so other services can consume it through DI.
// GameService.cs
public class GameService : IGameService
{
private readonly ITableService _tables;
private readonly IUIService _ui;
// Constructor injection through DI
public GameService(ITableService tables, IUIService ui)
{
_tables = tables;
_ui = ui;
}
public void StartStage(int stageId)
{
var stageData = _tables.Get<StageTable>().Get(stageId);
var enemies = _tables.Get<EnemyTable>().GetByStage(stageId);
_ui.Show<GameHUDView>(hud => hud.SetStage(stageData));
foreach (var enemy in enemies)
SpawnEnemy(enemy);
}
}5. In-Game Access from MonoBehaviour
Because MonoBehaviour instances are not created directly by the DI container, they usually use ServiceLocator.
// PlayerController.cs
public class PlayerController : MonoBehaviour
{
private IGameService _gameService;
private IAudioService _audioService;
private void Start()
{
// Resolve services at runtime through ServiceLocator
_gameService = ServiceLocator.Resolve<IGameService>();
_audioService = ServiceLocator.Resolve<IAudioService>();
}
private void OnTriggerEnter(Collider other)
{
if (other.TryGetComponent<IEnemy>(out var enemy))
{
_gameService.OnPlayerHit(enemy.Damage);
_audioService.PlaySFX("hit");
}
}
}When [Inject] Works
If the container creates the object itself (for example, after Register<PlayerController>() and without manual Instantiate), you can use [Inject].
Summary of the Full Flow
App Start
└── Load Bootstrap scene
└── AchEngineScope → GlobalInstaller → Register global services
└── Initialize ServiceLocator
Scene Transition
└── ISceneService.LoadLobby()
└── Load Lobby scene additively
└── LobbyScope → LobbyInstaller → Register lobby services
Popup
└── IUIService.Show<ItemDetailPopup>(popup => popup.SetItem(...))
InGame
└── MonoBehaviour → ServiceLocator.Resolve<T>()
or [Inject] attribute injection