20200702のUnityに関する記事は12件です。

Unityの新しいDIライブラリ:VContainer (0.0.2対応修正 7/4)

VContainer is 何?

 タイトルの通りです。現在Unityで使えるDIライブラリにはZenject/Extenjectがありますが、それとは別の選択肢としてVContainerが作られています。なんでもZenject/Extenjectは多機能で便利なのですが、もっとシンプルな薄いDIライブラリを目指されているのだとか。作者はハダシA氏です。
https://github.com/hadashiA/VContainer
 現在、開発中なのですが、0.0.1がリリースされたので早速触って見ました。
0.0.2が出ました(7/4)。InjectとIInitialize関係が更新されました。

まずはZenject/Extenjectで作る

IPerson.cs
namespace ZenjectSayHello
{
    public interface IPerson
    {
        string Name { get; }
    }
}
Parent.cs
namespace ZenjectSayHello
{
    public class Parent : IPerson
    {
        public string Name => "親御";
    }
}
SayHello.cs
using UnityEngine;
namespace ZenjectSayHello
{
    public class SayHello
    {
        public SayHello(IPerson person)
        {
            Debug.Log($"{person.Name}さんによろしく!");
        }
    }
}
SayHelloInstaller.cs
using Zenject;
namespace ZenjectSayHello
{
    public class SayHelloInstaller : MonoInstaller
    {
        public override void InstallBindings()
        {
            Container.Bind<IPerson>().To<Parent>().AsSingle();
            Container.Bind<SayHello>().AsSingle();
        }
    }
}
SayHelloMonoBehaviour.cs
using UnityEngine;
using Zenject;
namespace ZenjectSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject] private SayHello _sayHello = default;
    }
}

 SayHelloMonoBehaviourは適当なGameObjectにアタッチします。SayHelloがResolveされるときに、コンストラクタのIPersonにParentが放り込まれ、
スクリーンショット 2020-07-02 20.26.20.png
と出ます。こいつをVContainerで書いてみます。

VContainerで書く

インストール

 まずインストール方法ですが、上記Githubリンクのリリースページにunitypackageがありますので、それをダウンロード、インストールするだけでOKです。

インストーラーを作る

 最初から手で書いてもいいのですが、テンプレートを自動生成してくれます。Projectの右クリックからCreate > C# Script。で、ファイル名をXXXInstallerとすれば、Installerのテンプレートを作ってくれます。

LifetimeScopeを作る

 Zenject/ExtenjectのSceneContext等にあたるものがLifetimeScopeになります。作り方はHierarchyの右クリックでVContainer > LifetimeScope。で、InspectorのMonoInstallerの+ボタンをクリックすると、さっき作ったSayHellowInstallerが出てきます。指定したInstallerはLifetimeScopeGameObjectにアタッチされます。
スクリーンショット 2020-07-02 21.04.50.png

コード書く

コードを書いていきます。

IPerson.cs
namespace VContainerSayHello
{
    public interface IPerson
    {
        string Name { get; }
    }
}
Parent.cs
namespace VContainerSayHello
{
    public class Parent : IPerson
    {
        public string Name => "親御";
    }
}
SayHello.cs
using UnityEngine;
namespace VContainerSayHello
{
    public class SayHello
    {
        public SayHello(IPerson person)
        {
            Debug.Log($"{person.Name}さんによろしく!");
        }
    }
}

ここまではさっきと同じです。

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject]
        public void Construct(SayHello sayHello)
        {
        }
    }
}

 MonoBehaviourです。エディター上で適当なGameObjectを作って、それにアタッチします。サンプルではメソッドインジェクションがありましたので、そちらにしました。

SayHelloInstaller.cs
using UnityEngine;
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        [SerializeField] private SayHelloMonoBehaviour sayHello = default;

        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
            builder.RegisterComponent(sayHello);
        }
    }
}

 キモとなる部分です。書き加えたのはInstallメソッド内の3行とsayHelloフィールド。

builder.Register<IPerson, Parent>(Lifetime.Singleton);
builder.Register<SayHello>(Lifetime.Singleton);

 この2行は、ParentとSayHelloをZenject/ExtenjectでいうところのBindします。

[SerializeField] private SayHelloMonoBehaviour sayHello = default;

builder.RegisterComponent(sayHello);

 その次の行とsayHelloのインスタンスです。この解説の前に、まずInjectの挙動について解説します。

[Inject]の挙動

 Zenject/ExtenjectだとMonoBehaviourにInjectを書いておけば、どこでもInjectしてくれました。VContainerはエディター上でGameObjectを作ってHierarchyに存在してるだけではInjectを発動してくれません。
 VContainerでは、Injectは原則的にResolveするときに発火します。例えばなんらかのクラスのコンストラクタ引数になっててそこに放り込まれる時とか、IInitializableが実装されててそれが走る時とか。
 DIはコンストラクタでのインジェクションが原則だと言われます。コンストラクタインジェクションはResolveされる時以外はなんらInjectされないので、それとタイミングを合わせていると考えれば納得です。

[Inject]を発火させる方法

 ですんで、IInitializableをSayHelloMonoBehaviourに実装してAsを使って発火するか、適当なコンストラクタにぶち込むか、なんですが(7/4追記に転記)、もっと簡単なやり方があります。それが上述の

builder.RegisterComponent(sayHello);

です。これでsayHelloインスタンスをInjectしてくれます。

※0.0.2で実装されました。0.0.1でのやり方は下の方に残しておきます。

動作

スクリーンショット 2020-07-03 22.15.25.png
動きました。

Instantiateとかはどうなんの?

 試しにPrefabをInstantiateしてみましたが、Injectは発火しません。PlaceholderFactoryもありません(少なくとも現在は)。DiContainer.Injectのようなものもないようです。つまり自前でFactoryを作って、必要な依存をそこで解決しましょう。

所感

 [Inject]の使い方にちょっと混乱しましたが、使えそうです。この辺りのZenject/Extenjectとの違いは軽量化のためでしょうか。
 一つ興味深いのはZenject/ExtenjectにあるFactory関係の実装予定はないそうで。まぁ確かにあれはPlaceholderFactory作ってそれをSpawnerで包んで、なんか手間だなぁ、と感じたり。いまいち使いこなせてない感がありました。ですんで、多機能よりも軽量化に注力するのは大歓迎です。今後に期待です。

追記(7/2)

 すみません。同じProjectにあったZenjectと競合してました。名前空間を貫通してくるとは思いませんでした。IInitialize関係がこちらの追記にあったのですが、上の方にスクリプト修正の上で転記しました。

追記(7/3)

 ハダシAさんに質問した所、Zenject/Extenjectのような一方的なInjectは想定しておらず、Resolveが必要とのご回答をいただきました。Zenject/Extenjectのように使っていたInjectを上述のとおり修正いたしました。

追記(7/4)

 0.0.2でRegisterComponentが実装されました。この記事をみて速攻で実装してくれました。熱い&感謝です。
 IInitializableを使ってResolveする方法をこちらに残しておきます。以下のようなやり方は現在不要です。ただ、基本的には以下のようにしてInjectを発火させるんだ、と思っておいた方がVContainerを扱いやすくなると思います。
 ちなみにAsとやっていますが、0.0.2で

builder.RegisterEntryPoint<Foo>(..);

が実装され、IInitializableとかITickableをまとめてやってくれるようになってます。

SayHelloMonoBehaviourにIInitializable等を実装

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour, IInitializable
    {
        [Inject]
        public void Construct(SayHello sayHello)
        {
        }

        public void Initialize()
        {
        }
    }
}
SayHelloInstaller.cs
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
            builder.RegisterComponentInHierarchy<SayHelloMonoBehaviour>().As<IInitializable>();
            //HierarchyにあるSayHelloMonoBehaviourのInitializeを発火。Resolveされる。
        }
    }
}

IInitializableをMonoBehaviourに実装してAsで発火します。

何かのコンストラクタでResolveさせる

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject]
        public void Construct(SayHello sayHello)
        {
        }
    }
}
SayHelloMonoBehaviourInjector.cs
using VContainer.Unity;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviourInjector : IInitializable
    {
        public SayHelloMonoBehaviourInjector(SayHelloMonoBehaviour sayHelloMonoBehaviour)
        {
        }

        public void Initialize()
        {
        }
    }
}
SayHelloInstaller.cs
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        [SerializeField] private SayHelloMonoBehaviour sayHello = default;

        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
            builder.RegisterInstance(sayHello);
            builder.Register<SayHelloMonoBehaviourInjector>(Lifetime.Singleton).As<IInitializable>();
            //SayHelloMonoBehaviourInjectorがInitializeの発火でResolveされる。
            //その際、コンストラクタ引数にSayHelloMonoBehaviourがあるのでsayHelloインスタンスがResolveされる。
            //builder.RegisterEntryPoint<SayHelloMonoBehaviourInjector>(Lifetime.Singleton);
            //で代用可能。
        }
    }
}

 RegisterInstanceで該当のMonoBehaviourをRegisterして、適当なコンストラクタにぶち込みます。そのときResolveされます。
 IInitializableをRegisterEntryPointやAsすることで、Zenject/ExtenjectのNonLazy的に扱えます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityの新しいDIライブラリ:VContainer

VContainer is 何?

タイトルの通りです。現在Unityで使えるDIライブラリにはZenject/Extenjectがありますが、それとは別の選択肢としてVContainerが作られています。なんでもZenject/Extenjectは多機能で便利なのですが、もっとシンプルな薄いDIライブラリを目指されているのだとか。作者はハダシA氏です。
https://github.com/hadashiA/VContainer
現在、開発中なのですが、0.0.1がリリースされたので早速触って見ました。

まずはZenject/Extenjectで作る

IPerson.cs
namespace ZenjectSayHello
{
    public interface IPerson
    {
        string Name { get; }
    }
}
Parent.cs
namespace ZenjectSayHello
{
    public class Parent : IPerson
    {
        public string Name => "親御";
    }
}
SayHello.cs
using UnityEngine;
namespace ZenjectSayHello
{
    public class SayHello
    {
        public SayHello(IPerson person)
        {
            Debug.Log($"{person.Name}さんによろしく!");
        }
    }
}
SayHelloInstaller.cs
using Zenject;
namespace ZenjectSayHello
{
    public class SayHelloInstaller : MonoInstaller
    {
        public override void InstallBindings()
        {
            Container.Bind<IPerson>().To<Parent>().AsSingle();
            Container.Bind<SayHello>().AsSingle();
        }
    }
}
SayHelloMonoBehaviour.cs
using UnityEngine;
using Zenject;
namespace ZenjectSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject] private SayHello _sayHello = default;
    }
}

SayHelloMonoBehaviourは適当なGameObjectにアタッチします。
SayHelloがResolveされるときに、コンストラクタのIPersonにParentが放り込まれ、
スクリーンショット 2020-07-02 20.26.20.png
と出ます。こいつをVContainerで書いてみます。

VContainerで書く

インストール

まずインストール方法ですが、上記Githubリンクのリリースページにunitypackageがありますので、それをダウンロード、インストールするだけでOKです。

インストーラーを作る

最初から手で書いてもいいのですが、テンプレートを自動生成してくれます。Projectの右クリックからCreate > C# Script。で、ファイル名をXXXInstallerとすれば、Installerのテンプレートを作ってくれます。

LifetimeScopeを作る

Zenject/ExtenjectのSceneContext等にあたるものがLifetimeScopeになります。作り方はHierarchyの右クリックでVContainer > LifetimeScope。で、InspectorのMonoInstallerの+ボタンをクリックすると、さっき作ったSayHellowInstallerが出てきます。指定したInstallerはLifetimeScopeGameObjectにアタッチされます。
スクリーンショット 2020-07-02 21.04.50.png

コード書く

あとはガリガリ書くだけです。

IPerson.cs
namespace VContainerSayHello
{
    public interface IPerson
    {
        string Name { get; }
    }
}
Parent.cs
namespace VContainerSayHello
{
    public class Parent : IPerson
    {
        public string Name => "親御";
    }
}
SayHello.cs
using UnityEngine;
namespace VContainerSayHello
{
    public class SayHello
    {
        public SayHello(IPerson person)
        {
            Debug.Log($"{person.Name}さんによろしく!");
        }
    }
}

ここまではさっきと同じです。

SayHelloInstaller.cs
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
        }
    }
}

キモとなる部分です。実際に書いたのはInstallメソッド内の2行です。Interfaceの紐付けがこんな感じなんですねぇ。

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject]
        public void Construct(SayHello sayHello)
        {
        }
    }
}

やはり適当なGameObjectにアタッチします。メソッドインジェクションがサンプルに載ってたので、とりあえずこっちにしました。

動作

スクリーンショット 2020-07-02 20.42.35.png
動きました。

所感

 使い心地はZenject/Extenjectとあまり変わらない感じですね。一つ興味深いのはZenject/ExtenjectにあるFactory関係の実装予定はないそうで。まぁ確かにあれはPlaceholderFactory作ってそれをSpawnerで包んで、なんか手間だなぁ、と感じたり。いまいち使いこなせてない感がありました。ですんで、多機能よりも軽量化に注力するのは大歓迎です。今後に期待です。

追記(7/2)

 すみません。同じProjectにあったZenjectと競合してました。あの子、名前空間も貫通してくるんやな。どうもメソッドインジェクションがうまく動いていない様子。どこか読み忘れてたかな。

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        private SayHello _sayHello = default;
        [Inject]
        public void Construct(SayHello sayHello)
        {
            _sayHello = sayHello;
        }
        private void Start()
        {
            Debug.Log(_sayHello == null); //Trueを出す
        }
    }
}

SayHelloにIInitialize(とかITickableとか)を実装すれば、とりあえずコンストラクタは走ります。

SayHello.cs
using UnityEngine;
using VContainer.Unity;

namespace VContainerSayHello
{
    public class SayHello : IInitializable
    {
        public SayHello(IPerson person)
        {
            Debug.Log($"{person.Name}さんによろしく!");
        }

        public void Initialize()
        {

        }
    }
}
SayHelloInstaller.cs
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton).As<IInitializable>();
            //Initializeを実装するときはこう書く。
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityの新しいDIライブラリ:VContainer (7/3修正)

VContainer is 何?

タイトルの通りです。現在Unityで使えるDIライブラリにはZenject/Extenjectがありますが、それとは別の選択肢としてVContainerが作られています。なんでもZenject/Extenjectは多機能で便利なのですが、もっとシンプルな薄いDIライブラリを目指されているのだとか。作者はハダシA氏です。
https://github.com/hadashiA/VContainer
現在、開発中なのですが、0.0.1がリリースされたので早速触って見ました。

まずはZenject/Extenjectで作る

IPerson.cs
namespace ZenjectSayHello
{
    public interface IPerson
    {
        string Name { get; }
    }
}
Parent.cs
namespace ZenjectSayHello
{
    public class Parent : IPerson
    {
        public string Name => "親御";
    }
}
SayHello.cs
using UnityEngine;
namespace ZenjectSayHello
{
    public class SayHello
    {
        public SayHello(IPerson person)
        {
            Debug.Log($"{person.Name}さんによろしく!");
        }
    }
}
SayHelloInstaller.cs
using Zenject;
namespace ZenjectSayHello
{
    public class SayHelloInstaller : MonoInstaller
    {
        public override void InstallBindings()
        {
            Container.Bind<IPerson>().To<Parent>().AsSingle();
            Container.Bind<SayHello>().AsSingle();
        }
    }
}
SayHelloMonoBehaviour.cs
using UnityEngine;
using Zenject;
namespace ZenjectSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject] private SayHello _sayHello = default;
    }
}

SayHelloMonoBehaviourは適当なGameObjectにアタッチします。
SayHelloがResolveされるときに、コンストラクタのIPersonにParentが放り込まれ、
スクリーンショット 2020-07-02 20.26.20.png
と出ます。こいつをVContainerで書いてみます。

VContainerで書く

インストール

まずインストール方法ですが、上記Githubリンクのリリースページにunitypackageがありますので、それをダウンロード、インストールするだけでOKです。

インストーラーを作る

最初から手で書いてもいいのですが、テンプレートを自動生成してくれます。Projectの右クリックからCreate > C# Script。で、ファイル名をXXXInstallerとすれば、Installerのテンプレートを作ってくれます。

LifetimeScopeを作る

Zenject/ExtenjectのSceneContext等にあたるものがLifetimeScopeになります。作り方はHierarchyの右クリックでVContainer > LifetimeScope。で、InspectorのMonoInstallerの+ボタンをクリックすると、さっき作ったSayHellowInstallerが出てきます。指定したInstallerはLifetimeScopeGameObjectにアタッチされます。
スクリーンショット 2020-07-02 21.04.50.png

コード書く

コードを書いていきます。

IPerson.cs
namespace VContainerSayHello
{
    public interface IPerson
    {
        string Name { get; }
    }
}
Parent.cs
namespace VContainerSayHello
{
    public class Parent : IPerson
    {
        public string Name => "親御";
    }
}
SayHello.cs
using UnityEngine;
namespace VContainerSayHello
{
    public class SayHello
    {
        public SayHello(IPerson person)
        {
            Debug.Log($"{person.Name}さんによろしく!");
        }
    }
}

ここまではさっきと同じです。

Injectの扱い

SayHelloInstaller.cs
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
        }
    }
}

とりあえずInstallerをこう書きます。実際に書いたのはInstallメソッド内の2行です。

次は、MonoBehaviourです。Zenject/ExtenjectだとMonoBehaviourにInjectを書いておけば、どこでもInjectしてくれました。VContainerはただHierarchyに存在してるだけではInjectを発動してくれません。

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject] private SayHello _sayHello; //Injectされない

        [Inject]
        public void Construct(SayHello sayHello)
        { //呼ばれない
        }
    }
}

例えば、エディター上で適当なGameObjectを作って、上述のSayHelloMonoBehaviour.csをアタッチ。再生ボタンを押したとき、Extenject/ZenjectはInjectしてくれますが、VContainerはやってくれません。

Injectの発動条件ですが、Resolveする必要があります。例えば

SayHelloMonoBehaviourにIInitializable等を実装

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour, IInitializable
    {
        [Inject]
        public void Construct(SayHello sayHello)
        {
        }

        public void Initialize()
        {
        }
    }
}
SayHelloInstaller.cs
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
            builder.RegisterComponentInHierarchy<SayHelloMonoBehaviour>().As<IInitializable>();
            //HierarchyにあるSayHelloMonoBehaviourのInitializeを発火。Resolveされる。
        }
    }
}

IInitializableをMonoBehaviourに実装してAsで発火します。

何かのコンストラクタでResolveさせる

SayHelloMonoBehaviour.cs
using UnityEngine;
using VContainer;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviour : MonoBehaviour
    {
        [Inject]
        public void Construct(SayHello sayHello)
        {
        }
    }
}
SayHelloMonoBehaviourInjector.cs
using VContainer.Unity;

namespace VContainerSayHello
{
    public class SayHelloMonoBehaviourInjector : IInitializable
    {
        public SayHelloMonoBehaviourInjector(SayHelloMonoBehaviour sayHelloMonoBehaviour)
        {
        }

        public void Initialize()
        {
        }
    }
}
SayHelloInstaller.cs
using VContainer;
using VContainer.Unity;

namespace VContainerSayHello
{
    public sealed class SayHelloInstaller : MonoInstaller
    {
        [SerializeField] private SayHelloMonoBehaviour sayHello = default;

        public override void Install(IContainerBuilder builder)
        {
            builder.Register<IPerson, Parent>(Lifetime.Singleton);
            builder.Register<SayHello>(Lifetime.Singleton);
            builder.RegisterInstance(sayHello);
            builder.Register<SayHelloMonoBehaviourInjector>(Lifetime.Singleton).As<IInitializable>();
            //SayHelloMonoBehaviourInjectorがInitializeの発火でResolveされる。
            //その際、コンストラクタ引数にSayHelloMonoBehaviourがあるのでsayHelloインスタンスがResolveされる。
        }
    }
}

RegisterInstanceで該当のMonoBehaviourをRegisterして、適当なコンストラクタにぶち込みます。そのときResolveされます。
IInitializableをAsすることで、Zenject/ExtenjectのNonLazy的に扱えます。

Instantiateとかはどうなんの?

試しにPrefabをInstantiateしてみましたが、Injectは発火しません。PlaceholderFactoryもありません(少なくとも現在は)。DiContainer.Injectのようなものもないようです。つまり自前でFactoryを作って、必要な依存をそこで解決しましょう。

動作

スクリーンショット 2020-07-03 22.15.25.png
動きました。(ちゃんと撮り直した)

所感

 [Inject]の使い方にちょっと混乱しましたが、使えそうです。この辺りのZenject/Extenjectとの違いは軽量化のためでしょうか。
 一つ興味深いのはZenject/ExtenjectにあるFactory関係の実装予定はないそうで。まぁ確かにあれはPlaceholderFactory作ってそれをSpawnerで包んで、なんか手間だなぁ、と感じたり。いまいち使いこなせてない感がありました。ですんで、多機能よりも軽量化に注力するのは大歓迎です。今後に期待です。

追記(7/2)

 すみません。同じProjectにあったZenjectと競合してました。名前空間を貫通してくるとは思いませんでした。IInitialize関係がこちらの追記にあったのですが、上の方にスクリプト修正の上で転記しました。

追記(7/3)

 ハダシAさんに質問した所、Zenject/Extenjectのような一方的なInjectは想定しておらず、Resolveが必要とのご回答をいただきました。Zenject/Extenjectのように使っていたInjectを上述のとおり修正いたしました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity Technologies製推論エンジン Barracudaがスゴイという話

概要

2020年5月12日のML-Agents正式版公開に伴い、
ML-Agentsで利用されている推論エンジンのBarracudaが正式公開されたのでテストした所
導入および実施がありえない程簡単だったので記事にしました

Unity Technologies 公式ブログ

実施内容

以前実施した画像分類モデルに加え、画風変換モデルをテストしました。

1.画像分類

空間内に配置した写真をUnityのカメラで撮影し、画像分類モデルで判定を行いました
sc.PNG

結果は次のようになりました

malchese.PNG
Maltese dog

convertible.PNG
convertible(オープンカー)
見た限り、正しく分類できていそうです。

使用したモデルは次の通りです

画像分類
モデル名 VGG-19
入力 224×224×3
出力 1000
データセット ImageNet (ILSVRCA2012)

2.画風変換

画像分類同様、空間内に配置した写真をUnityのカメラで撮影し、画風変換モデルを適用したものを表示しました

candy.PNG
イラスト風

mosaic.PNG
ステンドグラス風
シェーダーみたいに使えたら面白そうですね

画風変換
モデル名 Fast Neural Style Transfer
入力 224×224×3
出力 224×224×3
データセット COCO 2014 Training images dataset

学習済みモデルの配布元

上で示した通り、Unity上で学習済みモデルを推論できることが分かりました。
次から実施方法について解説します。

導入方法

1. PackageMangerを開き、Barracudaをインストールします。

マネージャー.PNG
barracuda.PNG

2. スクリプトを作成し、Unity.Barracudaをインポートします。
3. NNModelをPublic変数で定義します
using UnityEngine;
using UnityEngine.UI;
using Unity.Barracuda;  // <- 2.

public class Classification : MonoBehaviour
{
    //  Barracuda 推論用
    public NNModel modelAsset;  // <- 3.
    private Model m_RuntimeModel;
    private IWorker m_worker;
4. 適当なオブジェクトにスクリプトを追加し、InspectorからONNXファイル(学習済みモデル)をアタッチします。

barracuda.PNG

5. モデルをロードし、推論を実行するワーカーを作成します
    void Start()
    {
        m_RuntimeModel = ModelLoader.Load(modelAsset);
        m_worker = WorkerFactory.CreateWorker(WorkerFactory.Type.Compute, m_RuntimeModel);
    }

CPU・GPUの使用はここで決定します。
Workerのタイプ

6. ProjectタブからRenderTextureを作成します

barracuda.PNG

7. Hierarchyからカメラを作成し、出力先を先ほど作成したRenderTextureにします

barracuda.PNG
barracuda.PNG

8. 作成したRenderTextureをスクリプトにアタッチし、次のようなコードで推論を実行します
   Tensor input = new Tensor(targetTexture); 

   m_worker.Execute(input);
   Tensor output = m_worker.PeekOutput();

 入力はRenderTextureから直接作成できます!!
 以前実施した際には前処理が色々面倒だったのでここだけでも使う価値があると思います。

9. 出力は次のような形で取り出せます
  output.ToReadOnlyArray();  //floatの配列で取得する

  output.ToRenderTexture(outputTexture, 0, 0, 1/255f, 0, null);  //RenderTextureに直接出力

※Tensor及びworkerは処理が終了したタイミングで破棄する必要があります

  private void Update()
  {
      Tensor input = new Tensor(inputTexture); 
      Inference(input);

      input.Dispose(); //処理が終わったタイミングで破棄
  }

  private void OnDestroy()
  {
      m_worker.Dispose(); //終了時に破棄する
  }

まとめ

今回のポイント

  • 入力が簡単
  • モデルをUnity上で管理できる
  • RenderTextureに直接出力できる

さすが公式の実装だけあって以前と比べてかなり簡単に実施できるようになっていました。
Unityはスマートフォンアプリへの出力にも対応しているので、ARアプリの作成等にも使っていけそうです。

リンク

公式ドキュメント
今回のコード

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

net::ERR_CLEARTEXT_NOT_PERMITTED

エラー:net::ERR_CLEARTEXT_NOT_PERMITTED

AndroidManifest.xmlに追加

・android:usesCleartextTraffic="true"

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】「1 exception was raised by workers」 の直し方

どうもマカロンです。

ここではUntiyで「1 exception was raised by workers」というエラーが出てしまった人たちを助けるために私がnoteの方で書かせていただいた記事を紹介させていただきます。

Unity エラー「1 exception was raised by workers」との死闘

【完全版】続・Unity エラー「1 exception was raised by workers」との死闘

全5項目紹介していますので、エラーが出てしまっている人はぜひ確認してみてください!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VIVE CosmosでSRWorksを使う

VIVE ProでARコンテンツを作成するために導入されていたSRWorksというパッケージがあります
VIVE Cosmosでもversion 0.9.0.3より利用可能になっているということで導入の方法を紹介します

機器概要

  • Unity 2019.3.2.f1
  • SteamVR Plugin 2.5.0
  • windows Laptop PC
  • VIVE Cosmos <導入はこちら>

準備

1. 必要ファイルのDL

先に以下のサイトからファイルをDLしておく
[DOWNLOAD] VIVE SRWorks (XR) SDK 0.9.0.3
追伸 -6/3-
SRWorksのversionが更新されました(v0.9.3.0)

Vive SRWorks SDK Release 0.9.3.0

必要なファイルは以下の3つ
- SRWorks (XR) SDK Unity Plugin (zip形式)
- SRWorks (XR) SDK Unity Samples (zip形式)
- SRWorks Runtime (msi形式)

他のファイルは導入に必要ないがサンプルなどがあるので各自必要な人はDLしてください
またUE4用のPluginとSampleもあるが今回はUnityで行う
キャプチャ_LI.jpg

2. SRWorks Runtimeをインストール

DLしたVIVE_SRWorksInstaller_0.9.0.3.msiを開く
image.png
Nextを押す
image.png
インストールする場所を決める
基本デフォルトのままでよい
EveryoneとJust meはどちらでも構わないと思うが一応適切に答えておく
image.png
Nextを押す
デバイスへの変更を許可するとインストールが始まる
image.png
完了したらCloseを押す

3. zipファイルの解凍

DLしたzip形式のファイルを解凍しておく

導入手順

最初にVIVE Cosmosは接続しておく

1. projectを新規作成する

3Dを選択
名前は任意でよい
無題.jpg

2. VIVE Cosmosの環境設定

VIVE Cosmosが利用できる環境を設定する
・SteamVR Pluginの導入 <導入の記事>

3. SRWorks-Pluginを導入する

先ほどDL,解凍したファイルの中にあるVive-SRWorks-Unity-Plugin.unitypackageをUnityへimportする
importはProjectウィンドウのAssetsの中に直接ファイルをDrag&Drop
もしくはタブからAssets > Import Package > Custom Package...でファイルを選択
image.png
image.png
Import Unity Packageウィンドウが出てくるのでそのままImportする
image.png

同様のやり方でVive-SRWorks-Unity-Experience.unitypackageもImportする

※Importすると何やらwarningがたくさん出てくるが気にしなくてよい
image.png

4.XR機能の設定

File/Build Settingsを開く
image.png
Player Settingsを開く
image.png
XR Plug-in ManagementからInitialing XR on Startupの項目にチェックを入れる
image.png
AssetにXRというファイルが追加されるので確認する
image.png

5. サンプルシーンを開く

Assets/ViveSR/Scenes/ViveSR_Sampleをopenする
するとSettingsのウィンドウが出てくるのでAcceptを押す
image.png
気にせずUpdate layersをクリック
image.png
完了したら実行してみる
image.png
前面カメラを通して前方の映像が見える
またAssets/Vive_SRExperience/Scenes/Demoを実行するとより様々なデモを体験することができる



参考記事

公式ページ
- Intro To VIVE SRWorks SDK
- VIVE Pro SRWorks SDK and VIVE Audio SDKs - downloads
- VIVE SRWorks

VIVE Developers
- Download

バグ
- VIVE SRWorks 0.8.0.2 demos crash. How to fix?
- ViveSR_Sample Unity Black Screen with Vive Cosmos Elite
- v0.9.0.3_Prebuilt_Samples-Unity Visual C++ ランタイム エラー
- Runtime Error Crash in Unity

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】簡単にSceneをまたいだ値の受け渡し

初めに

Scene間での値の受け渡しに関してはまだ様々なところでこっちの方がいい、あっちの方がいいなど議論されています、ですのでどれが正解なのかは各個人によって決まります。
今回はいくつかの方法をお伝えできればと思います。

目次

  • DontDestroyOnLoad
  • PlayerPrefs
  • 静的クラスまたは静的変数

DontDestroyOnLoad

UnityにはDontDestroyOnLoadという関数があります、この関数はObjectをSceneをまたいでも引き継がれるObjectにするための関数です。
このDontDestroyOnLoadなObjectに値を渡すことで次のSceneでも値を引き継ぐことができます。

サンプルコード

    //値を保存用
    public int Score { set; get;}

    void Start()
    {
    //↓これを呼び出せばDontDestroyObjectにできます
    DontDestroyOnLoad(gameObject);
    }

実行結果

FadeOut.gif

PlayerPrefs

PlayerPrefsに関しては過去にまとめた記事がありますのでこちらを参照してください。

サンプルコード

値を保存

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            PlayerPrefs.SetInt("Score",int.Parse(GetComponent<InputField>().text));
            SceneManager.LoadScene("2");
        }
    }

値を呼び出す

    void Start()
    {
        GetComponent<Text>().text = PlayerPrefs.GetInt("Score").ToString();
    }

実行結果

PlayerPrefs.gif

静的クラスまたは静的変数

静的クラスなどはゲーム開始時に最初にメモリ領域を確保してゲーム終了までそのメモリ領域を解放しませんので値が常に保持されているということができます。

サンプルコード

値保存用クラス

public static class Test
{
    public static int Score { set; get;}
}

値を入れる

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            Test.Score=int.Parse(GetComponent<InputField>().text);
            SceneManager.LoadScene("2");
        }
    }

値を呼び出す

    void Start()
    {
        GetComponent<Text>().text = Test.Score.ToString();
    }

実行結果

static.gif

まとめ

実行結果が少しわかりずらいのでなくてもよかったかな?って感じる…
ぶっちゃけScene間の値のやり取りって難しいですよね、今回記事にした内容以外にもいくつかやり方ありますし、そもそもScene遷移しないという選択肢もあるわけで…
とりあえず色々試して自分に一番合うものをお探しください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Sceneをまたいだ値の受け渡し方

初めに

Scene間での値の受け渡しに関してはまだ様々なところでこっちの方がいい、あっちの方がいいなど議論されています、ですのでどれが正解なのかは各個人によって決まります。
今回はいくつかの方法をお伝えできればと思います。

目次

  • DontDestroyOnLoad
  • PlayerPrefs
  • 静的クラスまたは静的変数

DontDestroyOnLoad

UnityにはDontDestroyOnLoadという関数があります、この関数はObjectをSceneをまたいでも引き継がれるObjectにするための関数です。
このDontDestroyOnLoadなObjectに値を渡すことで次のSceneでも値を引き継ぐことができます。

サンプルコード

    //値を保存用
    public int Score { set; get;}

    void Start()
    {
    //↓これを呼び出せばDontDestroyObjectにできます
    DontDestroyOnLoad(gameObject);
    }

実行結果

FadeOut.gif

PlayerPrefs

PlayerPrefsに関しては過去にまとめた記事がありますのでこちらを参照してください。

サンプルコード

値を保存

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            PlayerPrefs.SetInt("Score",int.Parse(GetComponent<InputField>().text));
            SceneManager.LoadScene("2");
        }
    }

値を呼び出す

    void Start()
    {
        GetComponent<Text>().text = PlayerPrefs.GetInt("Score").ToString();
    }

実行結果

PlayerPrefs.gif

静的クラスまたは静的変数

静的クラスなどはゲーム開始時に最初にメモリ領域を確保してゲーム終了までそのメモリ領域を解放しませんので値が常に保持されているということができます。

サンプルコード

値保存用クラス

public static class Test
{
    public static int Score { set; get;}
}

値を入れる

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            Test.Score=int.Parse(GetComponent<InputField>().text);
            SceneManager.LoadScene("2");
        }
    }

値を呼び出す

    void Start()
    {
        GetComponent<Text>().text = Test.Score.ToString();
    }

実行結果

static.gif

まとめ

実行結果が少しわかりずらいのでなくてもよかったかな?って感じる…
ぶっちゃけScene間の値のやり取りって難しいですよね、今回記事にした内容以外にもいくつかやり方ありますし、そもそもScene遷移しないという選択肢もあるわけで…
とりあえず色々試して自分に一番合うものをお探しください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MagicLeapコントローラのイベント取得方法

公式のチュートリアルの動画だと若干コードが古かったりしたのでメモ

詳細な動作については後日追記予定

以下のボタン操作のバインディングのメモ

  • バンパーボタン
  • ホームボタン
  • タッチパッド
  • トリガー

ホームボタン押下したらアプリを閉じるようにした
最初にサンプルアプリ作ったときにボタンイベント取得方法わからんくて適当なオブジェクトにアタッチしたコンポーネントで一定時間経過したらアプリ閉じるようにしてたのは内緒

using UnityEngine;
using UnityEngine.XR.MagicLeap;


/// <summary>
/// MagicLeapコントローラ.
/// </summary>
public class MlController : MonoBehaviour, IController
{

    [SerializeField] MLInput.Controller controller;

    void Start()
    {
        // コントローラの入力を有効にし,対応するイベントハンドラを登録する.
        MLInput.Start();
        MLInput.OnControllerButtonDown += OnButtonDown;
        MLInput.OnControllerButtonUp += OnButtonUp;

        MLInput.OnTriggerDown += OnTriggerDown;
        MLInput.OnTriggerUp += OnTriggerUp;

        MLInput.OnControllerTouchpadGestureStart += OnTouchPadGestureStart;
        MLInput.OnControllerTouchpadGestureContinue += OnTouchPadGestureContinue;
        MLInput.OnControllerTouchpadGestureEnd += OnTouchPadGestureEnd;
    }


    void OnDestroy()
    {
        // コントローラの入力を無効にし,登録していたイベントハンドラを削除する.
        MLInput.Stop();
        MLInput.OnControllerButtonDown -= OnButtonDown;
        MLInput.OnControllerButtonUp -= OnButtonUp;

        MLInput.OnTriggerDown -= OnTriggerDown;
        MLInput.OnTriggerUp -= OnTriggerUp;

        MLInput.OnControllerTouchpadGestureStart -= OnTouchPadGestureStart;
        MLInput.OnControllerTouchpadGestureContinue -= OnTouchPadGestureContinue;
        MLInput.OnControllerTouchpadGestureEnd -= OnTouchPadGestureEnd;
    }


    /// <summary>
    /// ボタン押下時の処理.
    /// </summary>
    /// <param name="controllerId"></param>
    /// <param name="button"></param>
    void OnButtonDown(
        byte controllerId,
        MLInput.Controller.Button button)
    {
        switch (button)
        {
            case MLInput.Controller.Button.Bumper:
                break;

            case MLInput.Controller.Button.HomeTap:
                Application.Quit();
                break;
        }
    }


    /// <summary>
    /// ボタン押上時の処理.
    /// </summary>
    /// <param name="controllerId"></param>
    /// <param name="button"></param>
    void OnButtonUp(
        byte controllerId,
        MLInput.Controller.Button button)
    {
        switch (button)
        {
            case MLInput.Controller.Button.Bumper:
                break;

            case MLInput.Controller.Button.HomeTap:
                break;
        }
    }


    /// <summary>
    /// トリガーの押下処理.
    /// </summary>
    /// <param name="controllerId"></param>
    /// <param name="value"></param>
    void OnTriggerDown(
        byte controllerId,
        float value)
    {
    }


    /// <summary>
    /// トリガーの押上処理.
    /// </summary>
    /// <param name="controllerId"></param>
    /// <param name="value"></param>
    void OnTriggerUp(
        byte controllerId,
        float value)
    {
    }


    /// <summary>
    /// タッチパッドのジェスチャー始点.
    /// </summary>
    /// <param name="controllerId"></param>
    /// <param name="gesture"></param>
    void OnTouchPadGestureStart(
        byte controllerId,
        MLInput.Controller.TouchpadGesture gesture)
    {
    }

    /// <summary>
    /// タッチパッドのジェスチャー操作中.
    /// </summary>
    /// <param name="controllerId"></param>
    /// <param name="gesture"></param>
    void OnTouchPadGestureContinue(
        byte controllerId,
        MLInput.Controller.TouchpadGesture gesture)
    {
    }


    /// <summary>
    /// タッチパッドのジェスチャ終点.
    /// </summary>
    /// <param name="controllerId"></param>
    /// <param name="gesture"></param>
    void OnTouchPadGestureEnd(
        byte controllerId,
        MLInput.Controller.TouchpadGesture gesture)
    {
    }

}


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DOTween入門してみた ~1日目~

はじめに

いい感じにアニメーションできるという噂を耳にしDOTweenに入門してみました。
尚2日目以降の記事の予定はありません。
DOTweenとは何か、入手方法、基本的な機能などは、わかりやすく解説してくれている記事がたくさんあるのでこの記事では触れません。
よさげな参考文献は一番下に記載しておきます。

動作環境

・Windows10 Home
・Unity2019.4.0f1
・DOTween 1.2.335

Sequenceの無限ループさせても、gameObjectが消えるときにSequenceをKillさせたい

SequenceのSetloop(-1)により無限ループにさせると、gameObjectがdestroyされてもSequenceは実行され続けます。
そして実行を止めると「Tweenさせるオブジェクトがないよ」と警告が...
a.png

この警告はDOTweenがいい感じに処理してくれているもので、設定からsafeModeのチェックを外すと、警告だったものが大量のエラーになるはずです。
↓safeModeをONにしたまま、警告を消すこともできます。
https://kan-kikuchi.hatenablog.com/entry/DOTween_Safe_Mode

しかし、できれば警告すら起きないような実装にしたい...と思いまして。

SetLink(gameObject)を利用する ※2020/07/03追記

もうこれでいいです。
SetLink(gameObject)で、gameObjectがDestroyされるときに自動でKillしてくれます。

以下蛇足の3つの方法です。

Sequenceのフィールドを用意し、OnDestroyでKill

この実装の欠点。フィールドとしてSequenceを記憶しなければならないこと。

private Sequence currentSequence;

private void OnDestroy()
{
    this.currentSequence.Kill();
}

Sequence.OnUpdateでdestroyされていたらKill

Sequence sequence = DOTween.Sequence();
sequence.
    OnUpdate(() =>
    {
        if (this == null) sequence.Kill();
    })
    .SetLoops(-1)
    /*以下sequenceの設定をしていく*/
    .Play();

よし。これで無駄なフィールドを用意しなくて済みます。
動かしてみると...ちゃんとSequenceがKillされていることが確認できます。
しかし、先ほど同様警告が(今回はひとつだけ)出てしまいます。
safeModeの力を借りれば、この実装でもいいかもしれませんね。

OnDestroyで呼ぶUnityEventに代入

Sequenceというフィールドを用意するから邪魔なのであって、OnDestroyの際に呼ぶ処理を用意したら、邪魔にならないはず。

結局フィールドを用意することに変わりないのですが...。

private UnityEvent onDestroy = new UnityEvent();

//どこかの関数内-------------------
Sequence sequence = DOTween.Sequence();
sequence.
    .SetLoops(-1)
    /*以下sequenceの設定をしていく*/
    .Play();
this.onDestroy.AddListener(()=>sequence.Kill());
//--------------------------------

private void OnDestroy()
{
    this.onDestroy.Invoke();
}

余談(?) Sequenceではなく、直接呼んでも警告でた

DOTweenの記事をいろいろと漁っていたのですが、「SequenceでSetLoops(-1)の無限ループをすると、手動でKillしないといけない」みたいな話が多く、「問題解決のためには、Sequenceを使わないで、SetLoops(-1)にしよう」のような話も多くありました。
しかしですね、例えば以下のような処理をしたとしても、safeMode外したらバリバリエラー出ます...。
状態としてはOnUpdateでKillした3つ目の実装と同じですかね。

this.transform.DOBlendableRotateBy(this.transform.right * 200f, 3f, RotateMode.WorldAxisAdd)
    .SetLoops(-1)

a.png

というわけで3つ目のUnityEventを使った方法で、無事解決(サンプルとして無限に回転させてみました)

Tweener tweener = this.transform.DOBlendableRotateBy(this.transform.right * 200f, 3f, RotateMode.WorldAxisAdd)
    .SetLoops(-1)

this.onDestroy.AddListener(() => tweener.Kill());

Sequenceが1周するごとに処理をしたい

OnStepComplete()でできます。
Appendなどで複数まとめたときは、ひとまとまり全て終わった段階で呼ばれます。

前回のSequenceから初期化したくない

通常はひとつのSequenceが終了しループするたびに、開始時の状態に戻ります。
戻したくないときは、SetLoops(n, LoopType.Incremental)とする。

ここが不便だよDOTween

触っていくと、いろいろと不便に感じる点があったので書いていきます。
私もまだDOTweenを触ったばかりなので、「こんな実装方法があるよ」等あればぜひぜひ教えてほしいです泣

LoopType.Incrementalにしても、rotationは初期化される

前回のSequenceの終わったときの状態を考慮する、つまりSequenceループのたびに初期化をおこなわないというものですが、この効果が出るのはどうもpositionであり、rotationは初期化されてしまうんですよね...。
どぉ~してぇ~

ループ終わりのOnStepCompleteはあっても、ループ始まりのOnStepStartはない ※2020/07/03修正

AppendCallBackを使いましょう。
アニメーションの任意のタイミングにコールバックを加えることができます。便利!

OnStepCompleteはループ終了後の初期化処理がおこなわれたあとに呼ばれる

例えば以下のような処理があったとき、Debug.Log()で何が出力されるでしょうか。
これはループ終了時のpositionではなく、ループ開始前のpositionの値になります。
Debug.Log(Time.time)にしてみると、ちゃんと時間経過しているのがわかるため、OnStepCompleteはループ終了後の初期化処理が終わってから呼ばれていると推測できます。
これとrotationが初期化される点で、複雑な回転ループは難しいと感じます...

Sequence sequence = DOTween.Sequence()
    .Append(this.transform.DOBlendableLocalMoveBy(Vector3.right * 10f, 1f))
    .OnStepComplete(() => Debug.Log(this.transform.position))
    .SetLoops(-1)
    .Play();

Pause中はOnUpdateが呼ばれない

逆に呼ばれたら困ることのほうが多いので、これは助かりますが。
呼ばれないので、Update内で処理しましょう

実装例

といってもこの手の記事は、もっと優秀なものが沢山あるので少しだけです。

特定の垂直平面に沿った長方形周回移動

2020-07-02-00-10-36.gif

    /// <summary>
    /// 特定の垂直平面において(反)時計回りに長方形移動し続ける
    /// </summary>
    /// <param name="horizontalDirection">水平方向移動量</param>
    /// <param name="verticalLength">垂直方向移動量(正のとき初期位置から上がって下がる)</param>
    /// <param name="oneRoutineTime">長方形を一周する時間</param>
    private void MoveSquareSample(Vector3 horizontalDirection, float verticalLength, float oneRoutineTime)
    {
        if (horizontalDirection.y != 0) throw new System.Exception();
        float horizontalLength = horizontalDirection.magnitude;
        float horizontalTime = oneRoutineTime * (horizontalLength / (2 * horizontalLength + 2 * verticalLength));
        float verticalTime = oneRoutineTime * (verticalLength / (2 * horizontalLength + 2 * verticalLength));

        Sequence sequence = DOTween.Sequence();
        sequence
            .OnStepComplete(() =>
            {
                Debug.Log("1ループ終了");
            })
            .Append(this.transform.DOBlendableLocalMoveBy(Vector3.up * verticalLength, verticalTime))
            .Append(this.transform.DOBlendableLocalMoveBy(horizontalDirection, horizontalTime))
            .Append(this.transform.DOBlendableLocalMoveBy(Vector3.down * verticalLength, verticalTime))
            .Append(this.transform.DOBlendableLocalMoveBy(horizontalDirection * -1, horizontalTime))
            .SetRelative()
            .SetLoops(-1)
            .SetLink(this.gameObject)
            .Play();
    }

きりもみ回転

ここまでくると、もはやDOTweenを使う意味が...はい
あくまでなんちゃって。実用性はないですね

2020-07-02-00-37-21.gif

    /// <summary>
    /// きりもみ回転
    /// </summary>
    /// <param name="durationOfRotationUpAxis">transform.upを軸とした1回転の時間</param>
    /// <param name="durationOfRotationHorizontalAxis">horizontalAxisを軸とした1回転の時間</param>
    /// <param name="horizontalAxis">水平方向の回転軸。yは0</param>
    private void RotationSample(float durationOfRotationUpAxis, float durationOfRotationHorizontalAxis, Vector3 horizontalAxis)
    {
        if (horizontalAxis.y != 0f) throw new System.Exception();
        horizontalAxis = horizontalAxis.normalized;
        Sequence sequence = DOTween.Sequence();
        sequence
            .SetLoops(-1)
            .OnUpdate(() => 
            {
                this.transform.RotateAround(this.transform.position, horizontalAxis, 360f * Time.deltaTime / durationOfRotationHorizontalAxis);
                this.transform.RotateAround(this.transform.position, this.transform.up, 360f * Time.deltaTime / durationOfRotationUpAxis); 
            })
            .SetLink(this.gameObject)
            .Play();
    }

特定の軌道を往復し続ける

        this.sequence = DOTween.Sequence()
            .SetRelative()
            .SetLink(this.gameObject)
            .SetLoops(-1);
        foreach(Vector3 p in this.path.Concat(path.Reverse().Select(v => v * -1f)))
        {
            this.sequence.Append(this.transform.DOMove(p, p.magnitude / this.moveSpeed));
        }
        this.sequence.Play();

よさげな文献

最初に一通り目を通すのに良いと思います。
https://gist.github.com/anzfactory/da73149ba91626ba796d598578b163cc
https://amagamina.jp/how-to-dotween/
https://qiita.com/kagigi/items/bdf4d42835add07b0077

様々なEasingのグラフが並んでいます。
https://easings.net/

MoveとRotateに着目して、各種操作
https://qiita.com/BEATnonanka/items/378de2bca3c972a95399
https://qiita.com/BEATnonanka/items/b4cca6471e77466cec74

Sequenceによる統合
https://qiita.com/lycoris102/items/6a9e1e39bfa69880eaba

公式ドキュメント
http://dotween.demigiant.com/documentation.php

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Assets/Plugins はもう不要?→必要(な場合もある)

はじめに

これをつぶやいてから「いや、 Assets/Plugins はUnityの制限があるからしょうがなくない?」みたいなご指摘をいくつかいただいたので調べました(調べる前は「 PluginsResources と同じでどこにあっても・複数あっても同じ」という認識)。

結論から言うとUnityの仕様で Assets/Plugins に配置しないといけない場合がまだあったので諦めるしかない場合もある(Unityなんとかしてくれ?)という話でした。

マニュアル記載の Assets/Plugins の挙動

マニュアルのいろんなページに散らばってるのですが、以下のそれぞれで Assets/Plugins に関する挙動が説明されています。

Plug-ins

プラグイン をプロジェクトに追加して Unity の機能を拡張することができます。プラグインは、通常は C/C++ で作成されたネイティブの DLL です。それらは、サードパーティのコードライブラリ、システムコール、その他の Unity ビルトイン機能にアクセスできます。Unity が検出できるように、プラグインは常に Plugins というフォルダーに配置してください。

1 つの Plugins フォルダーのみを持つことができ、プロジェクトのルートに配置する必要があります。Assets フォルダー内に直接配置します。

このフォルダーがスクリプトのコンパイルに与える影響の詳細については、特殊フォルダーとスクリプトのコンパイル順 を、異なるターゲットプラットフォームのプラグインの管理については Plugin インスペクター を参照してください。

定義済みのアセンブリ

Unityは、プロジェクトフォルダー構造内のスクリプトファイルの場所に基づいて、4 つの異なるフェーズでスクリプトをコンパイルします。各フェーズのために、別々の CSharp プロジェクトファイル (.csproj) と事前定義されたアセンブリが作成されます。任意のフェーズに適当なスクリプトがない場合、Unity は対応するプロジェクトファイルやアセンブリを作成しません。

スクリプトが、異なるフェーズでコンパイルされた (つまり、別のアセンブリにある) クラスを参照する場合、コンパイル順はとても重要です。基本的なルールは、現在のフェーズ の後に コンパイルされたものは参照できないということです。現在のフェーズ、またはそれ以前のフェーズでコンパイルされたものは、すべて参照可能です。

コンパイルの各フェーズは下記のとおりです。

フェーズ アセンブリ名 スクリプトファイル
1 Assembly-CSharp-firstpass Standard Assets、Pro Standard Assets、Plugins という名のフォルダー内のランタイムスクリプト
2 Assembly-CSharp-Editor-firstpass Standard Assets、Pro Standard Assets、Plugins という名の最上位フォルダー内のあらゆる場所にある Editor という名のフォルダー内のエディタースクリプト
3 Assembly-CSharp Editor という名のフォルダーに入っていないすべてのスクリプト
4 Assembly-CSharp-Editor Editor という名のフォルダー内にある残りのすべてのスクリプト


(↑Pathは Assets/MyPlugin/MyPlugin.dll になってる)

デフォルト設定

Unity は、プラグインが置かれたフォルダーに応じて、プラグインファイルのインポート設定のデフォルトを設定します。

フォルダー デフォルト設定
(省略) (省略)
Assets/Plugins/iOS プラグインは iOS でのみ互換性があると判断されます。
(省略) (省略)

Unityのマニュアルではないですが、こんなブログ記事もありました。

Pluginsフォルダがありますけど名残というかわかりやすいようにしているだけです。Pluginsフォルダは必要ありません。


というわけで、 Assets/Plugins の仕様としては

  • ネイティブプラグインはここに入れる必要がある
  • コンパイルの順番に関係する(そもそも含まれるDLLに違いが出る)

の2点っぽいことがわかったので検証していきます。

若干古い気もしますがUnity2018.4.0f1にて行いました。

ネイティブプラグイン置き場として必須?

これについては先述の

プラグインは、通常は C/C++ で作成されたネイティブの DLL です。それらは、サードパーティのコードライブラリ、システムコール、その他の Unity ビルトイン機能にアクセスできます。Unity が検出できるように、プラグインは常に Plugins というフォルダーに配置してください。

を疑う形になりますが一応確認しておきます。インポート設定とファイルの配置を変えてiOS用にビルドしてみました。

Assets/Foo/ 以下で Any Platform の場合

マニュアルが正しければ含まれないはず。

↓結果

含まれました(!)

Assets/Plugins/iOS 以下でいずれのプラットフォームにも含めない設定にした場合


↓結果

含まれてません。

* * *

というわけで、マニュアルのスクショを見てもそうであるように、ネイティブプラグインファイルが Assets/Plugins 以下である必要はなさそうです(インスペクタからの設定に依存する)。

コンパイルの順番に関係する?

配置場所によって含まれるDLLが違うということで、スクリプトを Assets/Foo/Plugins/Assets/Plugins/ にそれぞれ配置して、含まれるDLLを調べました。

一応それぞれの *.csproj を覗いてみましたがインスペクタに表示されたDLL側にちゃんと含まれてました。あと当然 Assets/Foo/Plugins から Assets/Foo/ にスクリプトを移動しても結果は変わりませんでした。

* * *

というわけで、 Assets/Plugins/ とそれ以外にスクリプトを配置した場合とでは違いがあることがわかりました。

まとめ

結論としては

  • ネイティブプラグインの置き場所として Assets/Plugins 以下でなければならない制限はもうない
  • スクリプトが Assets/Plugins 以下だとコンパイル順に影響する
    • Assets/Plugins/ 以下なら Assembly-CSharp-firstpass.dll に含まれる
    • つまり最初にコンパイルされる

ということになりました。

正直後者のメリットというかそうしなければならない理由が思い浮かばず、アセット作者の方々は前者のことを知らずに今でも惰性で Assets/Plugins 以下に入れてる場合が多いんじゃないかなと思いました(今だと asmdef でなんとかなるのでは??)。

それと、【Unity】アプリに含めるアセットを抑えて、アプリサイズを小さくする(テラシュールブログ)によると

StreamingAssetsやPlugins/iOSやPlugins/Androidに含めたファイルは、そのまま含まれます。

とのことなので、もし Assets/Plugins に展開されるアセットがデモとかサンプルに画像を含みまくってたらビルド後のサイズが膨れそうなのでやめてほしいです(未検証)。

* * *

いろいろ書いてきましたがアセットを実際に作ってる方のほうが詳しいと思うので、間違い等あればご指摘ください。

あとコンパイル順に関して「 Assembly-CSharp-firstpass.dll じゃないとダメなんだぜ」というのがあればぜひ教えて下さい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む