Extend VContainer DI: eliminate manual composition and duplicate references

- Register InventoryModel, ShopModel as container-managed singletons
- Register GameController, ShopController, InventoryController via RegisterComponent
- Replace [SerializeField] with [Inject] for service dependencies in controllers
- Move maxActiveModifierSlots config to GameLifetimeScope (composition root)
- Remove manual model creation and Initialize() calls from GameController
- Add ToggleVisibility() to ShopController/InventoryController, removing GetComponentInChildren
- Move event subscriptions from Awake to Start for safe VContainer injection order
- Transfer game startup orchestration to GameController.Start()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 16:14:44 +07:00
parent 6c10a35bf9
commit 3c50415111
5 changed files with 116 additions and 98 deletions
+18 -1
View File
@@ -5,10 +5,13 @@ using YachtDice.Categories;
using YachtDice.Economy; using YachtDice.Economy;
using YachtDice.Events; using YachtDice.Events;
using YachtDice.Game; using YachtDice.Game;
using YachtDice.Inventory;
using YachtDice.Modifiers.Definition; using YachtDice.Modifiers.Definition;
using YachtDice.Modifiers.Pipeline; using YachtDice.Modifiers.Pipeline;
using YachtDice.Modifiers.Runtime; using YachtDice.Modifiers.Runtime;
using YachtDice.Scoring; using YachtDice.Scoring;
using YachtDice.Shop;
using YachtDice.UI;
namespace YachtDice.DI namespace YachtDice.DI
{ {
@@ -22,6 +25,12 @@ namespace YachtDice.DI
[SerializeField] private CurrencyBank currencyBank; [SerializeField] private CurrencyBank currencyBank;
[SerializeField] private GameManager gameManager; [SerializeField] private GameManager gameManager;
[SerializeField] private DiceManager diceManager; [SerializeField] private DiceManager diceManager;
[SerializeField] private GameController gameController;
[SerializeField] private ShopController shopController;
[SerializeField] private InventoryController inventoryController;
[Header("Settings")]
[SerializeField] private int maxActiveModifierSlots = 5;
protected override void Configure(IContainerBuilder builder) protected override void Configure(IContainerBuilder builder)
{ {
@@ -30,15 +39,23 @@ namespace YachtDice.DI
builder.RegisterInstance(categoryCatalog); builder.RegisterInstance(categoryCatalog);
// Core modifier services // Core modifier services
builder.Register<ModifierRegistry>(Lifetime.Singleton); builder.Register<ModifierRegistry>(Lifetime.Singleton)
.WithParameter<int>(maxActiveModifierSlots);
builder.Register<ModifierPipeline>(Lifetime.Singleton); builder.Register<ModifierPipeline>(Lifetime.Singleton);
builder.Register<GameEventBus>(Lifetime.Singleton); builder.Register<GameEventBus>(Lifetime.Singleton);
// Domain models
builder.Register<InventoryModel>(Lifetime.Singleton);
builder.Register<ShopModel>(Lifetime.Singleton);
// Scene MonoBehaviour components // Scene MonoBehaviour components
builder.RegisterComponent(scoringSystem); builder.RegisterComponent(scoringSystem);
builder.RegisterComponent(currencyBank); builder.RegisterComponent(currencyBank);
builder.RegisterComponent(gameManager); builder.RegisterComponent(gameManager);
builder.RegisterComponent(diceManager); builder.RegisterComponent(diceManager);
builder.RegisterComponent(gameController);
builder.RegisterComponent(shopController);
builder.RegisterComponent(inventoryController);
} }
} }
} }
+8 -6
View File
@@ -1,5 +1,6 @@
using System; using System;
using UnityEngine; using UnityEngine;
using VContainer;
using YachtDice.Categories; using YachtDice.Categories;
using YachtDice.Scoring; using YachtDice.Scoring;
@@ -7,13 +8,12 @@ namespace YachtDice.Game
{ {
public class GameManager : MonoBehaviour public class GameManager : MonoBehaviour
{ {
[Header("References")]
[SerializeField] private DiceManager diceManager;
[SerializeField] private ScoringSystem scoringSystem;
[Header("Settings")] [Header("Settings")]
[SerializeField] private int maxRollsPerTurn = 3; [SerializeField] private int maxRollsPerTurn = 3;
private DiceManager diceManager;
private ScoringSystem scoringSystem;
public int CurrentRoll { get; private set; } public int CurrentRoll { get; private set; }
public int CurrentTurn { get; private set; } public int CurrentTurn { get; private set; }
@@ -26,9 +26,11 @@ namespace YachtDice.Game
public event Action<CategoryDefinition, int> OnScored; public event Action<CategoryDefinition, int> OnScored;
public event Action<int> OnGameOver; public event Action<int> OnGameOver;
private void Start() [Inject]
public void Construct(DiceManager diceManager, ScoringSystem scoringSystem)
{ {
StartNewGame(); this.diceManager = diceManager;
this.scoringSystem = scoringSystem;
} }
public void StartNewGame() public void StartNewGame()
@@ -1,4 +1,5 @@
using UnityEngine; using UnityEngine;
using VContainer;
using YachtDice.Categories; using YachtDice.Categories;
using YachtDice.Economy; using YachtDice.Economy;
using YachtDice.Modifiers.Runtime; using YachtDice.Modifiers.Runtime;
@@ -9,25 +10,29 @@ namespace YachtDice.Inventory
public class InventoryController : MonoBehaviour public class InventoryController : MonoBehaviour
{ {
[SerializeField] private InventoryView inventoryView; [SerializeField] private InventoryView inventoryView;
[SerializeField] private ScoringSystem scoringSystem;
[SerializeField] private CurrencyBank currencyBank;
private InventoryModel model; private InventoryModel model;
private ScoringSystem scoringSystem;
private CurrencyBank currencyBank;
public InventoryModel Model => model; public InventoryModel Model => model;
public void Initialize(InventoryModel inventoryModel) [Inject]
public void Construct(InventoryModel model, ScoringSystem scoringSystem, CurrencyBank currencyBank)
{ {
model = inventoryModel; this.model = model;
this.scoringSystem = scoringSystem;
this.currencyBank = currencyBank;
}
private void Start()
{
inventoryView.OnActivateClicked += HandleActivate; inventoryView.OnActivateClicked += HandleActivate;
inventoryView.OnDeactivateClicked += HandleDeactivate; inventoryView.OnDeactivateClicked += HandleDeactivate;
inventoryView.OnSellClicked += HandleSell; inventoryView.OnSellClicked += HandleSell;
model.OnInventoryChanged += HandleInventoryChanged; model.OnInventoryChanged += HandleInventoryChanged;
scoringSystem.OnCategoryConfirmed += HandleCategoryConfirmed;
if (scoringSystem != null)
scoringSystem.OnCategoryConfirmed += HandleCategoryConfirmed;
RefreshView(); RefreshView();
} }
@@ -48,6 +53,16 @@ namespace YachtDice.Inventory
scoringSystem.OnCategoryConfirmed -= HandleCategoryConfirmed; scoringSystem.OnCategoryConfirmed -= HandleCategoryConfirmed;
} }
public void ToggleVisibility()
{
if (inventoryView == null) return;
if (inventoryView.IsVisible)
inventoryView.Hide();
else
inventoryView.Show();
}
private void HandleActivate(ModifierInstance instance) private void HandleActivate(ModifierInstance instance)
{ {
model.TryActivate(instance); model.TryActivate(instance);
+23 -9
View File
@@ -1,4 +1,5 @@
using UnityEngine; using UnityEngine;
using VContainer;
using YachtDice.Economy; using YachtDice.Economy;
using YachtDice.Modifiers.Definition; using YachtDice.Modifiers.Definition;
@@ -6,27 +7,30 @@ namespace YachtDice.Shop
{ {
public class ShopController : MonoBehaviour public class ShopController : MonoBehaviour
{ {
[SerializeField] private ModifierCatalogSO catalog;
[SerializeField] private ShopView shopView; [SerializeField] private ShopView shopView;
[SerializeField] private CurrencyBank currencyBank;
private ModifierCatalogSO catalog;
private CurrencyBank currencyBank;
private ShopModel model; private ShopModel model;
public ModifierCatalogSO Catalog => catalog; public ModifierCatalogSO Catalog => catalog;
public void Initialize(ShopModel shopModel) [Inject]
public void Construct(ModifierCatalogSO catalog, CurrencyBank currencyBank, ShopModel model)
{ {
model = shopModel; this.catalog = catalog;
this.currencyBank = currencyBank;
this.model = model;
}
private void Start()
{
shopView.OnBuyClicked += HandleBuyClicked; shopView.OnBuyClicked += HandleBuyClicked;
currencyBank.OnBalanceChanged += HandleCurrencyChanged;
if (currencyBank != null)
currencyBank.OnBalanceChanged += HandleCurrencyChanged;
model.OnItemPurchased += HandleItemPurchased; model.OnItemPurchased += HandleItemPurchased;
shopView.Populate(catalog.All, model); shopView.Populate(catalog.All, model);
shopView.UpdateCurrencyDisplay(currencyBank != null ? currencyBank.Balance : 0); shopView.UpdateCurrencyDisplay(currencyBank.Balance);
} }
private void OnDestroy() private void OnDestroy()
@@ -41,6 +45,16 @@ namespace YachtDice.Shop
model.OnItemPurchased -= HandleItemPurchased; model.OnItemPurchased -= HandleItemPurchased;
} }
public void ToggleVisibility()
{
if (shopView == null) return;
if (shopView.IsVisible)
shopView.Hide();
else
shopView.Show();
}
private void HandleBuyClicked(ModifierDefinitionSO def) private void HandleBuyClicked(ModifierDefinitionSO def)
{ {
model.TryPurchase(def); model.TryPurchase(def);
+45 -75
View File
@@ -16,43 +16,56 @@ namespace YachtDice.UI
{ {
public class GameController : MonoBehaviour public class GameController : MonoBehaviour
{ {
[Header("Model")]
[SerializeField] private GameManager gameManager;
[SerializeField] private ScoringSystem scoringSystem;
[SerializeField] private DiceManager diceManager;
[Header("Views")] [Header("Views")]
[SerializeField] private ScoreCardView scoreCardView; [SerializeField] private ScoreCardView scoreCardView;
[SerializeField] private DicePanelView dicePanelView; [SerializeField] private DicePanelView dicePanelView;
[SerializeField] private GameInfoView gameInfoView; [SerializeField] private GameInfoView gameInfoView;
[Header("Economy & Modifiers")]
[SerializeField] private CurrencyBank currencyBank;
[SerializeField] private ShopController shopController;
[SerializeField] private InventoryController inventoryController;
[Header("Settings")] [Header("Settings")]
[SerializeField] private int maxRollsPerTurn = 3; [SerializeField] private int maxRollsPerTurn = 3;
[SerializeField] private int maxActiveModifierSlots = 5;
private const int UpperBonusThreshold = 63; private const int UpperBonusThreshold = 63;
private const int UpperBonusValue = 35; private const int UpperBonusValue = 35;
private GameManager gameManager;
private ScoringSystem scoringSystem;
private DiceManager diceManager;
private CurrencyBank currencyBank;
private ShopController shopController;
private InventoryController inventoryController;
private ModifierRegistry modifierRegistry; private ModifierRegistry modifierRegistry;
private CategoryCatalog categoryCatalog; private CategoryCatalog categoryCatalog;
private InventoryModel inventoryModel; private ModifierCatalogSO modifierCatalog;
private ShopModel shopModel; private ShopModel shopModel;
[Inject] [Inject]
public void Construct(ModifierRegistry modifierRegistry, CategoryCatalog categoryCatalog) public void Construct(
GameManager gameManager,
ScoringSystem scoringSystem,
DiceManager diceManager,
CurrencyBank currencyBank,
ShopController shopController,
InventoryController inventoryController,
ModifierRegistry modifierRegistry,
CategoryCatalog categoryCatalog,
ModifierCatalogSO modifierCatalog,
ShopModel shopModel)
{ {
this.gameManager = gameManager;
this.scoringSystem = scoringSystem;
this.diceManager = diceManager;
this.currencyBank = currencyBank;
this.shopController = shopController;
this.inventoryController = inventoryController;
this.modifierRegistry = modifierRegistry; this.modifierRegistry = modifierRegistry;
this.categoryCatalog = categoryCatalog; this.categoryCatalog = categoryCatalog;
this.modifierCatalog = modifierCatalog;
this.shopModel = shopModel;
} }
// ── Lifecycle ────────────────────────────────────────────── // ── Lifecycle ──────────────────────────────────────────────
private void Awake() private void Start()
{ {
// Model → Controller // Model → Controller
gameManager.OnTurnStarted += HandleTurnStarted; gameManager.OnTurnStarted += HandleTurnStarted;
@@ -69,17 +82,17 @@ namespace YachtDice.UI
gameInfoView.OnShopClicked += HandleShopClicked; gameInfoView.OnShopClicked += HandleShopClicked;
gameInfoView.OnInventoryClicked += HandleInventoryClicked; gameInfoView.OnInventoryClicked += HandleInventoryClicked;
// Currency // Currency & Modifiers
if (currencyBank != null) currencyBank.OnBalanceChanged += HandleCurrencyChanged;
currencyBank.OnBalanceChanged += HandleCurrencyChanged; modifierRegistry.OnChanged += HandleInventoryChangedForSave;
}
private void Start() // Initialize
{
// Инициализируем скоркарту из каталога категорий
scoreCardView.Initialize(categoryCatalog); scoreCardView.Initialize(categoryCatalog);
LoadSaveData();
gameInfoView.SetCurrencyText(currencyBank.Balance);
InitializeModifierSystems(); // Start the game after all subscriptions are in place
gameManager.StartNewGame();
} }
private void OnDestroy() private void OnDestroy()
@@ -97,49 +110,22 @@ namespace YachtDice.UI
gameInfoView.OnShopClicked -= HandleShopClicked; gameInfoView.OnShopClicked -= HandleShopClicked;
gameInfoView.OnInventoryClicked -= HandleInventoryClicked; gameInfoView.OnInventoryClicked -= HandleInventoryClicked;
if (currencyBank != null) currencyBank.OnBalanceChanged -= HandleCurrencyChanged;
currencyBank.OnBalanceChanged -= HandleCurrencyChanged;
if (modifierRegistry != null) if (modifierRegistry != null)
modifierRegistry.OnChanged -= HandleInventoryChangedForSave; modifierRegistry.OnChanged -= HandleInventoryChangedForSave;
} }
// ── Modifier System Init ───────────────────────────────── // ── Save / Load ─────────────────────────────────────────
private void InitializeModifierSystems() private void LoadSaveData()
{
if (modifierRegistry == null)
modifierRegistry = new ModifierRegistry(maxActiveModifierSlots);
else
modifierRegistry.SetMaxActiveSlots(maxActiveModifierSlots);
modifierRegistry.OnChanged += HandleInventoryChangedForSave;
inventoryModel = new InventoryModel(modifierRegistry);
shopModel = new ShopModel(currencyBank, inventoryModel);
ModifierCatalogSO catalog = shopController != null ? shopController.Catalog : null;
LoadSaveData(catalog);
if (inventoryController != null)
inventoryController.Initialize(inventoryModel);
if (shopController != null)
shopController.Initialize(shopModel);
if (currencyBank != null)
gameInfoView.SetCurrencyText(currencyBank.Balance);
}
private void LoadSaveData(ModifierCatalogSO catalog)
{ {
SaveData save = SaveSystem.Load(); SaveData save = SaveSystem.Load();
if (currencyBank != null && save.Currency > 0) if (save.Currency > 0)
currencyBank.SetBalance(save.Currency); currencyBank.SetBalance(save.Currency);
if (catalog != null && save.OwnedModifiers.Count > 0) if (modifierCatalog != null && save.OwnedModifiers.Count > 0)
{ {
var entries = new List<ModifierSaveEntry>(); var entries = new List<ModifierSaveEntry>();
var permanentIds = new HashSet<string>(); var permanentIds = new HashSet<string>();
@@ -147,7 +133,7 @@ namespace YachtDice.UI
for (int i = 0; i < save.OwnedModifiers.Count; i++) for (int i = 0; i < save.OwnedModifiers.Count; i++)
{ {
var oldEntry = save.OwnedModifiers[i]; var oldEntry = save.OwnedModifiers[i];
var def = catalog.FindById(oldEntry.ModifierId); var def = modifierCatalog.FindById(oldEntry.ModifierId);
if (def == null) if (def == null)
{ {
@@ -168,7 +154,7 @@ namespace YachtDice.UI
permanentIds.Add(def.Id); permanentIds.Add(def.Id);
} }
modifierRegistry.LoadSaveData(entries, catalog); modifierRegistry.LoadSaveData(entries, modifierCatalog);
shopModel.LoadPurchasedPermanentIds(permanentIds); shopModel.LoadPurchasedPermanentIds(permanentIds);
} }
} }
@@ -177,7 +163,7 @@ namespace YachtDice.UI
{ {
var save = new SaveData var save = new SaveData
{ {
Currency = currencyBank != null ? currencyBank.Balance : 0 Currency = currencyBank.Balance
}; };
var entries = modifierRegistry.GetSaveData(); var entries = modifierRegistry.GetSaveData();
@@ -277,28 +263,12 @@ namespace YachtDice.UI
private void HandleShopClicked() private void HandleShopClicked()
{ {
if (shopController != null) shopController.ToggleVisibility();
{
var shopView = shopController.GetComponentInChildren<ShopView>(true);
if (shopView != null)
{
if (shopView.IsVisible) shopView.Hide();
else shopView.Show();
}
}
} }
private void HandleInventoryClicked() private void HandleInventoryClicked()
{ {
if (inventoryController != null) inventoryController.ToggleVisibility();
{
var inventoryView = inventoryController.GetComponentInChildren<InventoryView>(true);
if (inventoryView != null)
{
if (inventoryView.IsVisible) inventoryView.Hide();
else inventoryView.Show();
}
}
} }
private void HandleCurrencyChanged(int newBalance) private void HandleCurrencyChanged(int newBalance)