
ããã«ã¡ã¯ ã²ãŒã éçºã®ã¢ãŒããã¯ãã£ã«é¢ããèšäºãæžããŠããŸãã ãã®èšäºã§ã¯ã ã³ãã³ããã¿ãŒã³ãè§£æããŸãã å€é¢çã§ãããããŸããŸãªæ¹æ³ã§é©çšã§ããŸãã ããããã²ãŒã ã®ç¶æ
倿Žããããã°ããããã®ã¿ã€ã ãã·ã³ã§ãããç§ã®ãæ°ã«å
¥ãã®ããªãã¯ãè¡ãæ¹æ³ã玹ä»ããŸãã
ãã®ããšã«ãããè€éãªãã°ãèŠã€ããŠåçããæéã倧å¹
ã«ççž®ãããŸããã ã²ãŒã ã®ç¶æ
ã倿Žã®å±¥æŽã®ãã¹ãããã·ã§ããããäœæããããããæ®µéçã«é©çšã§ããŸãã
åå¿è
ã®éçºè
ã¯ãã®ãã¿ãŒã³ã«æ
£ããã§ãããããäžçŽã®éçºè
ã¯ãã®ããªãã¯ã圹ç«ã€ãšæããããããŸããã
ãããè¡ãæ¹æ³ãç¥ãããã§ããïŒ ç«ããé¡ãããŸãã
æ¢ã«ã³ãã³ããã¿ãŒã³ã«ç²ŸéããŠããå Žåã¯ããåæ¹åã®ç¶æ
倿Žã®äœæãã»ã¯ã·ã§ã³ã«çŽæ¥é²ãã§ãã ããã
ã³ãã³ããã¿ãŒã³
ãããŒã ããšããèšèã¯ã©ãããæå³ã§ããïŒ ããã¯æ³šæã®ãããªãã®ã§ãã ããŒã ã®å©ããåããŠã人ã¯ã¢ã¯ã·ã§ã³ãå®è¡ããå¿
èŠæ§ã衚æããŸãã ã¢ã¯ã·ã§ã³ã¯ããŒã ããåãé¢ããŸããã
ã³ãã³ããã¿ãŒã³ã¯ããªããžã§ã¯ãæåããã°ã©ãã³ã°ã®äžçã§ã¢ã¯ã·ã§ã³ã衚çŸããæ¹æ³ã§ãã ãããŠããããå¯èœã«ãªãã®ã¯ããªã¢ãŒãã£ãºã ã®ãããã§ãã
ãã¿ãŒã³ã®èãæ¹ã¯ãã·ã¹ãã ã®ãã¹ãŠã®ã³ãã³ããåãã§ãããšããããšã§ãã OOPã«é¢ããŠã¯ããã¹ãŠã®ã³ãã³ãã«å
±éã®ã€ã³ã¿ãŒãã§ãŒã¹ããããŸãã ã·ã¹ãã ã¯ãããã®ãããããééçã«å®è¡ã§ããŸãã ããã¯ãããŒã ãå®å
šã«ç¬ç«ããå®è¡ã«å¿
èŠãªãã¹ãŠã®ããŒã¿ãããèªäœã§ã«ãã»ã«åããå¿
èŠãããããšãæå³ããŸãã
説æã¯éåžžã«æœè±¡çãªãã®ã§ãã 詳现ã«ç§»ããŸãããã ãã¹ãŠã®ã³ãã³ãã®åºæ¬ã€ã³ã¿ãŒãã§ãŒã¹ïŒ
public interface ICommand { void Execute(); }
次ã«ãã³ãã³ãã®å
·äœçãªå®è£
ã®äŸã瀺ããŸãã
public class WriteToConsoleCommand : ICommand { public string Message { get; private set; } public void Execute() { Console.WriteLine(Message); } }
ããã¯ãããŒã ã®äžçš®ã®ãHello Worldãã§ãã ããããããããå®è¡ããæ¹æ³ã¯ïŒ ç°¡åãªã³ãã³ãåŠçã·ã¹ãã ãäœæããŸãã
public interface IGameSystem { void Execute(ICommand cmd); } public class LoggableGameSystem : IGameSystem { public LoggableGameSystem(ILogger log) { _log = log; } public void Execute(ICommand cmd) { _log.Debug(string.Format("Executing command <{0}>: {1}", cmd.GetType(), cmd); cmd.Execute(); } private ILogger _log; }
ããã§ããããã°ã®ããã«ãã¹ãŠã®å®è¡å¯èœã³ãã³ããèšé²ã§ããŸãã 䟿å©ã§ããïŒ ãã ããããŒã ã¯ãããåºåã®ããã«æºåããå¿
èŠããããToStringïŒïŒã¡ãœããã远å ããŸãã
public class WriteToConsoleCommand : ICommand { public string Message { get; private set; } public void Execute() { Console.WriteLine(Message); } public override string ToString() { return Message; } }
ä»çµã¿ã確èªããŸãããã
class Program { static void Main(string[] args) { var gameSystem = new LoggableGameSystem(); var cmd = new WriteToConsoleCommand("Hello world"); var cmd2 = new WriteToConsoleCommand("Hello world2"); gameSystem.Execute(cmd); gameSystem.Execute(cmd2); } }
ããã¯éåžžã«åçŽãªäŸã§ãã ãã¡ãããããã¥ãŒã®çµè«ã¯æçšã§ããããã®ãã¿ãŒã³ããä»ã«æçšãªãã®ãåŒãåºãããšãã§ãããã©ããã¯æããã§ã¯ãããŸããã
ç§ã®ãããžã§ã¯ãã§ã¯ãããã€ãã®çç±ã§ãã®ãã¿ãŒã³ãåžžã«äœ¿çšããŠããŸãã
- ããŒã ã¯ãå®è¡åã«å¿
èŠãªãã¹ãŠãä¿æããŸãã å®éã圌女ã¯äžå€ã®ãªããžã§ã¯ãã§ãã ãããã£ãŠããããã¯ãŒã¯çµç±ã§ã®éä¿¡ã¯ç°¡åã§ãã¯ã©ã€ã¢ã³ããšãµãŒããŒã®äž¡æ¹ã§çããå®è¡ã§ããŸãã ãã¡ãããããã¯ãåãå
¥åãã©ã¡ãŒã¿ãŒã§ãã¯ã©ã€ã¢ã³ããšãµãŒããŒã®äž¡æ¹ãåãçµæãæäŸããããšãåæãšããŠããŸãã
- ããŒã ã¯éåžžã«å°ããªããžãã¯ã§ãã äœæãçè§£ããããã°ãç°¡åã§ãã ã³ãã³ãã¯äžå€ã§ããã远å ã®äŸåé¢ä¿ãå«ãŸããŠããªãããããŠããããã¹ããç°¡åã«èšè¿°ã§ããŸãã
- è€éãªããžãã¹ããžãã¯ã¯ãäžé£ã®åçŽãªã³ãã³ãã䜿çšããŠç°¡åã«è¡šçŸã§ããŸãã ã³ãã³ãã¯ç°¡åã«åå©çšããé çªã«çµã¿ç«ãŠãããšãã§ããŸãã
- ããŒã ã¯ãå¿
èŠã«å¿ããŠããã§ãã¯ãã€ã³ããŸãã¯ãã©ã³ã¶ã¯ã·ã§ã³ã«ããããšãã§ããŸãã ããŒã¿ã®ç¶æ
ã®å€åãã³ãã³ãã«ãã£ãŠã®ã¿çºçããå Žåãããã«ãããããã°ãç°¡åã«ãªããããã°ã©ã ãçè§£ã§ããŸãã äœããå£ããå Žåããã€ã©ã®ã³ãã³ãããšã©ãŒã«ãªã£ããã远跡ã§ããŸãã 䟿å©ãªãã®-ã³ãã³ããå®è¡ãããã©ã¡ãŒã¿ãŒã確èªã§ããŸãã
- ã³ãã³ãã®å®è¡ãé
ããå ŽåããããŸãã å
žåçãªäŸã¯ãã³ãã³ãããµãŒããŒã«éä¿¡ããããšã§ãã ãŠãŒã¶ãŒãã²ãŒã ã§ã¢ã¯ã·ã§ã³ãéå§ãããšãããŒã ãäœæãããå®è¡ãã¥ãŒã«è¿œå ãããŸãã ã³ãã³ãã®å®éã®å®è¡ã¯ããµãŒããŒããã®ç¢ºèªåŸã«ã®ã¿è¡ãããŸãã
- ããŒã ã¯ãã¹ãŠã®äŸåé¢ä¿ããååã«æœè±¡åãããŠãããããã¢ãŒããã¯ãã£ã®å€æŽã¯ç°¡åã§ãã ããšãã°ã以åã®ã³ãŒãããªãã©ã€ã³ã®ã¿ã§ãAIãããŒã«ã«ã§ã®ã¿å¶åŸ¡ãããŠããå ŽåããµãŒããŒã䜿çšããŠAIãå¶åŸ¡ããããã«ç°¡åã«å€æŽã§ããŸãã çµå±ã®ãšãããã³ãŒãã¯èª°ãã³ãã³ããããŒã«ã«ã³ãŒãããŸãã¯ãµãŒããŒãéä¿¡ããŠãããŸããŸããã
- ã³ãã³ãã®æ¢ç¥ã®æ©èœ-倿Žãé©çšã§ããã ãã§ãªããã¢ã¯ã·ã§ã³ã®ããã£ã³ã»ã«ãããµããŒãããããšãã§ããŸã
- ã³ãã³ãã€ããªãã®ãŒã§èšè¿°ãããã³ãŒãã¯ãåŸæ¥ã®é¢æ°åŒã³åºãã¢ãããŒããšã¯å°ãç°ãªããŸãã ããã°ã©ããŒãã³ãã³ããäœæãããšãç¶æ
ã倿Žããå¿
èŠãããããšãåããŸãã ããããã€ã©ã®ããã«è¡ãããã-圌ã¯èå³ããããŸããã ããã«ãããé¢çœãããšãã§ããŸãã
æåŸã®ãã€ã³ãã«ã€ããŠããå°ãã ããšãã°ãéåæã«ãªãã¯ãã®åæé¢æ°ããããŸããã ãããè¡ãã«ã¯ããã®çœ²åã倿Žããã³ãŒã«ããã¯ãã³ã«ãŒãã³ããŸãã¯éåæ/åŸ
æ©ã®åœ¢åŒã§éåæçµæãåŠçããã¡ã«ããºã ãèšè¿°ããå¿
èŠããããŸãïŒ.net 4.6ã«ã¯ããŒã«ããå ŽåïŒã ãããŠæ¯åãåã
ã®æ©èœããšã«ã
ã³ãã³ãã¡ã«ããºã ã䜿çšãããšãå®è¡ã¡ã«ããºã ããæœè±¡åã§ããŸãã ãããã£ãŠãã³ãã³ãã以åã«å³åº§ã«å®è¡ãããå Žåãç°¡åã«éåæã«ããããšãã§ããŸãã å®è¡æã«åçã«å€æŽããããšãã§ããŸãã
å
·äœäŸã ã²ãŒã ã¯éšåçãªãªãã©ã€ã³ããµããŒãããŠããŸãã ãããã¯ãŒã¯æ¥ç¶ãçŸåšå©çšã§ããªãå Žåãã³ãã³ãã¯ãã¥ãŒã«å
¥ããããæ¥ç¶ã埩å
ããããšãã«å®è¡ãããŸãã æ¥ç¶ãããå Žåãã³ãã³ãã¯å³åº§ã«å®è¡ãããŸãã
ã³ãã³ãã䜿çšããããžãã¯ããã®ç¶æ
ã®ç°¡åãªåé¢
çè«
ãã®ã¢ã€ãã ã¯ããã¿ã€ã ãã·ã³ãã®å®è£
ã«ã¯å¿
èŠãããŸããããUIã®åå¿æ§ã¯ãããã°äžã«ã圹ç«ã€ãããæçšã§ãã
ãŸããUIãããžãã¯ããç°¡åã«åãé¢ãããšã«ã€ããŠã話ããããã£ãã®ã§ãã Unityã¯MVVMãå«ãããŸããŸãªãã¿ãŒã³ãé©çšããããã«ã¯å€ãã®ãã¬ãŒã ã¯ãŒã¯ããããŸãã ããããäžè¬çã«ãããã¯UIã«ã€ããŠã§ã¯ãªããç¶æ
èªäœã®å€æŽã«ã€ããŠã§ãã
äžè¬çãªæŠå¿µãèŠãŠãèªåã§ç°¡åãªã·ã¹ãã ãæ§ç¯ããŠã¿ãŸãããã
ç¶æ
ã®ã©ã®ãããªäžæ¹åã®å€æŽã§ããïŒ ãã®ã¢ã€ãã¢ã¯ãFacebookã®ä»²éã説æããFluxã¢ãããŒãããåçšããŠããŸãã Reduxã®ãããªãã¹ãŠã®æ°ããã©ã€ãã©ãªã¯ãåãã¢ãããŒãã§æ§ç¯ãããŠããŸãã
åŸæ¥ã®MV *ã¢ãããŒãã§ã¯ãViewã¯ã¢ãã«ãšçžäºã«å¯Ÿè©±ããŸãã
Unityã§ã¯ãç¶æ³ã¯ãã°ãã°ããã«æªåããŸãã åŸæ¥ã®MVCã¯ããã§ã¯é©åã§ã¯ãªããããŒã¿ã¯å€ãã®å Žåã以äžã«ç€ºãããã«ãã¥ãŒããçŽæ¥å€æŽãããŸãã è€éãªã¢ããªã±ãŒã·ã§ã³ã§ã¯ãæ¥ç¶ã®æ°ã屿 ¹ãééããæŽæ°ã§æŽæ°ã倱ããããã¹ãŠãæ··ä¹±ããã¹ãã²ããã£ãçºçããŸãã

ïŒãœãŒã¹ïŒ medium.com ïŒ
Reduxã«äŒŒãã·ã¹ãã ã詊ããŠã¿ãããšããå§ãããŸãã äž»ãªã¢ã€ãã¢ã¯ãReduxãã¢ããªã±ãŒã·ã§ã³ã®ãã¹ãŠã®ç¶æ
ã1ã€ã®ãªããžã§ã¯ãã«ä¿åããããšãæäŸãããšããããšã§ãã ãããäžã€ã®ã¢ãã«ã§ãã
ããã€ãã¯ããã§æãããã§ãã ããããçµå±ã®ãšãããã²ãŒã ã®ç¶æ
ã®ã·ãªã¢ã«åã¯ãå€ãã®å Žåã1ã€ã®ãªããžã§ã¯ãã®ã·ãªã¢ã«åã«åž°çããŸãã ããã¯éåžžã«èªç¶ãªã²ãŒã ã®ã¢ãããŒãã§ãã
2çªç®ã®ã¢ã€ãã¢ã¯ãã¢ã¯ã·ã§ã³ã䜿çšããŠç¶æ
ã倿Žããããšã§ãã å®éãããã¯åè¿°ã®ã³ãã³ããšãŸã£ããåãã§ãã ãã¥ãŒã¯ç¶æ
ãçŽæ¥å€æŽããããšã¯ã§ããŸããããã³ãã³ããä»ããŠã®ã¿å€æŽã§ããŸãã
3çªç®ã®ã¢ã€ãã¢ã¯èªç¶ãªç¶ç¶ã§ãããViewã¯ç¶æ
ãèªã¿åãããã®æŽæ°ããµãã¹ã¯ã©ã€ãããããšããã§ããŸããã
Fluxã€ããªãã®ãŒã§ã¯æ¬¡ã®ããã«ãªããŸãã

ïŒãœãŒã¹ïŒ medium.com ïŒ
ç§ãã¡ã®å Žåãã¹ãã¢ã¯ã²ãŒã ã®ç¶æ
ã§ãã ãããŠãã¢ã¯ã·ã§ã³ã¯ããŒã ã§ãã Dispatcherã¯ãããããã³ãã³ããå®è¡ãããã®ã§ãã
ãã®ã¢ãããŒãã¯å€ãã®å©ç¹ããããããŸãã ç¶æ
ãªããžã§ã¯ãã¯1ã€ãããªãããã®å€æŽã¯ã³ãã³ããä»ããŠã®ã¿å®è¡ããããããåäžã®ç¶æ
æŽæ°ã€ãã³ããç°¡åã«äœæã§ããŸãã
ãã®åŸãUIãç°¡åã«ãªã¢ã¯ãã£ãã«ã§ããŸãã ã€ãŸããç¶æ
ãæŽæ°ãããšãã«èªåçã«ããŒã¿ãæŽæ°ããŸãïŒhello UniRx ããã®äœ¿çšã«ã€ããŠã¯å¥ã®èšäºã§èª¬æããŸãïŒã
ãã®ã¢ãããŒãã§ã¯ãã²ãŒã ã®ç¶æ
ã®å€æŽããµãŒããŒåŽããéå§ããããšãã§ããŸãã ãŸããããŒã ãéããŠã æŽæ°ã€ãã³ãã¯ãŸã£ããåãã§ãããããUIã¯å®å
šã«çŽ«è²ã§ãããæŽæ°å
ã¯ã©ãã§ããã
ãã1ã€ã®è¯ãç¹ã¯ãã¯ãŒã«ãªãããã°æ©èœã§ãã Viewã¯ããŒã ã«ããåºç£ã§ããªãã®ã§ãèžããã«ãã®ç¶æ
ã®å€åã远跡ãããããªããŸãã
詳现ãªãã°ãã³ãã³ãå±¥æŽããã°ã®åçŸãªã©ããã®ãã¹ãŠããã®ãã¿ãŒã³ã®ãããã§å¯èœã«ãªããŸãã
å®è£
ãŸããã²ãŒã ã®ç¶æ
ã倿ããŸãããã æ¬¡ã®ã¯ã©ã¹ã«ããŸãããïŒ
[System.Serializable] public class GameState { public int coins; }
ã²ãŒã ã®ç¶æ
ã®ä¿åãJSONãã¡ã€ã«ã«è¿œå ããŸãã ãããè¡ãã«ã¯ãå¥ã®ãããŒãžã£ãŒãäœæããŸãã
public interface IGameStateManager { GameState GameState { get; set; } void Load(); void Save(); } public class LocalGameStateManager : IGameStateManager { public GameState GameState { get; set; } public void Load() { if (!File.Exists(GAME_STATE_PATH)) { return; } GameState = JsonUtility.FromJson<GameState>(File.ReadAllText(GAME_STATE_PATH)); } public void Save() { File.WriteAllText(GAME_STATE_PATH, JsonUtility.ToJson(GameState)); } private static readonly string GAME_STATE_PATH = Path.Combine(Application.persistentDataPath, "gameState.json"); }
ååã®èšäºã§ã¯ãäŸåé¢ä¿ã®åé¡ã«æ³šç®ããDependency InjectionïŒDIïŒãã¿ãŒã³ã«ã€ããŠèª¬æããŸããã ããã䜿çšããæéã§ãã
Unity3dã«ã¯ãã·ã³ãã«ã§äŸ¿å©ãªZenject DIãã¬ãŒã ã¯ãŒã¯ããããŸãã 䜿çšããŸãã ã€ã³ã¹ããŒã«ãšæ§æã¯éåžžã«ç°¡åã§ãããã¥ã¡ã³ãã§è©³çްã«èª¬æãããŠããŸãã ãããã£ãŠãããã«ãã€ã³ãã«ã IGameStateManagerã®ãã€ã³ãã£ã³ã°ã宣èšããŸãã
ããã¥ã¡ã³ãã«åŸã£ãŠã BindingsInstaller
ãšããMonoInstaller
ã€ã³ã¹ã¿ã³ã¹ãäœæããã·ãŒã³ã«è¿œå ããŸããã
public class BindingsInstaller : MonoInstaller<BindingsInstaller> { public override void InstallBindings() { Container.Bind<IGameStateManager>().To<LocalGameStateManager>().AsSingle(); Container.Bind<Loader>().FromNewComponentOnNewGameObject().NonLazy(); }
ãŸããã²ãŒã ã®èªã¿èŸŒã¿ãšçµäºãç£èŠããããŒããŒã³ã³ããŒãã³ãã®ãã€ã³ãã£ã³ã°ã远å ããŸããã
public class Loader : MonoBehaviour { [Inject] public void Init(IGameStateManager gameStateManager) { _gameStateManager = gameStateManager; } private void Awake() { Debug.Log("Loading started"); _gameStateManager.Load(); } private void OnApplicationQuit() { Debug.Log("Quitting application"); _gameStateManager.Save(); } private IGameStateManager _gameStateManager; }
ããŒããŒã¹ã¯ãªããã¯ãã²ãŒã ã®äžçªæåã«å®è¡ãããŸãã åºçºç¹ãšããŠäœ¿çšããŸãã ãŸããã²ãŒã ã®ç¶æ
ã®èªã¿èŸŒã¿ãšä¿åãç£èŠããã¹ã¯ãªãããšããŠã
次ã«ãUIã®æãåçŽãªãã¥ãŒããŸãšããŸãã
public class CoinsView : MonoBehaviour { public Text currencyText; [Inject] public void Init(IGameStateManager gameStateManager) { _gameStateManager = gameStateManager; UpdateView(); } public void AddCoins() { _gameStateManager.GameState.coins += Random.Range(1,100); UpdateView(); } public void RemoveCoins() { _gameStateManager.GameState.coins -= Random.Range(1,100); UpdateView(); } public void UpdateView() { currencyText.text = "Coins: " + _gameStateManager.GameState.coins; } private IGameStateManager _gameStateManager; }
ããã§ãä»»æã®æ°ã®ã³ã€ã³ã远å ããã³åé€ãã2ã€ã®æ¹æ³ã远å ããŸããã ã³ãŒãã§ããç®ã«ããæšæºçãªã¢ãããŒãã¯ãããžãã¹ããžãã¯ãçŽæ¥UIã«ããã·ã¥ããããšã§ãã
ã ãããããããªãã§ãã ãã:)ã ããããä»ã®ãšãããå°ããªãããã¿ã€ããæ©èœããããšã確èªããŸãããã

ãã¿ã³ãæ©èœããèµ·åæã«ç¶æ
ãä¿åããã³åŸ©å
ãããŸãã
ããã§ã¯ãã³ãŒãã確èªããŸãããã
GameStateã倿Žããå¥ã®ã¿ã€ãã®ããŒã ãäœæããŸãããã
public interface ICommand { } public interface IGameStateCommand : ICommand { void Execute(GameState gameState); }
åäžã®ã¿ã€ãã®ã³ãã³ãã瀺ãããã«ãå
±éã€ã³ã¿ãŒãã§ãŒã¹ã空ã«ããŸãã GameStateã倿Žããã³ãã³ãã®å Žåãç¶æ
ããã©ã¡ãŒã¿ãŒãšããŠåãåãExecuteã¡ãœããã瀺ããŸãã
å
ã»ã©ç€ºãããããªç¶æ
ã倿Žããã³ãã³ããå®è¡ãããµãŒãã¹ãäœæããŸãããã ããããã¿ã€ãã®ã³ãã³ãã«é©åããããã«ãã€ã³ã¿ãŒãã§ãŒã¹ãæ±çšåããŸãã
public interface ICommandsExecutor<TCommand> where TCommand: ICommand { void Execute(TCommand command); } public class GameStateCommandsExecutor : ICommandsExecutor<IGameStateCommand> { public GameStateCommandsExecutor(IGameStateManager gameStateManager) { _gameStateManager = gameStateManager; } public void Execute(IGameStateCommand command) { command.Execute(_gameStateManager.GameState); } private readonly IGameStateManager _gameStateManager; }
ãããŒãžã£ãŒãDIã«ç»é²ããŸãã
public class BindingsInstaller : MonoInstaller<BindingsInstaller> { public override void InstallBindings() { Container.Bind<IGameStateManager>().To<LocalGameStateManager>().AsSingle(); Container.Bind<Loader>().FromNewComponentOnNewGameObject().AsSingle().NonLazy(); // added this line Container.Bind<ICommandsExecutor<IGameStateCommand>>().To<GameStateCommandsExecutor>().AsSingle(); } }
次ã«ãããŒã èªäœã®å®è£
ãè¡ããŸãã
public class AddCoinsCommand : IGameStateCommand { public AddCoinsCommand(int amount) { _amount = amount; } public void Execute(GameState gameState) { gameState.coins += _amount; } private int _amount; }
ã³ãã³ãã䜿çšããããã«CoinsViewã倿ŽããŸãã
public class CoinsView : MonoBehaviour { public Text currencyText; [Inject] public void Init(IGameStateManager gameStateManager, ICommandsExecutor<IGameStateCommand> commandsExecutor) { _gameStateManager = gameStateManager; _commandsExecutor = commandsExecutor; UpdateView(); } public void AddCoins() { var cmd = new AddCoinsCommand(Random.Range(1, 100)); _commandsExecutor.Execute(cmd); UpdateView(); } public void RemoveCoins() { var cmd = new AddCoinsCommand(-Random.Range(1, 100)); _commandsExecutor.Execute(cmd); UpdateView(); } public void UpdateView() { currencyText.text = "Coins: " + _gameStateManager.GameState.coins; } private IGameStateManager _gameStateManager; private ICommandsExecutor<IGameStateCommand> _commandsExecutor; }
CoinsViewã¯GameStateãèªã¿åãå°çšãšããŠäœ¿çšããããã«ãªããŸããã ãŸããç¶æ
ã®å€æŽã¯ãã¹ãŠããŒã ãéããŠè¡ãããŸãã
ããã§åçãå°ç¡ãã«ããŠããã®ã¯ãUpdateViewã®æååŒã³åºãã§ãã 圌ã«é»è©±ããã®ãå¿ããããšãã§ããŸãã ãŸãã¯ãå¥ã®ãã¥ãŒããã³ãã³ããéä¿¡ããŠã¹ããŒã¿ã¹ãæŽæ°ã§ããŸãã
ICommandExecutor
ã¹ããŒã¿ã¹æŽæ°ã€ãã³ãã远å ããŸãã ããã«ãExecutorã®ã²ãŒã ç¶æ
ã³ãã³ãçšã«å¥ã®ã€ã³ã¿ãŒãã§ã€ã¹ãšã€ãªã¢ã¹ãäœæããŠããžã§ããªãã¯ã®äœåãªã¿ã€ããé衚瀺ã«ããŸãã
public interface ICommandsExecutor<TState, TCommand> { // added event event System.Action<TState> stateUpdated; void Execute(TCommand command); } public interface IGameStateCommandsExecutor : ICommandsExecutor<GameState, IGameStateCommand> { }
DIã§ç»é²ãæŽæ°ãã
public class BindingsInstaller : MonoInstaller<BindingsInstaller> { public override void InstallBindings() { Container.Bind<IGameStateManager>().To<LocalGameStateManager>().AsSingle(); Container.Bind<Loader>().FromNewComponentOnNewGameObject().AsSingle().NonLazy(); // updated this line Container.Bind<IGameStateCommandsExecutor>() .To<DefaultCommandsExecutor>().AsSingle(); } }
ã€ãã³ããDefaultCommandsExecutor
远å ããŸãã
public class DefaultCommandsExecutor : IGameStateCommandsExecutor { // this event added public event Action<GameState> stateUpdated { add { _stateUpdated += value; if (value != null) { value(_gameStateManager.GameState); } } remove { _stateUpdated -= value; } } public DefaultCommandsExecutor(IGameStateManager gameStateManager) { _gameStateManager = gameStateManager; } public void Execute(IGameStateCommand command) { command.Execute(_gameStateManager.GameState); // these lines added if (_stateUpdate != null) { _stateUpdated(_gameStateManager.GameState); } } private readonly IGameStateManager _gameStateManager; // this line added private Action<GameState> _stateUpdated; }
ã€ãã³ãã®å®è£
ã«æ³šæãæã䟡å€ããããŸãã executorã¯ã€ãã³ãå
ã§ã®ã¿ç¶æ
ããã¡ã³ãã«ããããããµãã¹ã¯ã©ã€ããããšãã«ããã«ãã«ããããšãéèŠã§ãã
æåŸã«ããã¥ãŒãæŽæ°ããŸãã
public class CoinsView : MonoBehaviour { public Text currencyText; [Inject] public void Init(IGameStateCommandsExecutor commandsExecutor) { _commandsExecutor = commandsExecutor; _commandsExecutor.stateUpdated += UpdateView; } public void AddCoins() { var cmd = new AddCoinsCommand(Random.Range(1, 100)); _commandsExecutor.Execute(cmd); } public void RemoveCoins() { var cmd = new AddCoinsCommand(-Random.Range(1, 100)); _commandsExecutor.Execute(cmd); } public void UpdateView(GameState gameState) { currencyText.text = "Coins: " + gameState.coins; } private void OnDestroy() { _commandsExecutor.stateUpdated -= UpdateView; } private IGameStateCommandsExecutor _commandsExecutor; }
IGameStateManager
ã¯GameStateããã©ã¡ãŒã¿ãŒãšããŠååŸãããããIGameStateManagerã¯Viewã«å¿
èŠãªããªããŸããã çŽ æŽããããäžèŠãªäŸåçãåãé€ããŸããïŒ UpdateViewèªäœã¯ã IGameStateCommandsExecutor
ã€ãã³ãã«ãµãã¹ã¯ã©ã€ãããŸãã ç¶æ
ãå€åãããšåŒã³åºãããŸãã ãŸããOnDestroyã®ã€ãã³ãããéäŒããããšãå¿ããªãã§ãã ããã
ãããã¢ãããŒãã§ãã ãšãŠããããã è€éã§ã¯ãããŸããã ä»ã§ã¯ãæã®ç¹å®ã®æ®µéã§ã®ã¿åçŸãããããæ¡ä»¶ã§ãããå Žæã§UpdateViewãåŒã³åºãããšãå¿ããããšã¯ã§ããŸããã
ããã åãåºãããŠå
ãžé²ããšãããã«å€ãã®å©ç¹ããããŸãã
ã³ãã³ãã®å±¥æŽãã¿ã€ã ãã·ã³ãšããŠäœ¿çšããŠãè€éãªããžãã¯ããããã°ããŸã
ãã°ãã©ã®ããã«ãã¹ãããŸããïŒ ã¢ããªã±ãŒã·ã§ã³ãèµ·åããæé ã«åŸã£ãŠãã°ãåçŸããŸãã å€ãã®å Žåããããã®æé ã¯æåã§å®è¡ãããUIã®æäœããã¿ã³ã®çªåºãããã¹ãŠã®äœæ¥ãè¡ãããŸãã
ãã°ãåçŽãªå ŽåããŸãã¯ãã°ãåçŸããããã®æ¡ä»¶ãç°¡åã«ç¹°ãè¿ãããšãã§ããã°ããã¹ãŠãæ£åžžã§ãã ãããããã°ããããã¯ãŒã¯ããžãã¯ãšæéã«é¢ä¿ããŠããå Žåã¯ã©ãã§ãããã ããšãã°ãã²ãŒã ã«ã¯10åéç¶ãã€ãã³ãããããŸãã ãã°ã¯ã€ãã³ãã®æåŸã«çºçããŸãã
åãã¹ãã®å埩ã«ã¯ãå°ãªããšã 10åããããŸãã éåžžãããã€ãã®å埩ãå¿
èŠã§ããããããã®éã§äœãã修埩ããå¿
èŠããããŸãã
äžèšã®ãã¿ãŒã³ã䜿çšããè峿·±ãããªãã¯ã玹ä»ããŸããããã«ãããé çã軜æžãããŸãã
åã®æ®µèœã®ã³ãŒãã§ã¯ããã°ãæããã«å
¥ã蟌ãã§ããŸãã çµå±ã®ãšãããã³ã€ã³ã®æ°ã¯è² ã«ãªãå¯èœæ§ããããŸãã ãã¡ããããã®ã±ãŒã¹ã¯æãé£ãããã®ãšã¯ã»ã©é ãã§ãããããªããè¯ãæ³ååãæã£ãŠããããšãé¡ã£ãŠããŸãã
ããžãã¯ãè€éã§ããã°ãæ¯ååçŸããã®ã¯é¢åã ãšæ³åããŠãã ããã ããããããã§ç§ãã¡ã¯ããŸãã¯ãã¹ã¿ãŒãå¶ç¶ãããèŠã€ããŸããã ãã®ãã°ããä¿åãã§ãããšãããã©ãã§ããããïŒ
ããªãã¯èªäœïŒã²ãŒã ã®éå§æã«ãã£ãç¶æ
ãšãã²ãŒã ã»ãã·ã§ã³äžã«ã³ãããããããŒã ã®å
šå±¥æŽãä¿åããŸãããã
ãã®ããŒã¿ã¯ãäžç¬ã§å¿
èŠãªåæ°ã ããã°ãåçŸããã®ã«ååã§ãã ãã®å ŽåãUIãå®è¡ããå¿
èŠãããããŸããã çµå±ãå£ããç¶æ
ã®ãã¹ãŠã®å€æŽã¯å±¥æŽã«ä¿åãããŸãã å°ããªçµ±åãã¹ãã±ãŒã¹ã®ãããªãã®ã§ãã
å®è£
ã«ç§»ããŸãã ãã®ãœãªã¥ãŒã·ã§ã³ã«ã¯ãã€ã³ã¿ãŒãã§ã€ã¹ã®ã·ãªã¢ã«åãªã©ãå°ãé«åºŠãªã·ãªã¢ã«åãå«ãŸãããããJsonUtilityã§ã¯ååã§ã¯ãããŸããã ãããã£ãŠãUnityçšã®Json.Netãã¢ã»ããããé
眮ããŸãã
ãŸããã²ãŒã ã®ãåæãç¶æ
ãå¥ã®ãã¡ã€ã«ã«ã³ããŒããIGameStateManager
ãããã°ããŒãžã§ã³ãäœæããŸãããã ã€ãŸããã²ãŒã ãéå§ãããæç¹ã®ç¶æ
ã§ãã
public class DebugGameStateManager : LocalGameStateManager { public override void Load() { base.Load(); File.WriteAllText(BACKUP_GAMESTATE_PATH, JsonUtility.ToJson(GameState)); } public void SaveBackupAs(string name) { File.Copy( Path.Combine(Application.persistentDataPath, "gameStateBackup.json"), Path.Combine(Application.persistentDataPath, name + ".json"), true); } public void RestoreBackupState(string name) { var path = Path.Combine(Application.persistentDataPath, name + ".json"); Debug.Log("Restoring state from " + path); GameState = JsonUtility.FromJson<GameState>(File.ReadAllText(path)); } private static readonly string BACKUP_GAMESTATE_PATH = Path.Combine(Application.persistentDataPath, "gameStateBackup.json"); }
èå°è£ã§ã¯ã芪ã¯ã©ã¹ã®ã¡ãœãããä»®æ³ã«å€æããŸããã ããã¯æŒç¿ãšããŠæ®ããŠãããŸãã ä»ã®ãã¹ãŠã«ã SaveBackupAs
ã¡ãœãããSaveBackupAs
ãSaveBackupAs
ãããã¯ãå°æ¥çã«ç¹å®ã®ååã§ããã£ã¹ãããä¿åã§ããããã«ããããã«å¿
èŠã«ãªããŸãã
次ã«ãã³ãã³ãã®å±¥æŽãä¿åã§ãããšã°ãŒãã¥ãŒã¿ãŒã®ããã¥ãŒããŒãžã§ã³ãäœæããéåžžã¯ãåæç¶æ
+ããŒã ããšãã圢åŒã®å®å
šãªãã£ã¹ããä¿åããŸãã
public class DebugCommandsExecutor : DefaultCommandsExecutor { public IList<IGameStateCommand> commandsHistory { get { return _commands; } } public DebugCommandsExecutor(DebugGameStateManager gameStateManager) : base(gameStateManager) { _debugGameStateManager = gameStateManager; } public void SaveReplay(string name) { _debugGameStateManager.SaveBackupAs(name); File.WriteAllText(GetReplayFile(name), JsonConvert.SerializeObject(new CommandsHistory { commands = _commands }, _jsonSettings)); } public void LoadReplay(string name) { _debugGameStateManager.RestoreBackupState(name); _commands = JsonConvert.DeserializeObject<CommandsHistory>( File.ReadAllText(GetReplayFile(name)), _jsonSettings ).commands; _stateUpdated(_gameStateManager.GameState); } public void Replay(string name, int toIndex) { _debugGameStateManager.RestoreBackupState(name); LoadReplay(name); var history = _commands; _commands = new List<IGameStateCommand>(); for (int i = 0; i < Math.Min(toIndex, history.Count); ++i) { Execute(history[i]); } _commands = history; } private string GetReplayFile(string name) { return Path.Combine(Application.persistentDataPath, name + "_commands.json"); } public override void Execute(IGameStateCommand command) { _commands.Add(command); base.Execute(command); } private List<IGameStateCommand> _commands = new List<IGameStateCommand>(); public class CommandsHistory { public List<IGameStateCommand> commands; } private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }; private readonly DebugGameStateManager _debugGameStateManager; }
ããã§ã¯ãJsonUtilityã®æšæºæ©èœã§ã¯äžååã§ããããšã¯æããã§ãã ã·ãªã¢ã«åèšå®ã«TypeNameHandling
ãèšå®ããTypeNameHandling
ãŸãããããã«ããããã²ãããããŒã/ä¿åãããšãã«ãã³ãã³ããåä»ãããããªããžã§ã¯ãã«ã·ãªã¢ã«åè§£é€ãããŸããã
ãã®ãšã°ãŒãã¥ãŒã¿ãŒã«ã€ããŠä»ã«æ³šç®ãã¹ãããšã¯äœã§ããïŒ
- ãã¹ãŠã®ããŒã ãæŽå²ã«æ®ããŸã
- ããŒã ãšå·ã®è©Šåã®å±¥æŽãä¿åããã³åŸ©å
ã§ãã
- ããŒã®ãªãã¬ã€ã¡ãœããã¯ãã²ãŒã ã®åæç¶æ
ãããæå®ãããã€ã³ããã¯ã¹ãæã€ããŒã ãŸã§ããã¹ãŠã®ããŒã ãã倱ããŸãã
ãªãªãŒã¹ãããžã§ã¯ãã§ã¯ãå±¥æŽãã¡ã¢ãªããã£ã±ãã«ããããªããããDEBUGå®çŸ©ãããå Žåã«ã®ã¿ããã®ãµãŒãã¹ãDIã«ç»é²ããŸãã
public class BindingsInstaller : MonoInstaller<BindingsInstaller> { public override void InstallBindings() { Container.Bind<Loader>().FromNewComponentOnNewGameObject().AsSingle().NonLazy(); #if DEBUG Container.Bind<IGameStateManager>().To<DebugGameStateManager>().AsSingle(); Container.Bind<DebugGameStateManager>().AsSingle(); Container.Bind<IGameStateCommandsExecutor>().To<DebugCommandsExecutor>().AsSingle(); #else Container.Bind<IGameStateManager>().To<LocalGameStateManager>().AsSingle(); Container.Bind<IGameStateCommandsExecutor>().To<DefaultCommandsExecutor>().AsSingle(); #endif } }
ãããããã·ãªã¢ã«åã®ããã«ããŒã ãæºåããå¿
èŠããããŸãã
public class AddCoinsCommand : IGameStateCommand { public AddCoinsCommand(int amount) { _amount = amount; } public void Execute(GameState gameState) { gameState.coins += _amount; } public override string ToString() { return GetType().ToString() + " " + _amount; } [JsonProperty("amount")] private int _amount; }
JsonProperty, . ToString(), .
, "DEBUG" Player Settings -> Other Settings -> Scripting define symbols.
/ Unity. EditorWindow.
public class CommandsHistoryWindow : EditorWindow { [MenuItem("Window/CommandsHistoryWindow")] public static CommandsHistoryWindow GetOrCreateWindow() { var window = EditorWindow.GetWindow<CommandsHistoryWindow>(); window.titleContent = new GUIContent("CommandsHistoryWindow"); return window; } public void OnGUI() { // this part is required to get // DI context of the scene var sceneContext = GameObject.FindObjectOfType<SceneContext>(); if (sceneContext == null || sceneContext.Container == null) { return; } // this guard ensures that OnGUI runs only when IGameStateCommandExecutor exists // in other words only in runtime var executor = sceneContext.Container.TryResolve<IGameStateCommandsExecutor>() as DebugCommandsExecutor; if (executor == null) { return; } // general buttons to load and save "snapshot" EditorGUILayout.BeginHorizontal(); _replayName = EditorGUILayout.TextField("Replay name", _replayName); if (GUILayout.Button("Save")) { executor.SaveReplay(_replayName); } if (GUILayout.Button("Load")) { executor.LoadReplay(_replayName); } EditorGUILayout.EndHorizontal(); // and the main block which allows us to walk through commands step by step EditorGUILayout.LabelField("Commands: " + executor.commandsHistory.Count); for (int i = 0; i < executor.commandsHistory.Count; ++i) { var cmd = executor.commandsHistory[i]; EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(cmd.ToString()); if (GUILayout.Button("Step to")) { executor.Replay(_replayName, i + 1); } EditorGUILayout.EndHorizontal(); } } private string _replayName; }
. ?

"initial" , , .
, , , .
version1.
Step to, "" , .
. . " " , . , "negativeCoins" save.
, negativeCoins.json negativeCoins_commands.json, . , negativeCoins, Load . .
, , UI, . .
. , "", , , .
, .
public class AddCoinsCommand : IGameStateCommand { public AddCoinsCommand(int amount) { _amount = amount; } public void Execute(GameState gameState) { gameState.coins += _amount; // this is the fix if (gameState.coins < 0) { gameState.coins = 0; } } public override string ToString() { return GetType().ToString() + " " + _amount; } [JsonProperty("amount")] private int _amount; }
version1
, .

, . !
ãŸãšãããš
Command. . , .
:
UI, Flux, UI.
, , , , , .
, , , , . , /. =).
. , , UI, , . , GameStateManager'a, UI .
UI â , . , , . .
.
, =), .