びぼうろくってみんなやってる

みんなやってるからぼくもやる

TDD

  • UnityでTest書く

    • Window -> General -> TestRunner
    • EditModeタブ、[Create Test Assembly Folder]押す
  • 原則 RED→GREEN→REFACTER→RED...

    • まずテストを書き、エラーになることを確認して、
    • 実装して、エラーを解消。
    • 実装をリファクタ
using NUnit.FrameWork;
using Unity.Engine;

public class HogeTest
{
  [Test]
  [TestCase(3)]
  [TestCase(6)]
  public void GetHogeTest(int num)
  {
    var hoge = new Hoge();
    var value = new hoge.GetHoge(num);
        Assert.AreEqual("Hoge", value);
  }
}

public class Hoge
{
  public string GetHoge(int num)
  {
    if(num % 3 == 0){ return "Hoge";}
    return "";
  }
}

なんかこんなかんじ

仮想抽象インターフェース

  • 使わなかったらすごい忘れそう

  • interface インターフェース

  • abstract 抽象
  • virtual 仮想

  • 以下大体の使い方

public interface IBaseClass
{
    int Level { get; }
    void SetLevel(int level);
}

public abstract class BaseClass : IBaseClass
{
    public int Level { get; private set; }

    public void SetLevel(int level)
    {
        Level = level;
    }

    protected virtual string Path { get; private set; }
    protected abstract string Name { get; }

    public virtual void TestVirtual()
    {
        Debug.Log("TestVirtual");
    }

    public abstract void TestAbstract();
}

public class SuperClass : BaseClass
{
    protected override string Path { get { return "Path"; } }
    protected override string Name { get { return "Name"; } }

    public override void TestVirtual()
    {
        base.TestVirtual();
        Debug.Log("TestSuperVirtual");
  }

    public override void TestAbstract()
    {
        Debug.Log("SuperAbstract");
    }
}

public class TestClass
{
    public TestClass()
    {
        //BaseClass baseClass = new BaseClass(); // ←抽象クラスはインスタンス作成できない。
    
        SuperClass superClass = new SuperClass();
        BaseClass baseClass = superClass; //これはできる
        IBaseClass baseInterface = superClass;  //これもできる

        //interfaceで定義したなら派生クラスでもつかえる
        var level = baseInterface.Level;
        level = baseClass.Level;
        level = superClass.Level;

        baseInterface.SetLevel(level);
        baseClass.SetLevel(level);
        superClass.SetLevel(level);

        //baseInterface.TestVirtual();//interfaceでは定義されていないのでできない
        baseClass.TestVirtual();//BaseClassでは定義されているので使える
        superClass.TestVirtual();//SuperClassでoverrideしたMethodを定義している場合、そっちを使う

        baseClass.TestAbstract();//baseClassに代入されているSuperClassのMethodが走る
        superClass.TestAbstract();//同様


    }
}

UniRxでUI等を監視する

//監視されるがわ
public class ObservableTest : MonoBehaviour
{  
    [SerializeField] Button button;
    public IObservable<Unit> OnClick { get { return button.OnClickAsObservable(); } }

  [SerializeField] Button returnTextButton;
  public IObservable<string> OnClickText 
  { 
    get
    { 
      return returnTextButton
      .OnClickAsObservable()
      .Select(_ => return "SelectでStringをとったり"); 
    }
  }
  
    [SerializeField] Slider slider;
    public IObservable<float> OnSlide { get { return slider.OnValueChangedAsObservable(); } }

    [SerializeField] Toggle toggle;
    public IObservable<bool> OnToggle { get { return toggle.OnValueChangedAsObservable(); } }

    public IObservable<Unit> OnStart { get { return onStart; } }
    Subject<Unit> onStart = new Subject<Unit>();

    void Start()
    {
        onStart.OnNext(Unit.Default);
    }
}

//監視する側
public class ObserverTest
{
    public void Init(ObservableTest observable)
    {
        //一行なら{}を書かなくても良い
        observable.OnClick.Subscribe(_ => Debug.Log("OnClick"));
        observable.OnSlide.Subscribe(f => Debug.Log("OnSlide:" + f.ToString()));
        observable.OnToggle.Subscribe(b => Debug.Log("OnToggle:" + b.ToString()));
        observable.OnStart.Subscribe(_ => Debug.Log("Start"));
    }
}

UniRx ReactiveProperty めも

  • 下記のようなやつを
public interface IReactiveTestModel
{
  IReadOnlyReactiveProperty<int> Num { get; }
  IReadOnlyReactiveProperty<string> Text { get; }

  void UpdateProperties(int updateNum, string updateText)
}

public class ReactiveTestModel : IReactiveTestModel
{
  public IReadOnlyReactiveProperty<int> Num { get {return num;} }
  public IReadOnlyReactiveProperty<string> Text { get {return text;} }
  
  IntReactiveProperty num = new IntReactiveProperty();
  StringReactiveProperty text = new StringReactiveProperty();

  public ReactiveTest(int initNum, string initText)
  {
    num.Value = initNum;
    text.Value = initText;
  }

  public void UpdateProperties(int updateNum, int updateText)
  {
    num.Value = updateNum;
    text.Value = updateText;
  }
}
  • 下記のように使う
public class ReactiveTest : MonoBehaviour
{
  [SerializeField] Text level;
  [SerializeField] Text description;

  IReactiveTestModel model;
  void Init(IReactiveTestModel model)
  {
    this.model = model;
    
    model.num.Subscribe(n => {level.text = n.ToString(); });
    model.text.Subscribe(t => { description.text = t; });
  }

  void SetModelValues(int currentLevel, string currentDescription)
  {
    model.UpdateProperties(currentLevel, currentDescription);
  }
}
  • Modelの数値の変化を監視して、それを反映させることができる。
  • 他にもReactiveCollectionとかいろいろある。

ScriptableObjectを使ってBuildProfileを簡単に作って、保存して、ビルド時に使い分けられるようにしたかったときのこと

  • ScriptableObjectでPlayerSettingsに入れる値を保存する
  • ただの箱
  • これを継承した各プラットフォーム用のProfileClassを作成
  • androidのKeyStoreName等
public class BuildProfile : ScriptableObject
{
  public string artifactName;//生成ファイルの名前
  public string bundleIdentifier;
  public string bundleVersion;
  public string productName;
  public string companyName;
  public bool release;
  public List<string> defineSymbols;
  public string iconPath;
  public int accelerometerFrequency;
  public List<string> ignorePaths;//どけときたいフォルダ
}
  • EditorWindowで簡単に扱えるようにしたい
  • BuildProfileのリストを持ち、Build実行や、中身の確認をするEditorWindowを開くためのWindow
public class ProfileWindow : EditorWindow
{
  [MenuItem("File/BuildProfiles")]
  static void Init()
  {
    GetWindow<ProfileWindow>("BuildProfiles");
  }

  void OnGUI()
  {
    //例にアンドロイド.
    isAndroidFoldOut = EditorGUILayout.Foldout(isAndroidFoldOut, "Android");

    if(isAndroidFoldOut)
    {
      EditorGUI.indentLevel++;
      foreach(var androidProfile in androidProfiles)
      {
        if(androidProfile == null) { RefreshBuildProfiles(); return; }
        ButtonLayout(androidProfile, BuildTarget.Android);
      }
      AddNewBuildProfileLayout(BuildTarget.Android);
      EditorGUI.indentLevel--;
    }
    GUILayout.EndVertical();
  }

  void ButtonLayout(BuildProfile buildProfile, BuildTarget buildTarget)
  {
    EditorGUILayout.BeginHorizontal();
    EditorGUILayout.LabelField(buildProfile.name);
    if(GUILayout.Button("Select"))
    {
      EditorGUIUtility.PingObject(buildProfile);
      Selection.objects = new[] { buildProfile };
    }
    if(GUILayout.Button("Build"))
    {
      Build(buildProfile, buildTarget);
    }
    EditorGUILayout.EndHorizontal();
  }
}
  • BuildProfileの中身を見るEditorWindowも欲しさ
    • PlayerSettingsの値と見比べて、違うなら強調表示するやつがほしかった
      • いわゆるPlaceHolder
    • それぞれのプラットフォームに合わせて継承したクラスも作る
public class ProfileEditor : EditorWindow
{
  public override void OnInspectorGUI()
  {
    EditorGUI.BeginChangeCheck();
    PropertyLayouter();
    ButtonLayouter();

    if(EditorGUI.EndChangeCheck())
    {
      EditorUtility.SetDirty(buildProfile);
    }
  }

  protected virtual void PropertyLayouter()
  {
    buildProfile.artifactName = PlaceHolderTextField("artifactName",buildProfile.artifactName,buildProfile.name);

    //以下それぞれのプロパティ書いていく
  }
  //このWindowでもビルドしたり、Profile選択Window開きたい的な
  protected virtual void ButtonLayouter()
  {
    if(GUILayout.Button("Open Window"))
    {
      EditorWindow.GetWindow<ProfileWindow>("BuildProfiles");
    }

    if(GUILayout.Button("Build " + buildTargetGroup.ToString()))
    {
      Build();
      GUIUtility.ExitGUI();
    }
  }

  protected string PlaceHolderTextField(string propertyName, string input, string placeHolder)
  {
    GUI.SetNextControlName(propertyName);
    if(!string.IsNullOrEmpty(input) || GUI.GetNameOfFocusedControl() == propertyName)
    {
      return EditorGUILayout.TextField(propertyName, input);
    }
    EditorGUILayout.TextField(propertyName, placeHolder, defaultStyle);

    return input;
  }
}
  • BuildPipeline.BuildPlayer()
  • ビルド中にScriptableObjectのインスタンスが破棄されるのでScriptableObjectにバックアップをとっておくとかはできない。

  • でもPlayerSettingsはバックアップを取りたい

  • ので、全部コピー。
using System.IO;

public class Builder
{
  const string PlayerSettingsPath = "ProjectSettings/ProjectSettings.asset";
  const string BackupPlayerSettingsPath = "Temp/BackupProjectSettings.asset";

  public void BuildWithProfile(BuildProfile profile)
  {
    //他にもapk等の保存先を作るとかする

    BackupProjectSettings();//バックアップ
    SetProfile();//BuildProfile適用
    Build();//実行
    RestorePlayerSetting();//もとに戻す

    //バージョニングとかもする
  }

  void BackupCurrentPlayerSetting()
  {
    if(!File.Exists(PlayerSettingsPath)) { Debug.Log("not Exist"); return; }
    File.Copy(PlayerSettingsPath, BackupPlayerSettingsPath, true);
  }
  void SetProfile()
  {
    //PlayerSettingsにBuildProfileを入れる処理
  }

  void Build()
  {
      //BuildPipeline.BuildPlayer()
  }

  void RestorePlayerSetting()
  {
    File.Copy(BackupPlayerSettingsPath, PlayerSettingsPath, true);
    File.Delete(BackupPlayerSettingsPath);
    AssetDatabase.Refresh();
    }
}

そういった次第だった