未分類

CAFUを使った設計導入

・CAFUとは?

CAFU は Clean Architecture を Unity 向けに実装したアーキテクチャフレームワークです。

作者様( @monry )ページ

https://github.com/umm/cafu_core

・yarnインストール方法

npmからインストール

npm install -g yarn

or

Macだとbrewが楽・・・!

brew update
brew install yarn

cd Unityプロジェクトのパス

・yarnからCAFUインストール


yarn init
yarn add "umm/cafu_core#^3.0.0"
yarn add "umm/cafu_generics"
yarn add "umm/cafu_generator"
yarn install

・Unityのプロジェクトを開く

BuildSetting > PlayerSettings > OtherSetting > Configuration .NET 4.x
を選択する

 

■各レイヤーの概要

 

 

Presenter:

Viewからイベントを受け取り、UseCaseに問い合わせを行うためのレイヤー

Sceneと1:1で紐づく。

各種ViewやUseCaseにつなぎこむHubのような役割を果たす

staticなInstanceを持ち各種Viewからアクセスができる。

UseCaseは複数あり、必要に応じたUseCaseを選択する。

また、UseCaseからの返答結果をViewに渡す。

(Observerパターンを使う)

 

Controller:

Presenterをコントロールするためのオブジェクト

シングルトンなInstanceを外部に公開している。

Sceneと1:1で紐づく。

 

View:

ユーザーからの操作を受け取りPresenterに渡す。

また、Presenterから通知を受けて表示をUpdateする。

 

UseCase:

機能の実装はここ!

実際に計算を行い、得られた結果をObserverパターンを使って通知する。

(ここではObserverパターンを使うことを想定して書いています。)

また、RepositoryからEntityを取得したり、
Translaterを使ってModelを取得したりする。

 

Repository:

UseCaseなどから問い合わせを受けた時にDataStoreへの問い合わせを行う

Domain層とData層のインターフェイスにあたる。

 

参考にしました! @karamem0 さんは以下の記事にてRepositoryパターンの適用例を記載しています。

 

DataStore:

サーバー通信、もしくはキャッシュ、ローカルの静的なデータ(Entity)を取得する。

取得したEntityをRepositoryを経由してUseCaseへ返す。

 

Entity:

データ構造の定義が行われている。

静的なモデルデータ,及びサーバー通信で取得したキャッシュを入れて置くための入れ物

※アプリ中で直接使われることはなく、Translater層で適切なModelへと加工されて使用される。

Translater:

UseCaseから値を受け取りModelへと加工する

加工したModelはUseCaseへ渡す

 

■試しに作ってみる!

今回はこれ!タイマー!!

・こんな感じのコードになりました〜!

 

TimerSamplePresenter.cs


using CAFU.Core.Presentation.Presenter;
using Domain.UseCase;

namespace Presentation.Presenter
{
    public class TimerSamplePresenter : IPresenter
    {
        ITimerUseCase timerUseCase { get; set; }
        public void AddTime(float addTime){
            timerUseCase.AddTime(addTime);
        }
        public string TimerDisplayText{
            get{
                return timerUseCase.CurrentTimeDisplayText;
            }
        }
        public UniRx.Subject EndTimeSubject{
            get{
                return timerUseCase.EndTimeSubject;
            }
        }


        public class Factory : DefaultPresenterFactory
        {
            protected override void Initialize(TimerSamplePresenter instance)
            {
                base.Initialize(instance);
                instance.timerUseCase = new TimerUseCase.Factory().Create();
            }
        }
    }
}

 

Controller.cs


using CAFU.Core.Presentation.View;
using Presentation.Presenter;

namespace Presentation.View.TimerSample
{
    public class Controller : Controller<Controller, TimerSamplePresenter, TimerSamplePresenter.Factory>
    {
        protected override void OnStart()
        {
            base.OnStart();
        }
    }

    public static class ViewExtension
    {
        public static TimerSamplePresenter GetPresenter(this IView view)
        {
            return Controller.Instance.Presenter;
        }
    }
}

 

TimerView.cs


using CAFU.Core.Presentation.View;
using UnityEngine;
using UnityEngine.UI;
using UniRx;
using UniRx.Triggers;
using System;

namespace Presentation.View.TimerSample
{
    public class TimerView : MonoBehaviour, IView
    {
        [SerializeField]
        Text timerText;

        private void Start()
        {
            Run();
        }
        void Run()
        {
            IDisposable updateDisposable = null;
            updateDisposable = this.UpdateAsObservable().Subscribe(_ =>
            {
                this.GetPresenter().AddTime(Time.deltaTime);
                timerText.text = this.GetPresenter().TimerDisplayText;
            }, () => { }).AddTo(this.gameObject);
            this.GetPresenter().EndTimeSubject.Subscribe(_ =>
            {
                Debug.Log("Timer終了!!");
                updateDisposable.Dispose();
            }).AddTo(this.gameObject);

        }
    }
}

 

TimerUseCase.cs


using CAFU.Core.Domain.UseCase;
using UniRx;

namespace Domain.UseCase
{
    public interface ITimerUseCase : IUseCase
    {
        void AddTime(float addTime);
        Subject EndTimeSubject { get; }
        string CurrentTimeDisplayText { get; }
    }

    public class TimerUseCase : ITimerUseCase
    {
        GameTimerModel gameTimerModel;
        public string CurrentTimeDisplayText
        {
            get
            {
                int min = (int)(gameTimerModel.RemainTime / 60);
                int second = (int)(gameTimerModel.RemainTime % 60);
                return min.ToString("00") + ":" + second.ToString("00");
            }
        }
        Subject endTimeSubject = new Subject();
        public Subject EndTimeSubject
        {
            get
            {
                return endTimeSubject;
            }
        }

        public void AddTime(float addTime){
            gameTimerModel.AddCurrentTime(addTime);
            if (gameTimerModel.RemainTime <= 0)
            {
                endTimeSubject.OnNext(new Unit());
                endTimeSubject.OnCompleted();
            }
        }

        public class Factory : DefaultUseCaseFactory
        {
            protected override void Initialize(TimerUseCase instance)
            {
                base.Initialize(instance);
                //TODO ここはRepositoryから取得する!
                float max_time = 10;
                instance.gameTimerModel = new GameTimerTranslater().Create(max_time);
            }
        }
    }
}

 

※ちょっとここは、CAFUのコードジェネレーターで作ったものとは違いますが・・・・!

GameTimerModel.cs


public class GameTimerModel{
    public float MaxTime{
        get{
            return maxTime;
        }
    }
    float maxTime;
    public float CurrentTime
    {
        get
        {
            return currentTime;
        }
    }
    float currentTime;

    public float RemainTime{
        get{
            return maxTime - currentTime;
        }
    }


    //最大時間
    //現在時間
    public GameTimerModel(float maxTime){
        this.maxTime = maxTime;
        currentTime = 0;
    }
    public void AddCurrentTime(float addTime){
        currentTime += addTime;
    }

}

 

※ちょっとここは、CAFUのコードジェネレーターで作ったものとは違いますが・・・・!

GameTimerTranslater.cs


public class GameTimerTranslater{
    //FactryMesthodを用いてModelを生成する
    public GameTimerModel Create(float maxTime){
        return new GameTimerModel(maxTime);
    }
}

 

 

■次回やりたいこと!

・完成版イメージ

こんな感じのミニゲームを設計に沿って作ってみたい!