へっぽこ日記

Unity向に記事を投稿しています。たまにUIの事とかも呟くかも。

GithubProの学生特例を申し込む

はじめに

学生だと、Githubの有償機能を無料で使える制度があります。しかし、以前のように学生証を撮って申請するのかと思っていたのですが、いろいろ手続きがめんどくさくなっていました。今回は、自分の自分なりに楽だと思った申請方法を紹介します。

 

以前は...

いつものように学生証を撮って、申請するのかと思っていましたが、どうやらそれだけではダメなようで。

いろいろ警告を受けています

何がいけないのか

アップロードした書類は学業状況を証明するには不十分と言われています。Githubの請求情報に登録されている名前と一致しているか確認してねとも言われていますが、一致してるはずだけど...なにが問題なんだろう。

解決

ネットで色々検索してみると、どうやら学生証を英訳したものアップロードしたら解決した人がいたのでやってみることにしました。その人は、紙に英訳した学生証の情報を載せていましたが、めんどくさいのでGoogle翻訳で英訳したものを提出しました。

 

translate.google.co.jp

Google翻訳で英訳して得られた学生証がこちらです。

英訳した学生証

文字が所々小さいですが、英訳されています。これをアップロードして数日後...

申請が無事とおりました。

まとめ

審査が厳しくなりましたが、いままでが緩すぎた印象があります。学生証を提出して数時間で審査が通った記憶があるような...。ちなみに申請をする場所なども関係するようで、学内で申請しないと通らなかった事例もあるようです。

 

是非、読者登録をしていただくと助かります!

大学卒業しました

はじめに

大学を卒業したので、近況報告をしたいと思います。

春休み

卒業研究が終わって久しぶりの纏まったお休みでした。モノづくりもしましたが、旅行もしました。

最近は電子工作にはまっている

今後

大学を卒業しましたが、就職ではなく進学の道を選びました。

入学式は学部生と一緒だった

以前から志望していたゲーム業界へ行く予定でしたが、やってみたいことが色々あったので、働くのはまだいいかなと...

なので、2年ちょっと追加で学生をやることになりました。緩く更新していので、これからもよろしくお願いします。

Unityがデザインパターンに関しての記事を出したので、ぼちぼち目を通していく(2/2)

はじめに

今回はUnityがデザインパターンに関しての記事を出したので、ぼちぼち目を通していく(2/2)記事です。

 

前回の記事に引き続き、公式の記事を咀嚼して、自分なりの意見を出したいと思います。

makaroni3018.hatenablog.com

 

 

Object Pool

インスタンスの生成と破壊が激重なので、生成したインスタンスを使いまわしつつ、非表示にすることで、生成と破壊の回数を押させるパターンです。シューティングゲームなどで使われているパターンです。

makaroni3018.hatenablog.com

 

Singleton

生成するインスタンスをひとつにすることを約束するパターン。インスタンスを必ずひとつに制限することで、インスタンスにゲーム全体のデータを持たせて、どのクラスからもデータを取得できるようにしたりと色々使い道があるパターン。

makaroni3018.hatenablog.com

 

Command

処理の呼び出しと、実装側を分けることを目的としたパターン。処理で呼び出される命令をオブジェクト単位で実装することで、RedoやUndoの機能を実装しやすいなどの利点がある。

makaroni3018.hatenablog.com

 

State

オブジェクトの状態をクラスとして管理し、状態が変わるごとに振る舞いを実行するパターンです。

makaroni3018.hatenablog.com

 

Observer

以前自分なりに試して実装した際の解説記事を置いておきます。

makaroni3018.hatenablog.com

 

MVP

Observerパターンの発展型です。オブジェクトをModel、View、Presenterに分けることで、保守性・拡張性を高めつつ、疎結合にしたいぜ!なパターンです。UI周りで使用されることが多いみたいです。以前自分なりに試して実装した際の解説記事を置いておきます。

makaroni3018.hatenablog.com

 

おわりに

今までのまとめになってしまいましたが、今後も設計思想やデザインパターンを試して、スキルを身に着けていきたいです

 

是非、読者登録をしていただくと助かります!

Stateパターンを試す

はじめに

Stateパターンを試す記事です。 ステートパターン自体は以前から利用していたのですが、公式とは異なる実装だったので、公式のStateパターンを基に試してみました。

サンプルプロジェクト github.com

そもそもStateパターンとは

Gofパターンの一種で、オブジェクトの状態をクラスとして管理し、状態が変わるごとに振る舞いを変えるパターン。振る舞いを条件ごとに変えるのなら、単純にIfなどを使えば良いのですが、新たに状態を追加した場合は全体の条件を見直す必要があったりと色々不便です。この問題を解決しようとする思想。

まずは、あまりよろしくない方法で状態ごとに振る舞いを実行したいと思います。

単純にEnumを使っての実装

これでも単純にIfとフラグを使って分けるよりかは良いのですが、新たに状態を加える場合はどうしてもEnumを触る必要があります。また、条件として加える際には、すべての分岐をチェックする必要があったりと、あまりよろしくないです。

 private enum State
    {
        Idle,
        Move,
        Jump
    }

    private State _state = State.Idle;

    private void Update()
    {
        OnStateChanged();
    }

    private void OnStateChanged()
    {
        switch (_state)
        {
            case State.Idle:
                if (Input.GetKeyDown(KeyCode.Space))
                {
                    _state = State.Jump;
                }
                else if (Input.GetKey(KeyCode.W))
                {
                    _state = State.Move;
                }
                else
                {
                    Idle();
                }

                break;

            case State.Jump:
                Jump();
                break;

            case State.Move:
                Move();
                break;
        }
    }

    private void Idle()
    {
        Log("Idle");
    }

    private void Move()
    {
        Log("Move");
         _state = State.Idle;
    }

    private void Jump()
    {
        Log("Jump");
        _state = State.Idle;
    }

    private void Log(string value)
    {
        Debug.Log(value);
    }

StateMachineを使って試してみる

先程のEnumで実装したものとは異なり、クラスを一つの状態として扱うように実装してみました。 キーを押すと、それに対応した状態がLogに表示されるサンプルです。公式のデモプロジェクトを基に作成しました。

状態をクラスごとに定義しているので、Enumの時の様に全体を見直す必要が無くなりました。そのため、新たに状態を追加したい場合も、他の条件や分岐を見直す必要がなく、単純にクラスを実装すれば良くなりました。

IState

各々の状態クラスで呼ばれるメソッドを定義しています。

 public void Enter();

 public void Update();

    public void Exit();

IdleState

IdleクラスやAttackクラスが状態クラスで、遷移条件が合致した場合に、StateMachineに遷移を依頼います。

 private Player _player;
    
    public IdleState(Player player)
    {
        _player = player;
    }
    
    public void Enter()
    {
        Debug.Log("IdleState Enter");
    }

    public void Update()
    {
        if (_player.IsAttacked)
        {
            _player.StatetateMachine.TransitionTo(_player.StatetateMachine.attackState);
        }
        Debug.Log("IdleState Update");
    }

    public void Exit()
    {
        Debug.Log("IdleState Exit");
    }

AttackState

 private Player _player;

    public AttackState(Player player)
    {
        _player = player;
    }

    public void Enter()
    {
        Debug.Log("AttackState Enter");
    }

    public void Update()
    {
        _player.StatetateMachine.TransitionTo(_player.StatetateMachine.idleState);
        Debug.Log("AttackState Update");
    }

    public void Exit()
    {
        Debug.Log("AttackState Exit");
    }

StateMachine

ここで状態をまとめて管理しており、現在の状態に対して、Interface経由でUpdateやEnterを呼び出しています。

 private IState state;

    public AttackState attackState;
    public IdleState idleState;

    public StateMachine(Player player)
    {
        attackState = new AttackState(player);
        idleState = new IdleState(player);
    }

    public void Initialize(IState state)
    {
        this.state = state;
        state.Enter();
    }

    public void TransitionTo(IState nextState)
    {
        state.Exit();
        state = nextState;
        nextState.Enter();
    }

    public void Update()
    {
         state?.Update();
    }

Player

ここでStateMachineを回して、状態に応じた振る舞いを依頼しています。

public StateMachine StatetateMachine;
    public bool IsAttacked = false;

    private void Start()
    {
        StatetateMachine = new StateMachine(this);
        StatetateMachine.Initialize(StatetateMachine.idleState);
    }

    private void Update()
    {
        IsAttacked = Input.GetKeyDown(KeyCode.Space);
        
        StatetateMachine.Update();
    }

おわりに

UnityだとAnimatorで使われていたりと、使いどころが多いので是非覚えておきたいですね。また、ここからObserverを混ぜて実装するのもありだと思います。

是非、読者登録をしていただくと助かります!

Commandパターンを試す

はじめに

Commandパターンを試す記事です。 以前から存在は知っていたのですが、実際に試したことはなかったので、実際に試してみました。

サンプルプロジェクト github.com

そもそもCommandパターンとは

処理の呼び出しと実装側を分けることを目的としたデザインパターンで、処理で呼び出される命令をオブジェクト単位で分けて実装するとのこと。オブジェクト単位で管理することで、UndoやRedoが実現しやすくなるみたいです。

登場する役割としては以下のものがあります

名前 役割
Command 命令(オブジェクト単位)
Receiver 依頼を受け取り、実行される。処理対象
Invoker 命令を実行する
Client 命令呼び出しを依頼

流れとしては、ClientがCommandをNew()して、CommandをInvokerに渡し、Invokerの実行メソッドを呼ぶことでInvokerにCommandを依頼します。Invokerでは、渡されたCommandオブジェクトをスタックにあげて管理します。その後に、Commandの実行関数を呼び、Receiverに定義されたメソッドを呼ばれることでClientがしたい事を実現する と云った感じです。

上記で色々言いましたが、Commandパターンを実装する方法は様々なようで、Wikipediaでは

Commandが内包する処理の記述は開発者に一任されている。全ての処理をCommand内に記述するパターン[4]、あるいはcommandインスタンス生成時に実行対?象(Receiver)を受け取りcommand.Execute()時にreceiver.action()コールのみをおこなう、すなわち実行をReceiverに委譲してCommandは繋ぎに徹するパターン もある[5]。尚 Receiver は Commandパターンの要件ではない。

Command パターン - Wikipedia とあるように、色々あります。次で説明するサンプルは、Receiverを委譲してCommandはつなぎに徹するパターンで制作しました。

実際に試してみる

今回は、公式が出しているサンプルを基に以下の物を作りました。移動とRedoができるサンプルです。

ICommand

ここでCommandの命名メソッドを定義しています。

public interface ICommand
{
   void Execute();

   void Undo();
}

IReceiver

public interface IReceiver
{
   void Move(Vector3 pos);
}

Client

ここでボタンとLeftMove(),RightMove(),Redo()を結び付けて、右・左どちらの方向に移動したいのかを指示できるようにしています。

public class Client : MonoBehaviour
{
    [SerializeField] private Button _rightMoveButton;
    [SerializeField] private Button _leftMoveButton;
    [SerializeField] private Button _undoButton;

    [SerializeField] private PlayerReceiver _receiver;
    
    private void Start()
    {
        _rightMoveButton.onClick.AddListener(LeftMove);
        _leftMoveButton.onClick.AddListener(RightMove);
        _undoButton.onClick.AddListener(Undo);
    }

    private void LeftMove()
    {
        ICommand command = new MoveCommand(_receiver,Vector3.left);
        Invoker.Execute(command);
    }

    private void RightMove()
    {
        ICommand command = new MoveCommand(_receiver,Vector3.right);
        Invoker.Execute(command);
    }

    private void Undo()
    {
        Invoker.Undo();
    }
}

Invoker

ここで、命令を実行しています。Stackを使ってCommandをまとめて管理しており、このStackを使ってRedoの実装を可能にしています。

public class Invoker
{
    private static Stack<ICommand> _commands = new Stack<ICommand>();
    
    public static void Execute(ICommand command)
    {
        command.Execute();
        _commands.Push(command);
    }

    public static void Undo()
    {
        if (_commands.Count > 0)
        {
            ICommand command = _commands.Pop();
            command.Undo();
        }
    }
}

PlayerReceiver

ここでIReceiverを実装しています。Clientから受けた指示を基に、命令を実行します。

public class PlayerReceiver : MonoBehaviour,IReceiver
{
    public void Move(Vector3 pos)
    {
        transform.position += pos;
    }
}

MoveCommand

ここでICommandを実装していています。あくまでReceiverを繋ぐことに徹しています。

public class MoveCommand : ICommand
{
    private PlayerReceiver _receiver;
    private Vector3 _pos;

    public MoveCommand(PlayerReceiver receiver,Vector3 pos)
    {
        _receiver = receiver;
        _pos = pos;
    }

    public void Execute()
    {
        _receiver.Move(_pos);
    }

    public void Undo()
    {
        _receiver.Move(-_pos);
    }
}

おわりに

Commandパターンは、パズルゲームやリバーシで使われているそうで、今回は実装しませんでしたが、Undo機能も追加出来たりと色々できます。

是非、読者登録をしていただくと助かります!

ObjectPoolを試してみる

はじめに

今回はObjectPoolを試してみる記事です。 ObjectPool自体は知っていたのですが、使ったことがないので試してみました。

サンプルプロジェクト github.com 使用アセット assetstore.unity.com

そもそもObjectPoolとは

繰り返し使うオブジェクトを、愚直に生成と破壊のサイクルを短期間で行うと激重です。そのため、生成したものを再利用することで生成する回数を抑えつつ、破壊する代わりに非表示にすることで負荷を抑えようとするデザインパターンです。

今回は、2021fからUnity公式から導入されているUnityObjectPoolで試してみます。

ObjectPoolを使うために必要な設定

Unityが提供しているObjectPoolは、最低でも以下4つのパラメータをコンストラクタに設定しないといけません。

new ObjectPool<T>(
createFunc: Create, //オブジェクトの生成処理
actionOnGet: OnGetFromPool, // オブジェクトをプールから取り出した時の処理
actionOnRelease: OnReleaseToPool, // オブジェクトをプールに戻したときの処理
actionOnDestroy: OnDestroyPooledObject, //未使用のオブジェクトが一定量を超えた時の処理
)
1.createFunc

オブジェクトの生成処理を担当する関数です。単純にInstantiate()したものを返します。一番最初と非アクティブのオブジェクトが無い時に使われます。

 private ParticleSystem Create()
    {
        return Instantiate(_effect, new Vector3(Random.Range(-10, 10), Random.Range(-10, 10), Random.Range(-10, 10)),
            Quaternion.identity);
    }
2.actionOnGet

プールからオブジェクトを貰ってくるときに実行する関数です。ここではオブジェクトを表示にしています。

private void OnGetFromPool(ParticleSystem effect)
    {
        effect.gameObject.SetActive(true);
    }
3.actionOnRelease

プールへオブジェクトを返却する時に実行する関数です。ここではオブジェクトを非表示にしています。

    private void OnReleaseToPool(ParticleSystem effect)
    {
        effect.gameObject.SetActive(false);
    }
4.actionOnDestroy

非アクティブが多くなりすぎた時にオブジェクトを削除する関数です。単純にDestroy()で削除しています。

private void OnDestroyPooledObject(ParticleSystem effect)
    {
        Destroy(effect);
    }
5.その他

必須ではないのですが、設定することができる値があります。好みに合わせて設定してください。

_pool = new ObjectPool<ParticleSystem>(
            collectionCheck: true, // 同一インスタンスが登録されていないかチェックするかどうか
            maxSize: 100 //オブジェクトの最大許容量
        );

ObjectPoolを利用して作ったもの

今回は単純にパーティクルをObjectPoolで使いまわすものになってます。

  • ParticlePool.cs : ObjectPoolの役割をしています
[SerializeField]
    private ParticleSystem _effect = null;
    
    private ObjectPool<ParticleSystem> _pool;

    private void Awake()
    {
        _pool = new ObjectPool<ParticleSystem>(
            createFunc: Create,
            actionOnGet: OnGetFromPool, // プールから取り出されたときの処理 
            actionOnRelease: OnReleaseToPool, // プールに戻したときの処理
            actionOnDestroy: OnDestroyPooledObject, // プールがmaxSizeを超えたときの処理
            collectionCheck: true, // 同一インスタンスが登録されていないかチェックするかどうか
            maxSize: 100 //オブジェクトの最大許容量
        );
    }
    
    /// <summary>
    /// オブジェクトを生成する
    /// </summary>
    /// <returns></returns>
    private ParticleSystem Create()
    {
        return Instantiate(_effect, new Vector3(Random.Range(-10, 10), Random.Range(-10, 10), Random.Range(-10, 10)),
            Quaternion.identity);
    }
    
    /// <summary>
    /// 非アクティブのオブジェクトが合ったら表示する
    /// </summary>
    /// <param name="effect"></param>
    private void OnGetFromPool(ParticleSystem effect)
    {
        effect.gameObject.SetActive(true);
    }
    
    /// <summary>
    /// プールに返却時にオブジェクトを非表示にする
    /// </summary>
    /// <param name="effect"></param>
    private void OnReleaseToPool(ParticleSystem effect)
    {
        effect.gameObject.SetActive(false);
    }
    
    /// <summary>
    /// 非アクティブが多くなりすぎた時にオブジェクトを削除する
    /// </summary>
    /// <param name="effect"></param>
    private void OnDestroyPooledObject(ParticleSystem effect)
    {
        Destroy(effect);
    }
    
    /// <summary>
    ///プールからオブジェクトを持ってくる 
    /// </summary>
    public void Explosion()
    { 
        _pool.Get();
    }
    
    /// <summary>
    /// オブジェクトを返却する
    /// </summary>
    /// <param name="effect"></param>
    public void Delete(ParticleSystem effect)
    {
        _pool.Release(effect);
    }
  • Particle.cs : パーティクル再生終了時にオブジェクトを返却するようにしています
 private ParticlePool _pool;

    private void Start()
    {
        _pool = GameObject.Find("ObjectPoolManager").gameObject.GetComponent<ParticlePool>();
    }

    //パーティクルの再生が終わった時に実行
    private void OnParticleSystemStopped()
    {
        _pool.Delete(this.GetComponent<ParticleSystem>());
    }
  • Tester.cs : ここでObjectPoolを操作しています
[SerializeField] private ParticlePool _manager;
   
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            _manager.Explosion();
        }
    }

おわりに

簡単に試すことが出来ました。自前でObjectPoolを実装するのは手間が掛かるので、公式から提供されているのは助かりますね。今回はUnityに導入されている物を試しましたが、UniRxにもObjectPoolがあるので、今後はUniRxのObjectPoolも試してみたいと思います。

是非、読者登録をしていただくと助かります!

VContainerを試してみる

はじめに

今回はVContainerを試してみる記事です。

ネット上でZenjectと比較してかなり高速でシンプルと聞いたので、実際に試してみました。

 

サンプルプロジェクト

github.com

 

 

試してみる

こんなものを試しに作りました。クリックすると箱がジャンプして、テキストにジャンプした回数が表示されます。

 

MVPパターンで実装していて、入力を環境によって分けています。また、Modelのテストに備えて、ModelをInterfaceで切り離しています。

 

導入

今回はunitypackage経由でインストールしました。

 

1. manifest.jsonに「"com.unity.nuget.mono-cecil": "1.10.1", 」を記述

2. 最新リリースからunitypackageをダウンロード

3. unitypackageインポート

 

準備

Zenjectだとコンストラクタインジェクションは[inject]は必要なかったのですが、VContainerでは必要みたいなのでコンスタントにも[inject]をつけています。

public class Model : IModel
{
   public IReactiveProperty CountProp=>_countProp;
   private readonly IntReactiveProperty _countProp;

   public Model()
   {
      _countProp=new IntReactiveProperty(0);
   }

   public void AddCount()
   {
      _countProp.Value++;
   }
}
public class Presenter : IStartable, IDisposable
{
    private readonly View _view;
    private readonly IModel _model;
    
    private readonly CompositeDisposable _compositeDisposable = new CompositeDisposable();

    [Inject]
    public Presenter(View view, IModel model)
    {
        _view = view;
        _model = model;
    }

    public void Start()
    {
        Bind();
        SetEvent();
    }

    private void Bind()
    {
        _model.CountProp
            .Subscribe(_view.SetCount)
            .AddTo(_compositeDisposable);
    }

    private void SetEvent()
    {
        _view.OnAnimationCallback += () => _model.AddCount();
        
        _view.InputJump()
            .Subscribe(_=>_view.Jump())
            .AddTo(_compositeDisposable);
    }
    
    public void Dispose()
    {
        _compositeDisposable.Dispose();
    }
}

public class View : MonoBehaviour
{
    public event Action OnAnimationCallback;

    [SerializeField] private Transform _cubeTransform;
    private Sequence _sequence;

    [SerializeField] private Text _countText;

    [Inject] IInputProvider _input;

    public void SetCount(int count)
    {
        _countText.text = count.ToString();
    }

    public IObservable InputJump()
    {
        return _input.InputJump();
    }

    public void Jump()
    {
        if (_sequence.IsActive()) return;

        _sequence = DOTween.Sequence()
            .Join(_cubeTransform.DOJump(new Vector3(0, 0, 0), 3f, 1, 1.0f).SetRelative())
            .Join(_cubeTransform.DOPunchRotation(new Vector3(360f, 360f, 360f), 1.0f,5).SetRelative())
            .SetEase(Ease.OutCubic).OnComplete(()=>OnAnimationCallback?.Invoke()).SetLink(this.gameObject);
    }
}

public interface IInputProvider
{ 
    public IObservable InputJump();
}

public class KeyInputProvider : IInputProvider
{
    public IObservable InputJump()
    {
        return InputAsObservable.GetKeyDown(KeyCode.Space);
    }
}

MVPでViewとPresenterを互いを知らない状態にしつつ、Interfaceを設けることでPresenterがModelの存在を知らない状態を実現しています。入力も同じように、Interfaceを設けてEditorとWebGLで切り替えています。

 

登録

Zenjectと同じように登録できました。RegisterEntryPoint()で注入先を指定して、Register()とRegisterComponet()で注入するクラスを登録しています。

    public class MvpLifetimeScope : LifetimeScope
{
    [SerializeField] private View _view;

    protected override void Configure(IContainerBuilder builder)
    {
#if UNITY_EDITOR
        builder.Register<IInputProvider, GamepadInputProvider>(Lifetime.Transient);
#elif UNITY_WEBGL
        builder.Register<IInputProvider,KeyInputProvider>(Lifetime.Transient);
#endif
        builder.Register<IModel,Model>(Lifetime.Transient);

        builder.RegisterComponent(_view);

        builder.RegisterEntryPoint(Presenter);
    }
}

 

おわりに

普段Zenjectを使っている身からすると、VContainerに移行しなくても良いかなと思いました。Zenjectにはあって、VContainerには無い機能もあったりしますし、ZenjectからVContainerへの移行に伴うリファクタを考えると頭が...

 

是非、読者登録をしていただくと助かります!