Unity VFX Graph 準備編

VFXを初めて触る方向けのチュートリアル的なものです。

プロジェクト作成~VFX導入

Unity Hubを起動し、右上のNew Projectを選択

以下のように入力してCreateProjectを選択

  • Editor Version: 今回使いたいUnityのバージョン
  • 3D (URP)
  • Project name: 好きな名前
  • Location: Projectファイルが生成される場所(好きな場所)
  • Unity Cloud Organization: 自分のOrganization

少し待つとUnityが開く 色んなタブがあるが、一旦色々あるなあくらいで良い

画面左上のWindow > Package Manager を開く

右上の検索窓にVisual Effect Graphと入力し、出てきたPackageのInstallを選択
インストールが完了したらこの画面は閉じてOK

続いて、画面下部にあるProjectタブ内で右クリックし、
Create > Visual Effects > Visual Effect Graph
を選択。作られたファイルに好きな名前を付ける。

作られたファイルをダブルクリックすると、新しいタブが開かれる。

これで準備完了

おまけ

Projectタブ内で、右クリック > Create > Folder でフォルダが作成できるので上手に使ってください

参考

https://tech-blog.cloud-config.jp/2022-10-11-unity-vfx-part1

Unity シーン間でデータを受け渡す方法まとめ

Unityではシーンをまたぐときに全てのゲームオブジェクトが破棄されます。
そのため前シーンのデータを利用するには、データを受け渡す必要があります。

シーン間でデータを受け渡す方法

シーン間のデータの受け渡しはいくつか方法があります。

  • DontDestroyOnLoadを使う
  • SceneManager.sceneLoadedを使う
  • 非MonoBehaviourのstaticクラスを使う
  • 非MonoBehaviourのシングルトンを使う
  • PlayerPrefsを使う
  • ScriptableObjectを使う
  • ファイルに書き出す

他にもプラグインを用いた方法や、これらを組み合わせた方法があります。

ざっくりとした紹介

それぞれの詳細は別の記事として作成するので、ここでは比較用にざっくりと特徴を説明します。
※詳細記事は随時執筆中

DontDestroyOnLoadを使う

シーンをまたいでも破棄されないゲームオブジェクトを作成することで、データを受け渡します。

長所

  • インスペクターが利用できる
  • MonoBehaviourが利用できる

短所

  • どのシーンでも常に残り続けるため、シーン遷移が多い場合は不要なデータとして残り続ける
  • 同じシーンに戻ってきた際の扱いに注意しないと、重複する可能性がある
  • GameObject.Findやシングルトンとの併用が必要

SceneManager.sceneLoadedを使う

シーンロード完了時のイベントハンドラを設定することで、データを受け渡します。

長所

  • 遷移前のシーンで完結するため簡単

短所

  • 遷移前のシーンで完結するため、遷移前のシーンは次のシーンの状態を知っておく必要がある
  • 一つの関数の中で完結する必要がある

非MonoBehaviourのstaticクラスを使う

UnityでのプログラミングはMonoBehaviourを継承し、GameObjectにアタッチすることが多いです。
ですが、MonoBehaviourを使わずGameObjectにもアタッチしないコードも利用可能です。
GameObjectに依存していなければ、シーンをまたいでも破棄されることはありません。

しかし、GameObject.Find等でクラスを取得できなくなるので、どうやってそのクラスにアクセスするかが問題です。
その対応の一つが、MonoBehaviourを継承していないstaticクラスを用意するという方法です。
staticクラスは全てのクラスから自由にアクセスすることができます。

長所

  • 全シーン共通のデータをクラスでまとめて管理できる
  • 変数を書き換えるだけなので簡単

短所

  • どのクラスからもアクセスできるので危険
  • データが残り続けるので、初期化を忘れバグに繋がりやすい

詳細はこちら tmls.hatenablog.com

非MonoBehaviourのシングルトンクラスを使う

上記staticクラスはインタフェースや継承を使えなかったり、インスタンスを破棄して初期化するといったことはできません。
これらを改善したのが、シングルトンという設計です。
シングルトンを使うと、staticクラス同様に全てのクラスから自由にアクセスできます。

長所

  • staticクラスよりも柔軟な使い方が可能(詳しくはデザインパターンで調べてくださいmm)

短所

  • 管理が難しく、怠ると複雑なバグに繋がるケースがある

PlayerPrefsを使う

Unityが標準で用意しているデータ保存機能であるPlayerPrefsを使います。
シーン間というより、ゲームを終了しても残るプレイヤーデータになります。

長所

  • Unityの標準機能なので簡単

短所

  • 渡すデータの型がfloat, int, stringに限られる
  • ゲームを終了してもデータが残る

ScriptableObjectを使う

ScriptableObjectは各データをアセットとして書き出す機能です。 シーン間でのデータの受け渡しだけでなく、事前に初期値をインスペクターで設定することができます。

長所

  • 初期値の設定にインスペクターが使える
  • Findやインスタンスの受け渡しをせず、それぞれのシーンでデータのロードを行うだけで良い

短所

  • Serialize可能な型しか渡せない
  • 扱いが特殊で難しい
  • 初期化されるタイミングを熟知していないと、意図しないタイミングで初期化される

ファイルに書き出す

C#標準のファイル入出力を使うことで、データをファイルに書き出したり読み込むことができます。 基本的にはScriptableObjectに似た方法になります。

長所

  • 自由な形式でファイルに書き出せる

短所

  • ゲームを終了してもデータが残る
  • ファイルの書き出し、読み込み機能を理解する必要がある
  • シーン遷移の度ににファイルロードが走る

まとめるとこのようになります。
長所はデータが渡せるだけで十分とも言えるので、重要なのはどの短所をとるかでしょうか。

手法 長所 短所
DontDestroyOnLoad ・インスペクターが利用できる
・MonoBehaviourが利用できる
・どのシーンでも残り続ける
・同じシーンに戻ってきた際の扱いに注意
・GameObject.Findやシングルトンとの併用が必要
SceneManager.sceneLoaded 遷移前のシーンで完結する ・次のシーンの状態を知っておく必要がある
・一つの関数の中で完結する必要がある
非MonoBehaviourのstaticクラス ・全シーン共通のデータをクラスでまとめて管理できる
・実装が簡単
・どのクラスからもアクセスできるので危険
・初期化を忘れバグに繋がりやすい
非MonoBehaviourのシングルトンクラス staticクラスよりも柔軟 管理が難しい
PlayerPrefs Unityの標準機能なので簡単 ・渡すデータの型がfloat, int, stringに限られる
・ゲームを終了してもデータが残る
ScriptableObject ・初期値の設定にインスペクターが使える
・各シーンでデータのロードを行うだけで良い
・Serialize可能な型しか渡せない
・扱いが特殊で難しい
・意図しないタイミングで初期化されやすい
ファイルに書き出す 自由な形式でファイルに書き出せる ・ゲームを終了してもデータが残る
・ファイルの書き出し、読み込み機能を理解する必要がある
・シーン遷移の度ににファイルロードが走る

総括

一長一短があり、一概にどれが良いとは言えません。
おすすめとしては、初心者であればstaticクラス、慣れた人であればDontDestroyOnLoadが良いのではないでしょうか。

業務レベルでは単にデータを受け渡すだけでなく、これらを組み合わせてシーン遷移全体を管理するクラスを作ってしまうケースが多いように思います。
それについてはまた別途執筆できれば。

Unity staticクラスを使ってシーン間でデータを受け渡す

staticクラスを用いてシーン間でデータを受け渡す方法について紹介します。

今回は、SceneAからSceneBに遷移することを考えます。
まずは受け渡したいデータを含むstaticクラスを作成しましょう。

CommonData.cs

public static class CommonData
{
    public static int hoge = 1;
}

続いてシーンAでこの変数を書き換え、その後シーン遷移を行います。
今回はhogeを2に変更します。

SceneA.cs

using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneA : MonoBehaviour
{
    private void Start()
    {
        CommonData.hoge = 2;
        SceneManager.LoadScene("SceneB");
    }
}

シーンBでデータにアクセスし値を確認します。

SceneB.cs

using UnityEngine;

public class SceneB : MonoBehaviour
{
    private void Start()
    {
        Debug.Log(CommonData.hoge);
    }
}

各シーンの構成はこのようになっています。

SceneAを実行すると正しくデータを受け渡せていることが確認できました。

問題点

staticクラスを使ったデータの受け渡しは簡単ですが、以下のような問題点もあります。

どこからでもアクセスできる
どのクラスからでもアクセスができるので、乱用されたり意図せず変更されてしまいバグに繋がる可能性があります。
例えば今回の例ではSceneBでもhogeの値を書き換えることができてしまいます。

初期化を忘れがち
staticクラスはゲーム終了まで常にデータが保たれるので、再びSceneAに戻った際に初期化をしないと前回SceneBに渡したデータが残ったままになり、バグや混乱の元になりやすいです。

これらの問題は初心者の頃はある程度気にしなくても良いですが、徐々に意外と大事なことが分かってくるかと思います...

Unity Addressableの基本的な使い方

Addressableとは?

Addressable(アドレサブル)とは、一言でいうと「色んなアセットにアドレスをつけて管理できる機能」のことです。
ではなぜアドレスをつけると便利になるかというと、以下のようなメリットがあるためです。

  • アドレスを指定することで、スクリプトからアセットをロード/破棄できる
  • iOSAndroidのストアを更新しなくても、アセットのアプデを実現できる

また似た機能にAsset Bundle、Resourcesが存在しますが様々な問題があるため、多くの場合でAddressableが良いとされています。

基本の使い方

PackageManagerからAddressablesをインストールします。

画像等のアセットに対して、インスペクター最上部にAddressableのチェックボックスが現れます。

チェックを入れるとアドレスが表示されます。 Addressableは、このアドレスを使ってファイルをロードします。

スクリプトでファイルを読み込むには、以下のようなコードを書きます。

using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;

public class AddressableSample : MonoBehaviour
{
    private void Start()
    {
        var handle = Addressables.LoadAssetAsync<Sprite>("Assets/Images/Star.png");
        handle.Completed += handle =>
        {
            Debug.Log($"Sprite is loaded. {handle.Result}");
        };
    }
}

Addressables.LoadAssetAsyncは、引数に指定されたアドレスを非同期で読み込み<>で指定した型に変換します。
また、非同期のハンドルAsyncOperationHandleを戻り値として返します。
handle.Completedは、ロード完了時に実行される関数を設定することができます。

また、使い終わったら破棄するのを忘れないようにしましょう。
以下のコードをよしななタイミングで呼び出してください。

Addressables.Release(handle);

ちょっとだけ応用

非同期処理はyield returnを用いてコルーチンとして待機することもできます。

private void Start()
{
    StartCoroutine(LoadSpriteCoroutine());
}

private IEnumerator LoadSpriteCoroutine()
{
    var handle = Addressables.LoadAssetAsync<Sprite>("Assets/Images/Star.png");
    yield return handle;
    Debug.Log($"Sprite is loaded. {handle.Result}");
}

非同期処理はasync, awaitを用いて待機することもできます。

private void Start()
{
    LoadSpriteAsync();
}

async void LoadSpriteAsync()
{
    var sprite = await Addressables.LoadAssetAsync<Sprite>("Assets/Images/Star.png").Task;
    Debug.Log($"Sprite is loaded. {sprite}");
}

C#デザインパターンカタログ GoFの23パターン

GoFデザインパターン23種類をC#で書いたまとめ記事です。

本記事はGang of Fourの「オブジェクト指向における再利用のためのデザインパターン」で紹介されている23種類のデザインパターンを参考に、最小構成のC#サンプルコードを列挙しています。 どのデザインパターンを適用するか悩んだ際など、自由にご利用ください。

なお免責として、自分の理解の範囲なので悪しからず。内容に関する指摘は歓迎です。

デザインパターンと直接関係していないクラスや関数・変数に関しては、Some~, Hoge, Foo等の言葉を用いて表しています。

http://amazon.co.jp/オブジェクト指向における再利用のためのデザインパターン-エリック-ガンマ/dp/4797311126amazon.co.jp

GitHubで見たい方はこちら

Abstract Factory

Factory

public abstract class AbstractFactory
{
    public abstract Product CreateProduct();
}


public class ConcreteFactory1 : AbstractFactory
{
    public override Product CreateProduct()
    {
        return new ProductA();
    }
}

public class ConcreteFactory2 : AbstractFactory
{
    public override Product CreateProduct()
    {
        return new ProductB();
    }
}

Product

public abstract class Product
{
}

public class ProductA : Product
{
}

public class ProductB : Product
{
}

Some Application

public class SomeApplication
{
    private Product product;

    public void Init(AbstractFactory factory)
    {
        this.product = factory.CreateProduct();
    }
}

public class SomeApplicationUser
{
    public void SomeFunction(bool someCondition)
    {
        var someApp = new SomeApplication();

        if (someCondition)
        {
            var factory1 = new ConcreteFactory1();
            someApp.Init(factory1);
        }
        else
        {
            var factory2 = new ConcreteFactory2();
            someApp.Init(factory2);
        }
    }
}

Builder

Result

public class Result
{
    public void AddFoo()
    {
    }
                   
    public void RemoveFoo()
    {
    }
                   
    public void SetBar(int someParam)
    {
    }
}

Builder

public abstract class Builder
{
    public abstract void BuildPartA();
    public abstract void BuildPartB();
    public abstract void BuildPartC();
    public abstract Result GetResult();
}
                                                   
public class ConcreteBuilder : Builder
{
    private Result result = null;
                                                 
    public ConcreteBuilder()
    {
        this.result = new Result();
    }
                                                 
    public override void BuildPartA()
    {
        this.result.AddFoo();
    }
                                                 
    public override void BuildPartB()
    {
        this.result.AddFoo();
        this.result.SetBar(0);
    }
                                                   
    public override void BuildPartC()
    {
        this.result.RemoveFoo();
        this.result.SetBar(1);
    }
                                                   
    public override Result GetResult()
    {
        return result;
    }
}

Director

public class Director
{
    public void Construct(Builder builder)
    {
        builder.BuildPartA();
        builder.BuildPartB();
    }
                                                 
    public void ConstructHoge(Builder builder)
    {
        builder.BuildPartB();
        builder.BuildPartC();
    }
                                                 
    public void ConstructHuga(Builder builder)
    {
        builder.BuildPartA();
        builder.BuildPartB();
        builder.BuildPartC();
    }
}

Some Application

public class SomeApplication
{
    public void SomeFunction(int someCondition)
    {
        var builder = new ConcreteBuilder();
        var director = new Director();
                                                 
        switch (someCondition)
          {
            case 1:
                director.Construct(builder);
                break;
            case 2:
                director.ConstructHoge(builder);
                break;
            case 3:
                director.ConstructHuga(builder);
                break;
        }

        var result = builder.GetResult();
    }
}

Factory Method

・抽象クラスが、具象クラスによって決定されるインスタンスに興味がないとき
・具象クラスにインスタンスを作らせ、抽象クラスではそのインタフェース(抽象クラス)のみを扱う

最もシンプルな実装

Product

public abstract class Product
{
}
                                
public class ProductA : Product
{
}
                                
public class ProductB : Product
{
}

Product User

public abstract class ProductUser
{
    private Product product = null;
                                                                                             
    // factory method
    protected abstract Product CreateProduct();
                                                                                             
    public ProductUser()
    {
        // you should not call CreateProduct() here.
        // because you still cannot use concrete CreateProduct() in super class constructor.
    }
                                                                                             
    public void LazyInit()
    {
        this.product = CreateProduct();
    }
                                                                                             
    public void SomeFunction()
    {
    }
}
                                                                                             
public class ConcreteProductUser1 : ProductUser
{
    protected override Product CreateProduct()
    {
        return new ProductA();
    }
}
                                                                                             
public class ConcreteProductUser2 : ProductUser
{
    protected override Product CreateProduct()
    {
        return new ProductB();
    }
}

Some Application

public class SomeApplication
{
    public void SomeFunction(bool someCondition)
    {
        if (someCondition)
        {
            var user1 = new ConcreteProductUser1();
            user1.LazyInit();
            user1.SomeFunction();
        }
        else
        {
            var user2 = new ConcreteProductUser2();
            user2.LazyInit();
            user2.SomeFunction();
        }
    }
}

テンプレートを使う場合

public class StandartProductUser<T> : ProductUser where T : Product, new()
{
    protected override Product CreateProduct()
    {
        return new T();
    }
}
public class SomeApplication
{
    public void SomeFunction(bool someCondition)
    {
        if (someCondition)
        {
            var user1 = new StandartProductUser<ProductA>();
            user1.LazyInit();
            user1.SomeFunction();
        }
        else
        {
            var user2 = new StandartProductUser<ProductB>();
            user2.LazyInit();
            user2.SomeFunction();
        }
    }
}

Prototype

Product

public class Product
{
    public void Init(int someParam)
    {
    }
                                                       
    public virtual Product Clone()
    {
        return (Product)MemberwiseClone();
    }
}
                                                       
public class ProductB : Product
{
    public override Product Clone()
    {
        return (Product)MemberwiseClone();
    }
}

Prototype Factory

public class PrototypeFactory
{
    private Product prototypeProduct = null;
                                                       
    public PrototypeFactory(Product prototype)
    {
        this.prototypeProduct = prototype;
    }
                                                       
    public Product MakeProduct1()
    {
        var product = this.prototypeProduct.Clone();
        product.Init(1);
        return product;
    }
                                                       
    public Product MakeProduct2()
    {
        var product = this.prototypeProduct.Clone();
        product.Init(2);
        return product;
    }
}

Some Application

public class SomeApplication
{
    public void SomeFunction(bool someCondition)
    {
        Product prototype;
        if (someCondition)
        {
            prototype = new Product();
        }
        else
        {
            prototype = new ProductB();
        }
                                                       
        var factory = new PrototypeFactory(prototype);
                                                       
        var product1 = factory.MakeProduct1();
        var product2 = factory.MakeProduct2();
    }
}

Singleton

public class Singleton
{
    private static Singleton instance = null;
    public static Singleton Instance
    {
        get
        {
            if (instance == null)
                instance = new Singleton();
            return instance;
        }
    }
                                              
    protected Singleton()
    {
    }
}

Adapterパターン

  • 既存クラスを利用したいが、インタフェースが合わない
    Class Adapter
public class Adaptee
{
    public void HugaFunction()
    {
    }
}

public interface Target
{
    public void HogeFunction();
}

public class Adapter : Adaptee, Target
{
    public void HogeFunction()
    {
        this.HogeFunction();
    }
}
Object Adapter
public class Adaptee
{
    public void HugaFunction()
    {
    }
}

public class Target
{
    public virtual void HogeFunction()
    {
    }
}

public class Adapter : Target
{
    private Adaptee adaptee = null;

    public Adapter(Adaptee adaptee)
    {
        this.adaptee = adaptee;
    }

    public override void HogeFunction()
    {
        adaptee.HugaFunction();
    }
}

Bridge

  • ネストされた継承を解消したい
  • 実装の変更に依存させない
  • 実装をサブクラスの追加により拡張可能にすべき

Implementor

public abstract class Implementor
{
    public abstract void SomeFunctionImp();
}

public class ConcreteImplementorA : Implementor
{
    public override void SomeFunctionImp()
    {
    }
}

public class ConcreteImplementorB : Implementor
{
    public override void SomeFunctionImp()
    {
    }
}

Abstraction

public abstract class Abstraction
{
    private Implementor imp = null;

    public Abstraction(Implementor implementor)
    {
        this.imp = implementor;
    }

    public void SomeFunction()
    {
        this.imp.SomeFunctionImp();
    }
}

public class RefinedAbstraction : Abstraction
{
    public RefinedAbstraction(Implementor implementor) : base(implementor)
    {
    }

    public void SomeRefinedFunction()
    {
        SomeFunction();
        SomeFunction();
    }
}

Composite

Component

public abstract class Component
{
    public virtual void Add(Component component)
    {
    }

    public virtual void Remove(Component component)
    {
    }

    public virtual Component GetChild(int index)
    {
        return null;
    }

    public abstract void Operation();
}

Composite, Leaf

using System.Collections.Generic;

public class Composite : Component
{
    private List<Component> children = null;

    public Composite()
    {
        this.children = new List<Component>();
    }

    public override void Add(Component component)
    {
        children.Add(component);
    }

    public override void Remove(Component component)
    {
        children.Remove(component);
    }

    public override Component GetChild(int index)
    {
        if (index < children.Count)
        {
            return children[index];
        }

        return null;
    }

    public override void Operation()
    {
        foreach (var child in children)
        {
            child.Operation();
        }
    }
}

public class Leaf : Component
{
    public override void Operation()
    {
    }
}

Decorator

Component

public abstract class Component
{
    public abstract void Operation();
}

public class ConcreteComponent : Component
{
    public override void Operation()
    {
    }
}

Decorator

public abstract class Decorator : Component
{
    private Component component;

    public Decorator(Component component)
    {
        this.component = component;
    }

    public override void Operation()
    {
        component.Operation();
    }
}

public class ConcreteDecoratorA : Decorator
{
    private int someParam;

    public ConcreteDecoratorA(Component component) : base(component)
    {
    }

    public override void Operation()
    {
        base.Operation();
    }
}

public class ConcreteDecoratorB : Decorator
{
    public ConcreteDecoratorB(Component component) : base(component)
    {
    }

    public override void Operation()
    {
        base.Operation();
        SomeFunction();
    }

    public void SomeFunction()
    {
    }
}

SomeApplication

public class SomeApplication
{
    public void SomeFunction()
    {
        Component comp = new ConcreteComponent();
        Component deco1 = new ConcreteDecoratorA(comp);
        Component deco2 = new ConcreteDecoratorB(comp);
        Component deco3 = new ConcreteDecoratorA(new ConcreteDecoratorB(comp));
        Component deco4 = new ConcreteDecoratorA(new ConcreteDecoratorB(new ConcreteDecoratorB(comp)));
    }
}

Facade

・サブシステムの構成要素を隠ぺい及び結合を弱める

Facade

using SomeSubSystems;

public class Facade
{
    public void SomeFunction()
    {
        var subSystemA = new SubSystemA();
        var subSystemB = new SubSystemB();
        var subSystemC = new SubSystemC();

        subSystemA.FunctionA();
        subSystemB.FunctionB();
        subSystemC.FunctionC();
    }
}
namespace SomeSubSystems
{
    public class SubSystemA
    {
        public void FunctionA()
        {
        }
    }

    public class SubSystemB
    {
        public void FunctionB()
        {
        }
    }

    public class SubSystemC
    {
        public void FunctionC()
        {
        }
    }
}

Flyweight

Flyweight

public abstract class Flyweight
{
    public abstract void Operation(ExtrinsicState extrinsicState);
}

public class ConcreteFlyweight : Flyweight
{
    private int someIntrinsicState;

    public override void Operation(ExtrinsicState extrinsicState)
    {
    }
}

public class UnsharedConcreteFlyweight : Flyweight
{
    private int allState;

    public override void Operation(ExtrinsicState extrinsicState)
    {
    }
}

ExtrinsicState

public class ExtrinsicState
{
    public int someParam;
}

FlyweightFactory

using System.Collections.Generic;

public class FlyweightFactory
{
    private Dictionary<string, ConcreteFlyweight> flyweights;

    public FlyweightFactory()
    {
        flyweights = new Dictionary<string, ConcreteFlyweight>();
    }

    public ConcreteFlyweight CreateConcreteFlyweight(string key)
    {
        if (!flyweights.ContainsKey(key))
        {
            flyweights.Add(key, new ConcreteFlyweight());
        }

        return flyweights[key];
    }

    public UnsharedConcreteFlyweight CreateUnsharedConcreteFlyweight()
    {
        return new UnsharedConcreteFlyweight();
    }
}

Some Application

public class SomeApplication
{
    public void SomeFuncition()
    {
        var factory = new FlyweightFactory();
        var exState = new ExtrinsicState();

        for (int i = 0; i < 100; i++)
        {
            // create 100 foo
            var concreteFlyweight = factory.CreateConcreteFlyweight("foo");
            exState.someParam = i;
            concreteFlyweight.Operation(exState);
        }

        for (int i = 0; i < 30; i++)
        {
            // create 30 bar
            var concreteFlyweight = factory.CreateConcreteFlyweight("bar");
            exState.someParam = i;
            concreteFlyweight.Operation(exState);
        }

        var unsharedFlyweight = factory.CreateUnsharedConcreteFlyweight();
        unsharedFlyweight.Operation(exState);
    }
}

Proxy

Subject

public abstract class Subject
{
    public abstract void Request();
}

public class RealSubject : Subject
{
    public override void Request()
    {
    }
}
public class Proxy : Subject
{
    RealSubject realSubject = null;

    public override void Request()
    {
        // you can manage how to access here
        realSubject.Request();
    }
}

Chain of Responsibility

・受け手を明確にせずに、複数あるオブジェクトの1つに対して要求を発行したい

Handler

public abstract class Handler
{
    protected Handler successor;

    public virtual void HandleRequest()
    {
        if (successor != null)
        {
            successor.HandleRequest();
        }
    }
}

public class ConcreteHandler1 : Handler
{
    public override void HandleRequest()
    {
        base.HandleRequest();
    }
}

public class ConcreteHandler2 : Handler
{
    public override void HandleRequest()
    {
        System.Console.WriteLine("some customized handle");
    }
}

SomeApplication

public class SomeApplication
{
    public void SomeFunction()
    {
        var handler1 = new ConcreteHandler1();
        var handler2 = new ConcreteHandler2();
        handler1.HandleRequest();
    }
}

Command

Command

public abstract class Command
{
    public abstract void Execute();
}

public class ConcreteCommand : Command
{
    private int someState;
    Receiver receiver = null;

    public ConcreteCommand(Receiver receiver)
    {
        this.receiver = receiver;
    }

    public override void Execute()
    {
        receiver.SomeAction();
    }
}

Receiver

public class Receiver
{
    public void SomeAction()
    {
    }
}
public class SomeApplication
{
    private List<Command> commandList = null;
    private int currentIndex = 0;

    public void SomeFunction()
    {
        this.commandList = new List<Command>();

        var receiver1 = new Receiver();
        var command1 = new ConcreteCommand(receiver1);
        commandList.Add(command1);

        var receiver2 = new Receiver();
        var command2 = new ConcreteCommand(receiver2);
        commandList.Add(command2);
    }

    public void ExecuteNext()
    {
        commandList[currentIndex].Execute();
        currentIndex++;
    }
}

Interpreter

  • 単純な文法の言語を解釈する

Interpreter

using System;

public abstract class AbstractExpression
{
    public abstract void Interpret(Context context);
}

public class TerminalExpression : AbstractExpression
{
    private string literal;

    public TerminalExpression(string literal)
    {
        this.literal = literal;
    }

    public override void Interpret(Context context)
    {
        // some interpret
        Console.Write(this.literal);
    }
}

public class NonterminalExpression : AbstractExpression
{
    private TerminalExpression exp1;
    private TerminalExpression exp2;

    public NonterminalExpression(TerminalExpression exp1, TerminalExpression exp2)
    {
        this.exp1 = exp1;
        this.exp2 = exp2;
    }

    public override void Interpret(Context context)
    {
        // some interpret
        Console.Write(this.exp1 + "&" + this.exp2);
    }
}

Context

public class Context
{
    public string SomeInfo;
}

Client

public class Client
{
    public void SomeFunction()
    {
        var terminalExp1 = new TerminalExpression("literal1");
        var terminalExp2 = new TerminalExpression("literal2");
        var someExp = new NonterminalExpression(terminalExp1, terminalExp2);
        var context = new Context { SomeInfo = "someINfo" };
        someExp.Interpret(context);
    }
}

Iterator

Aggregate

public abstract class Aggregate
{
    public abstract Iterator CreateIterator();
}

public class ConcreteAggregate : Aggregate
{
    public override Iterator CreateIterator()
    {
        return new ConcreteIterator(this);
    }
}

Iterator

public abstract class Iterator
{
    protected Aggregate aggregate;
    public Iterator(Aggregate aggregate)
    {
        this.aggregate = aggregate;
    }

    public abstract void First();
    public abstract void Next();
    public abstract bool IsDone();
    public abstract SomeItem CurrentItem();
}

public class ConcreteIterator : Iterator
{
    private int currentIndex = 0;

    public ConcreteIterator(Aggregate aggregate) : base(aggregate)
    {
    }

    public override void First()
    {
        // some algorithm
        this.currentIndex = 0;
    }

    public override void Next()
    {
        // some algorithm
        this.currentIndex++;
    }

    public override bool IsDone()
    {
        // some algorithm
        return this.currentIndex < aggregate.Count;
    }

    public override SomeItem CurrentItem()
    {
        // some algorithm
        return this.currentIndex[currentIndex];
    }
}

SomeApplication

public class SomeItem
{
}

public class SomeApplication
{
    private Aggregate someAggregate = null;

    public void SomeFunction()
    {
        var iterator = this.someAggregate.CreateIterator();
        iterator.First();
        while (!iterator.IsDone())
        {
            iterator.Next();
            var item = iterator.CurrentItem();
        }
    }
}

Mediator

Mediator

public abstract class Mediator
{
    public abstract void CreateColleagues();
    public abstract void ColleagueChanged(Colleague colleague);
}

public class ConcreteMediator : Mediator
{
    private ConcreteColleague1 colleague1;
    private ConcreteColleague2 colleague2;

    public override void CreateColleagues()
    {
        this.colleague1 = new ConcreteColleague1(this);
        this.colleague2 = new ConcreteColleague2(this);
    }

    public override void ColleagueChanged(Colleague colleague)
    {
        if (colleague == this.colleague1)
        {
            this.colleague1.SetFoo();
        }
        else if (colleague == this.colleague2)
        {
            this.colleague2.SetBar();
        }
    }
}

Colleague

public abstract class Colleague
{
    protected Mediator mediator;

    public Colleague(Mediator mediator)
    {
        this.mediator = mediator;
    }

    protected void Changed()
    {
        this.mediator.ColleagueChanged(this);
    }
}

public class ConcreteColleague1 : Colleague
{
    public ConcreteColleague1(Mediator mediator) : base(mediator)
    {
    }

    public void HogeEvent()
    {
        // some event such as mouse click
        Changed();
    }

    public void SetFoo()
    {
        // some action
    }
}

public class ConcreteColleague2 : Colleague
{
    public ConcreteColleague2(Mediator mediator) : base(mediator)
    {
    }

    public void HugaEvent()
    {
        // some event such as mouse click
        Changed();
    }

    public void SetBar()
    {
        // some action
    }
}

Some Application

public class SomeApplication
{
    public void SomeFunction()
    {
        var mediator = new ConcreteMediator();
        mediator.CreateColleagues();
    }
}

Memento

  • カプセル化を破壊せずに、ある時点のスナップショットを残しておき、後に復元等で利用する

Memento

public interface IMementoNarrow
{
}

public interface IMementoWide
{
    public void SetState(State state);
    public State GetState();
}

public class Memento : IMementoNarrow, IMementoWide
{
    private State state;

    public Memento()
    {

    }

    public void SetState(State state)
    {
        this.state = state;
    }

    public State GetState()
    {
        return this.state;
    }
}

public class State
{
    // some state params
    public string someState;
}

Originator

public class Originator
{
    public IMementoNarrow CreateMemento()
    {
        return new Memento();
    }

    public void SetMemento(IMementoNarrow memento)
    {
        var mementoWide = memento as IMementoWide;
    }

    public void Foo()
    {

    }
}

Caretaker

public class Caretaker
{
    private Originator originator;
    private IMementoNarrow prevMemento;

    public void SomeExecuteFunction()
    {
        // execute something
        this.originator.Foo();

        this.prevMemento = this.originator.CreateMemento();
    }

    public void SomeUndoFunction()
    {
        this.originator.SetMemento(this.prevMemento);
    }
}

Observer

Subject

public abstract class Subject
{
    protected List<Observer> observers = null;

    public virtual void Attach(Observer observer)
    {
        this.observers.Add(observer);
    }

    public virtual void Detach(Observer observer)
    {
        this.observers.Remove(observer);
    }

    public virtual void Notify()
    {
        foreach (var observer in this.observers)
        {
            observer.Update(this);
        }
    }
}

public class ConcreteSubject : Subject
{
    public void Foo()
    {
        // do something

        Notify();
    }
}

Observer

public abstract class Observer
{
    public abstract void Update(Subject changedSubject);
}

public class ConcreteObserver : Observer
{
    public override void Update(Subject changedSubject)
    {
        // do something update
    }
}

SomeApplication

public class SomeApplication
{
    public void SomeFunction()
    {
        var subject = new ConcreteSubject();
        subject.Foo();
    }
}

State

Context

public class Context
{
    private State currentState;
    public ConcreteStateA stateA { get; private set; }
    public ConcreteStateB stateB { get; private set; }

    public Context()
    {
        this.stateA = new ConcreteStateA();
        this.stateB = new ConcreteStateB();
        this.currentState = stateA;
    }

    public void Request()
    {
        this.currentState.Handle(this);
    }

    public void ChangeState(State state)
    {
        this.currentState = state;
    }
}

State

public abstract class State
{
    public abstract void Handle(Context context);
    public virtual void ChangeState(Context context, State state)
    {
        context.ChangeState(state);
    }
}

public class ConcreteStateA : State
{
    public override void Handle(Context context)
    {
        ChangeState(context, context.stateB);
    }
}

public class ConcreteStateB : State
{
    public override void Handle(Context context)
    {
        ChangeState(context, context.stateA);
    }
}

SomeApplication

public class SomeApplication
{
    public void SomeFunction()
    {
        var context = new Context();
        context.Request();
    }
}

Strategy

Context

public class Context
{
    private Strategy strategy;

    public Context(Strategy strategy)
    {
        this.strategy = strategy;
    }

    public void ContextInterface()
    {
        this.strategy.AlgorithmInterface();
    }
}

Strategy

public abstract class Strategy
{
    public abstract void AlgorithmInterface();
}

public class ConcreteStrategyA : Strategy
{
    public override void AlgorithmInterface()
    {
        // strategy A algorithm
    }
}

public class ConcreteStrategyB : Strategy
{
    public override void AlgorithmInterface()
    {
        // strategy B algorithm
    }
}

public class ConcreteStrategyC : Strategy
{
    public override void AlgorithmInterface()
    {
        // strategy C algorithm
    }
}

SomeApplication

public class SomeApplication
{
    public void SomeFunction()
    {
        // when use strategy A
        {
            var strategyA = new ConcreteStrategyA();
            var context = new Context(strategyA);
            context.ContextInterface();
        }

        // when use strategy B 
        {
            var strategyB = new ConcreteStrategyA();
            var context = new Context(strategyB);
            context.ContextInterface();
        }

        // when use strategy C
        {
            var strategyC = new ConcreteStrategyA();
            var context = new Context(strategyC);
            context.ContextInterface();
        }
    }
}

Template Method

  • ハリウッドの原則

TemplateMethod

public abstract class AbstractClass
{
    public void TemplateMethod()
    {
        PrimitiveOperation1();
        PrimitiveOperation2();
    }

    protected abstract void PrimitiveOperation1();
    protected abstract void PrimitiveOperation2();
}

public class ConcreteClass : AbstractClass
{
    protected override void PrimitiveOperation1()
    {
        // some first operation
    }

    protected override void PrimitiveOperation2()
    {
        // some second operation
    }
}

Visitor

  • オペレーションをカプセル化する
  • 多対多の関係で柔軟

Visitor

public abstract class Visitor
{
    public abstract void VisitConcreteElementA(ConcreteElementA elementA);
    public abstract void VisitConcreteElementB(ConcreteElementB elementB);
}

public class ConcreteVisitor1 : Visitor
{
    public override void VisitConcreteElementA(ConcreteElementA elementA)
    {
        // some action
    }

    public override void VisitConcreteElementB(ConcreteElementB elementB)
    {
        // some action
    }
}

public class ConcreteVisitor2 : Visitor
{
    public override void VisitConcreteElementA(ConcreteElementA elementA)
    {
        // some action
    }

    public override void VisitConcreteElementB(ConcreteElementB elementB)
    {
        // some action
    }
}

Element

public abstract class Element
{
    public abstract void Accept(Visitor visitor);
}

public class ConcreteElementA : Element
{
    public override void Accept(Visitor visitor)
    {
        visitor.VisitConcreteElementA(this);
    }
}

public class ConcreteElementB : Element
{
    public override void Accept(Visitor visitor)
    {
        visitor.VisitConcreteElementB(this);
    }
}

Structure

public class ObjectStructure
{
    public IEnumerable<Element> SomeStructure;
}

SomeApplication

public class SomeApplication
{
    public void SomeFunction()
    {
        var structureA = new ObjectStructure
        {
            SomeStructure = new List<ConcreteElementA>().Cast<Element>()
        };

        var structureB = new ObjectStructure
        {
            SomeStructure = new List<ConcreteElementB>().Cast<Element>()
        };

        var visitor1 = new ConcreteVisitor1();
        var visitor2 = new ConcreteVisitor2();

        // when use structureA and visitor1
        foreach (var element in structureA.SomeStructure)
        {
            element.Accept(visitor1);
        }

        // when use structureA and visitor2
        foreach (var element in structureA.SomeStructure)
        {
            element.Accept(visitor2);
        }

        // when use structureB and visitor1
        foreach (var element in structureB.SomeStructure)
        {
            element.Accept(visitor1);
        }

        // when use structureB and visitor2
        foreach (var element in structureB.SomeStructure)
        {
            element.Accept(visitor2);
        }
    }
}

Unityの新InputSystemとSteamworks SDKが競合してゲームパッドが反応しない

Unityの新InputSystemを試していたところ、ゲームパッドが反応しませんでした。
色々確認したところ、導入していたSteamworks SDKのSteamManagerを非アクティブにすると正常に動作することが分かりました。

じゃあ、SteamManagerを使えないのかというとそうではなく、私の場合、steam_appid.txtの中のAPP IDが間違っていたのが原因でした。
steam_appid.txtはUnityプロジェクトのルート(Assetsの一個上の階層)にあります。
これを自分のゲームのAPP IDにして、Unityを再起動でなおります(再起動しないとなおりません)。

SteamworksのInitializeが上手く完了しないとこうなるみたいなので、これでなおらなければ勘所として探ってみると良いかもしれません。

参考

github.com

herokuのGoogle API認証を簡単にするビルドパックの紹介

herokuでGoogle APIを認証するのは意外と骨が折れる。
それを簡単にしてくれるのが、こちらのビルドパックだ。

github.com

使い方

herokuにログインし、対象のアプリを選択する。
Settingsタブ内の、BuildpacksのAdd buildpackボタンをクリック。

表示されたポップアップに下記のurlを入力して、Save changes。

https://github.com/gerywahyunugraha/heroku-google-application-credentials-buildpack

次に、Config Vars(環境変数)に下記のように設定する。

GOOGLE_APPLICATION_CREDENTIALS google-credentials.json
GOOGLE_CREDENTIALS サービスアカウントキーのJsonを全てコピペ

これで、次のデプロイから認証が自動で設定される。