Unity action-platformer/shooter project based on the _SuperCommando_ gameplay package, now with a staged DI/service migration using VContainer.
Full project refactor
- Introduced a VContainer-based composition root with
ProjectLifetimeScope, bootstrap helpers, and explicit service registration. - Added a broad service-contract layer for scene loading, audio, ads, progression, inventory, upgrades, gameplay presentation, game session flow, input, persistence, menu flow, consent, analytics, and several UI/runtime bridges.
- Replaced direct singleton-heavy runtime access with legacy adapters, allowing an incremental migration away from
GameManager.Instance,MenuManager.Instance,CameraFollow.Instance,KeyUI.Instance, and similar template-era patterns. - Reworked the loading pipeline: removed the old timer-driven loading screen implementation, added
SceneLoaderService,SceneLoadOptions,LoadingScreenView, and properAsyncOperationprogress handling. - Moved large parts of menu, level navigation, inventory, upgrade, and scene-transition logic behind service abstractions instead of direct
GlobalValueandPlayerPrefsusage. - Added
UnityPrefsKeyValueStoreand routed persistence throughIKeyValueStore, reducing scattered inline persistence code. - Started extracting runtime game-state orchestration into
IGameSessionService,IGameSessionRuntimeState, and a dedicated listener broadcast service. - Added reusable enemy/runtime infrastructure such as
EnemyStateMachine,IEnemyState,BossDeathSequence, andBurstFireControllerto replace hidden coroutine-driven logic with explicit timer/state-driven flows. - Refactored many bosses, enemies, traps, projectiles, and interactables to use injected services for audio, session state, presentation, and menu flow.
- Split the player controller into partial classes so movement/runtime/combat responsibilities are no longer concentrated in a single monolithic file.
- Cleaned up repeated component lookups and normalized dependency resolution with cached references and safer same-object binding patterns.
- Added a dedicated launch consent flow with
IConsentService,ConsentService,IAnalyticsService, andReflectionFirebaseAnalyticsService. - Created an editor-authored
ConsentDialogprefab andConsentDialogController, so consent UI is now designed in Unity instead of being generated at runtime. - Gated startup progression in the logo/flash flow behind explicit user consent instead of always auto-advancing.
- Persisted consent acceptance locally and added analytics tracking for the
consent_acceptedevent, with optional Firebase forwarding through reflection when the SDK is present. - Added Google UMP synchronization for Android/iOS after consent acceptance.
- Expanded the DI/service bridge layer further with camera, dialog, menu, tutorial, floating-text, boss-healthbar, gun-runtime, keyboard-binding, and level-map service contracts plus legacy adapters.
- Continued runtime cleanup by moving more player, camera, HUD, menu, and bridge logic behind service abstractions.
- Separated additional player responsibilities into
Player.Runtime.csandPlayer.Combat.cs, improving maintainability of the player codebase. - Fixed the startup loading flow so
FlashSceneresolves the loading UI from an explicit loading-screen root object instead of relying on an incorrect local reference path. - Re-enabled the relevant startup/loading objects in scenes so the loading screen can actually appear during scene transitions.
- Removed an invalid or unused loading/progress text hookup from
MainMenuand cleaned up redundant UI objects that interfered with the loading setup. - Adjusted scene UI sizing/activation to stabilize the loading-screen presentation after the consent-flow changes.
- Fixed player damage handling by applying incoming damage to
Healthbefore death/game-over evaluation, which restores correct HP reduction behavior. - Increased player survivability via the player prefab/runtime update included in this pass.
- Fixed the level-finish flow by exposing
ShowGameFinish()throughIMenuFlowServiceand calling it fromGameManagerwhen the finish transition starts. - Fixed a boss health bar initialization timing bug by buffering pending icon/max-health data in
LegacyBossHealthbarServiceuntil the UI becomes available. - Reworked the consent dialog copy to be data-driven by introducing
ConsentDialogConfigas aScriptableObject. - Added a concrete
ConsentDialogConfigasset underAssets/_SuperCommando_/Data/Configs, making consent text editable without code changes. - Simplified consent button wiring by moving button listener registration to
OnEnable/OnDisable.
The project now has a root composition layer built on VContainer:
Assets/_SuperCommando_/Script/System/BootstrapAssets/_SuperCommando_/Script/System/ContractsAssets/_SuperCommando_/Script/System/LoadingAssets/_SuperCommando_/Script/System/Services/LegacyAssets/_SuperCommando_/Script/GUI/Loading
ProjectLifetimeScopeis the rootLifetimeScopeProjectLifetimeScopeBootstrapensures the root scope exists before scene code asks for servicesProjectScope.Inject(this)is the compatibility entry point for sceneMonoBehaviourclasses
Current service contracts registered in ProjectLifetimeScope:
ISceneLoaderIAudioServiceIAdsServiceIGameplayPresentationServiceILevelCatalogServiceIPlayerProfileServiceIProgressServiceIInventoryServiceIUpgradeServiceILevelSelectionStateICharacterSelectionServiceIGameSessionServiceIControllerInputServiceIKeyValueStoreICameraRigServiceIMenuFlowServiceIKeyPresentationServiceIDialogFlowServiceIMainMenuSceneServiceITutorialOverlayServiceIFloatingTextServiceIBossHealthbarServiceIGunRuntimeServiceIKeyboardBindingServiceILevelMapSettingsServiceIDefaultGameConfigServiceIAnalyticsServiceIConsentService
These are currently backed by legacy adapters where needed, so the migration is incremental rather than a risky rewrite.
The old timer-based loading implementation was replaced with a proper scene-loading architecture:
LoadingScreenViewis now a dumb view componentSceneLoaderServiceowns scene transition orchestration- scene loading uses real
AsyncOperationprogress - loading UI and loading orchestration are separated
- common scene transitions are centralized through
ISceneLoader
The old TimedLoadingScreen was removed and replaced by LoadingScreenView.
Main scene transition flow was moved away from local duplicated coroutines and into ISceneLoader:
MenuManagerMainMenuHomeSceneFlashSceneStoryComicResetDataOpenSceneGameManager
UI scripts that used to reach directly into GlobalValue now go through service contracts:
MainMenu_LevelMainMenu_WorldWorldChooseUIMainMenuUpdateCoinsUpgradeItemUIMenu_GUIMenu_StartScreenCharacterHolderMapControllerUI
Weapon and ammo related flow now uses IInventoryService and IUpgradeService:
GunManagerRangeAttackMeleeAttackPlayerCollectGunItemItemTypeShopItemUI
Recent follow-up work in this slice:
LegacyInventoryServicenow owns current selected gun runtime state instead of routing that state back throughGlobalValue- dart ammo persistence goes through
IKeyValueStore - upgrade values already route through
IUpgradeService LegacyPlayerProfileServicenow uses explicit key constants instead of depending onGlobalValuestring fieldsLegacyLevelSelectionStatenow owns level-selection runtime state directly instead of proxying throughGlobalValue.currentHighestLevelObjGlobalValueandGunTypeIDnow route persistence throughIKeyValueStorefallback stores instead of touchingPlayerPrefsinlinePlayeris now physically split intoPlayer.cs,Player.Runtime.cs, andPlayer.Combat.cspartials so movement/runtime/combat code no longer lives in one monolithic source file
The project now also exposes explicit bridges for camera and menu-driven runtime orchestration:
ICameraRigServiceIMenuFlowServiceIKeyPresentationService
These services now front legacy scene objects instead of letting gameplay code talk directly to them:
GameManagerPlayerControllerInputCameraLookPointDialogUITriggerFloatingTextManagerFloatingTextMenu_AskSaveMeWatchAdToFinishLevel
This removed the remaining direct gameplay dependencies on CameraFollow.Instance, MenuManager.Instance, and KeyUI.Instance from the core runtime flow.
Another adapter layer now covers secondary scene-level singleton flows that were still hanging off template-era Instance access:
IDialogFlowServiceIMainMenuSceneServiceITutorialOverlayServiceIFloatingTextServiceIBossHealthbarServiceIGunRuntimeServiceIKeyboardBindingServiceILevelMapSettingsServiceIDefaultGameConfigService
These are now used to reduce direct singleton reach-ins from:
DialogHandlerDialogUITriggerOpenSceneMainMenu_LevelWorldChooseUITutorialFlagTheGateBOSS_1CollectGunItemPlayerRangeAttackControllerInputMenu_AskSaveMeMainMenuHomeSceneLevelManagerLegacyProgressServiceGlobalValue
As a result, the old DialogManager.Instance, MainMenuHomeScene.Instance, Tutorial.Instance, BossHealthbar.Instance, FloatingTextManager.Instance, DefaultValueKeyboard.Instance, LevelMapType.Instance, and GunManager.Instance access patterns are no longer used by gameplay callers.
Rewarded and interstitial entry points are abstracted through IAdsService:
Menu_AskSaveMeWatchAdToFinishLevelMainMenu_LevelMainMenuHomeSceneMenuManagerShopItemUIFlashScene
This also removed reliance on older broken rewarded-ads call sites in UI code.
The ad orchestration layer itself is also partially modernized:
AdsManagernow uses explicit polling/backoff timers instead ofStartCoroutine/Invoke- ad removal checks go through progress state rather than direct
GlobalValue.RemoveAdsreads LegacyAdsServicestill provides compatibility for the rest of the project
The app now has a dedicated launch-consent and analytics layer:
IConsentServiceIAnalyticsServiceConsentServiceFirebaseAnalyticsServiceFirebaseRuntimeInitializerConsentDialogController
What this adds:
- a branded consent dialog is shown immediately on launch before the logo scene continues
- the consent dialog is now expected to be authored in the Unity editor instead of being generated at runtime
- tapping
ACCEPTpersists consent locally - tapping
ACCEPTtriggers the analytics eventconsent_accepted - Firebase Analytics is now initialized explicitly at runtime before queued events are flushed
- Android Firebase config is now included through
google-services.json - the app also syncs Google UMP consent state after acceptance when running on Android/iOS
Related startup polish included in this slice:
FlashSceneis now gated by launch consent instead of always auto-advancing after a fixed delayMainMenuHomeSceneno longer overrides menu music with gameplay music during startup- the
Ads Managerprefab instance inLogo Sceneis active again, fixing a broken startup path where ad services never initialized
Legacy singleton-based presentation logic was abstracted behind IGameplayPresentationService:
- black-screen transitions
- controller visibility
- gameplay UI visibility
- warning / clean encounter UI
Used by:
BOSS_FLYING_BOMBERBOSS_HOGRIDERBOSS_1DialogUITriggerDialogManagerCameraLookPointGroupEnemySystemStoryComic
Runtime game-session access is now abstracted behind IGameSessionService, backed by LegacyGameSessionService over GameManager.
The runtime session layer now also has dedicated infrastructure for internal core state:
IGameSessionRuntimeStateGameSessionRuntimeStateIListenerBroadcastServiceSceneListenerBroadcastService
This migration removed direct orchestration dependencies from a number of gameplay scripts:
BOSS_FLYING_BOMBERBOSS_HOGRIDERBOSS_1BOSS_2BOSS_3GroupEnemySystemKeyItemTheGateGameFinishFlagMenu_GameoverKillPlayerOnTouchMenu_AskSaveMeMonsterIVMonsterFishEnemyTankTurret
The service currently covers:
- game state
- player access
- key possession
- checkpoint access
- mission stars
- session points/coins
- continue cost
- camera pause
- game over / continue / finish / restart orchestration
The underlying GameManager runtime flow is also cleaner than the original template version:
- finish / continue transitions now use explicit timer-driven state changes instead of
Invoke/ coroutines - debug/reset shortcuts now route through service abstractions instead of writing directly to
PlayerPrefsorGlobalValue - listener notification is now delegated to a dedicated broadcast service instead of being owned inline by
GameManager - transient runtime values such as state, checkpoint, mission stars, point/coin totals and several flags now live in
GameSessionRuntimeStateinstead of being owned only as mutable fields onGameManager - the session lifecycle is now more predictable for
LegacyGameSessionService
Additional runtime callers migrated onto IGameSessionService in later passes include:
AutoSpawnAutoSetBoolAnimBrokenTreasureHelicopterControllerKeyUIStarEnemyHideAndShowEnemyHitAndHideSpringLaserObstacleTeleportSetActiveObjInRangePlaySoundWhenPlayerInRangePlaySoundWhenObjMovingHelperStandFallingSpikedObjectGiveDamageToTargetChasingStoneFallingProjectileBulletBulletPathedProjectileSpawnerGrenadeSetLayerFollowPlayerSpearTrapTruckleControllerTruckleTriggerSimplePathedMovingTutorialTeleportPointSimpleChasePlayerBoss_SuperAttackFlamePressureSwitchEventEffectChooseHelper
Editor/debug tooling and store integration were also cleaned up in later passes:
GameModeEditornow usesIKeyValueStoresemantics throughUnityPrefsKeyValueStoreinstead of writingPlayerPrefs/GlobalValueinlinePurchasernow routesRemoveAdsthroughIProgressServiceinstead of directly mutatingGlobalValueUnityIAPNotifyDelayernow usesEditorPrefsinstead ofPlayerPrefsLegacyGameSessionServiceandLegacyControllerInputServiceno longer depend onGameManager.Instance/ControllerInput.Instance, and instead resolve scene objects lazily without singleton couplingLegacyAudioServiceno longer usesSoundManageras a static gateway; it now adapts a concreteSoundManagerinstancePlayerPrefsstorage is now named and registered asUnityPrefsKeyValueStore, keeping persistence concerns isolated behindIKeyValueStore
Shared enemy runtime infrastructure has started moving away from ad-hoc coroutines and duplicated timing logic:
IEnemyStateEnemyStateMachineBossDeathSequenceBurstFireController
These are now used to remove coroutine-based orchestration from:
BOSS_FLYING_BOMBERBOSS_HOGRIDERBOSS_1BOSS_2BOSS_3EnemyRangeAttackEnemyTankTurretEnemyAIEnemyEnemyGroundedSimpleFlyingEnemyMonsterSnailSmartEnemyGroundedEnemyMeleeAttackEnemyThrowAttackEnemyBomberAttackEnemyCallMinion
This gives the project a reusable path for moving enemy logic to explicit update-driven state machines instead of hidden coroutine flows.
Smaller legacy bosses are also moving onto explicit timer-driven orchestration:
BOSS_1BOSS_2BOSS_3Boss1AttackOrder
In that slice:
- attack loops no longer self-schedule through coroutines
- blink/hurt/death cleanup no longer relies on
Invoke - orientation and combat checks use injected runtime services instead of direct singleton calls
- boss attack ordering is now an explicit update-driven loop instead of a hidden coroutine chain
Projectile-side combat helpers have also started moving onto the same service-oriented runtime assumptions:
ArrowProjectileSimpleProjectileSmartProjectileAttackPathedProjectileMonsterFishFishAIFireEvil
In this slice:
- projectile hit audio now goes through
IAudioService - score rewards now go through
IGameSessionService - local delayed destruction / reload / attack delays use explicit timers instead of coroutine helpers
Another batch of scene interactables and traps now uses injected runtime services instead of direct singleton access:
SpringLaserObstacleTeleportSetActiveObjInRangePlaySoundWhenPlayerInRangePlaySoundWhenObjMovingHelperStandFallingSpikedObjectGiveDamageToTargetChasingStoneFallingProjectileBulletBulletPathedProjectileSpawnerGrenadeSetLayerFollowPlayerSpearTrapTruckleControllerTruckleTriggerSimplePathedMovingTutorialTeleportPointSimpleChasePlayerEnemyJumpingEnemySpiderMagnetItemBlockMonsterUpDownPlatformControllerSwitcherMinerSimpleGravityObjectBridgeFallingProjectileBullet
In this slice:
- player access, game-state checks and camera pause go through
IGameSessionService - local trigger/repeat flows are moving away from
InvokeRepeatingand coroutine-driven throttling toward explicit timers - trap and obstacle audio now routes through
IAudioServicewhere the script itself owns the playback decision - several late legacy gameplay components now inject
IAudioServicedirectly instead of callingSoundManager.PlaySfx(...)
A dedicated cleanup pass also normalized Unity component lookup patterns in frequently-hit scripts:
- same-object dependencies now prefer cached references with
SerializeField+Awake/Startfallback - repeated lookup paths were removed from
Bridge,GameFinishFlag,MainMenu_Level,MainMenu_World,ZoomZone,CameraFollow,ControllerInput,GameManager,LevelManager,GroupEnemySystem,MainMenuHomeScene,ResetData,HealthBar, andChasingStone FindObjectOfTypeusage was reduced to a small residual tail, with most scene lookups moved to cachedObject.FindFirstObjectByType(...)or explicit serialized references- required same-object dependencies now use
RequireComponentwhere appropriate
Use these rules for new code:
- put contracts in
Assets/_SuperCommando_/Script/System/Contracts - put root bootstrap/container code in
Assets/_SuperCommando_/Script/System/Bootstrap - put scene-loading orchestration in
Assets/_SuperCommando_/Script/System/Loading - put compatibility adapters in
Assets/_SuperCommando_/Script/System/Services/Legacy - put UI view components in
Assets/_SuperCommando_/Script/GUI/...
Do not add new cross-project static helpers for systems that already have service contracts.
The project is not fully migrated yet. The largest remaining legacy areas are:
GlobalValuestill exists as a legacy compatibility bag, though itsPlayerPrefsaccess is now funneled throughIKeyValueStore- the former
GameManager.Instance/MenuManager.Instance/CameraFollow.Instance/KeyUI.Instanceruntime access paths have been replaced in core gameplay with explicit services and legacy adapters SoundManageris now mostly isolated toLegacyAudioServiceand old commented template code, but the compatibility layer still existsSoundManagernow primarily acts as the underlying concrete audio component instead of a cross-project static access point- remaining
PlayerPrefshits are now concentrated inUnityPrefsKeyValueStoreand a few explicit compatibility/bootstrap points rather than spread through gameplay code - older enemy bases are partially migrated, but still need deeper extraction of combat/state responsibilities into reusable state-machine-driven components
- runtime scene state still centered around
GameManager - old ad classes still exist as implementation details behind adapters
- menu audio data is still partly scene-driven; for example, some scenes rely on fallback behavior when a dedicated menu track is not assigned
- Firebase Analytics is now wired for Android, but iOS still needs its own Firebase config file and final device-side validation
- generated Android/Firebase resolver artifacts are still committed as build-support assets rather than being reduced to a leaner curated set
- the startup flow still combines consent, ads, analytics, and scene transition responsibilities in a small number of scene controllers
If the migration continues, the next bounded contexts should be:
GameManagerruntime state extraction into a proper per-level session model instead of a legacy adapter overGameManager- AI/environment audio migration onto
IAudioService - save/profile/progression persistence split away from
GlobalValue - ad implementation cleanup so only
IAdsServiceremains visible to game code - gradual removal of compatibility singletons once all callers are migrated
Additional practical improvements that would raise quality and reduce maintenance cost:
- assign an explicit dedicated menu music clip in all menu scenes and prefabs so menu audio no longer depends on runtime fallback logic
- add a small startup diagnostics layer for consent, Firebase initialization, ads initialization, and loading transitions to make device-side issues easier to trace
- validate the
consent_acceptedevent end-to-end in Firebase DebugView on real Android hardware and document the verification steps for future regressions - add iOS Firebase support by supplying
GoogleService-Info.plistand validating the same consent analytics flow on iOS builds - trim or isolate Firebase package payloads and generated Android resolver assets if repository size becomes a recurring issue
- continue splitting large legacy MonoBehaviours such as
GameManager, menu controllers, and older enemies into smaller runtime/state/presentation components - add targeted smoke tests or editor validation checks for startup-critical flows: consent dialog presence, loading-screen references, boss health bar initialization, and menu audio assignment
- VContainer is added through
Packages/manifest.json - after pulling/opening the project, let Unity reimport packages and scripts
- then check Unity Console for serialization or compile issues before continuing work
This migration was intentionally done in bounded slices. The goal was to raise architectural quality without destabilizing gameplay scenes and prefabs in one rewrite.