うにてぃブログ

主にUnityとC#に関する記事を書いていきます

まとめ

ライブラリ

github.com

デバッグツール
Easing関数
Missing Script 検索ツール
モデル撮影ツール
Module化ライブラリ
Unity上で使える Git Tool
単純なメッシュを作成するツール
カメラ一覧を表示
アセットのディレクトリを移動する
テクスチャ生成ツール
対象のコンポーネントを利用している Prefab や Scene を探すツール
CustomInspector の テンプレートコードを出力するツール
アセットの参照を確認するツール
Hierarchy に存在するオブジェクトの参照を調べるツール
Texture を加工するツール
ProjectWindow を拡張するツール
AnimationClip の Path を一括で置換するツール

アドベントカレンダー

hacchi-man.hatenablog.com

ジグソーパズル自動生成

1 2 3 4 5 6 7

【Unity】あるオブジェクトを中心に回転させる

Transform.RotateAround を利用することで、対象のオブジェクト方を向きながら回転させることができる

この際に対象のオブジェクトとの距離は呼び出したタイミングの距離なので開始時に対象の位置に移動させている

そうすることで以下のように回転させることができる

using UnityEngine;

public class RotateSphere : MonoBehaviour
{
    [SerializeField]
    private Transform _target;
    [SerializeField]
    private float _speed = 10f;
    [SerializeField]
    private float _radius = 0.5f;

    private void Start()
    {
        // RotateAround は現在の距離が半径として計算されるので、予め対象半径の位置に移動する
        transform.position = _radius * (transform.position - _target.position).normalized + _target.position;       
    }

    private void Update()
    {
        if (Input.anyKey)
        {
            if (Input.GetKey(KeyCode.W))
            {
                transform.RotateAround(_target.position, transform.right, _speed);
            }
            if (Input.GetKey(KeyCode.S))
            {
                transform.RotateAround(_target.position, transform.right, -_speed);
            }
            
            if (Input.GetKey(KeyCode.A))
            {
                transform.RotateAround(_target.position, transform.up, _speed);
            }
            if (Input.GetKey(KeyCode.D))
            {
                transform.RotateAround(_target.position, transform.up, -_speed);
            }
        }
    }
}

【Unity】オブジェクトを今向いている方向に前後左右移動させる

transform.rotation に移動ベクトルを乗算すると今向いている向きから前後左右に移動させることができる

using UnityEngine;
 
public class PlayerController : MonoBehaviour
{
    [SerializeField]
    private float _speed = 1f;
     
    private void Update()
    {
        if (Input.anyKey)
        {
            var velocity = Vector3.zero;
            if (Input.GetKey(KeyCode.W))
            {
                velocity.z = _speed;
            }
            if (Input.GetKey(KeyCode.A))
            {
                velocity.x = -_speed;
            }
            if (Input.GetKey(KeyCode.S))
            {
                velocity.z = -_speed;
            }
            if (Input.GetKey(KeyCode.D))
            {
                velocity.x = _speed;
            }

            if (velocity.x != 0 || velocity.z != 0)
            {
                transform.position += transform.rotation * velocity;
            }
        }
    }
}

【Unity】VideoPlayer を RenderTexture を生成せずに利用する

VideoPlayer では 現在のフレームの Texture を参照することが可能なため、RenderTexture を生成して tagetTexture を指定しなくてもお手軽にテクスチャを表示できる

※ 内部的に RenderTexture を生成しているので RenderTexture を利用しないというわけではない

texture: Internal texture in which video content is placed. (Read Only)

しかし、設定や RenderTexture のサイズは指定できない

それを実行できるコンポーネントを以下に示す

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video;
 
[RequireComponent(typeof(RawImage))]
[RequireComponent(typeof(VideoPlayer))]
public class VideoPlayerApi : MonoBehaviour
{
    [SerializeField]
    private VideoPlayer _videoPlayer;
    [SerializeField]
    private RawImage _rawImage;
 
    private void Reset()
    {
        _rawImage = GetComponent<RawImage>();
        _videoPlayer = GetComponent<VideoPlayer>();
        _videoPlayer.renderMode = VideoRenderMode.APIOnly;
        _videoPlayer.playOnAwake = false;
    }
 
    private void Awake()
    {
        _rawImage.enabled = false;
    }
 
    public void Play(VideoClip _clip)
    {
        _videoPlayer.clip = _clip;
        _videoPlayer.sendFrameReadyEvents = true;
        // 参照できるのはそのフレームでの Texture なのでフレームごとにテクスチャを更新
        _videoPlayer.frameReady += (vp, frame) =>
        {
            if (!_rawImage.enabled)
                _rawImage.enabled = true;
             
            _rawImage.texture = _videoPlayer.texture;
        };
 
        _videoPlayer.prepareCompleted += vp =>
        {
            _videoPlayer.Play();
        };
         
        _videoPlayer.Prepare();
    }
}

【Unity】Format が ASTC の Texture を Mask に利用するとノイズが発生する

ATSC はテクスチャのサイズが小さくなるため、よく利用される Format ですが、これを Mask に利用すると ノイズが発生するようです

Unity の Issue Trackerでも同じ問題が上がっていますが

Some texture compression will leave artifacts in some pixels. As we are clipping in the shader we clip fragments when alpha is close to 0 (< 1/256 here). There is not much we can do to correct this. Turning off the texture compression or finding a different algorithm is the correct solution.

とあるように現状解決策が無いようなので、フォーマットを変更するか

テクスチャの設定で Alpha Source : From Gray Scaleもしくは Alpha is Transparency : false に設定を変更すると多少改善されるようです

【Unity】なぜ UnityEvent は RemoveAllListeners しないと開放されないのか

【Unity】UnityEvent で 利用した Delegate は初期化しないとリークする - うにてぃブログ

昨日の記事でリークすることが判明しましたが、なぜリークするのか内部処理を追ってみます

※ Unity2020.3.14f1 UnityEditor上 Memory Profilerで確認

UnityEvent

UnityEvent.csのコードにて AddListener を行った処理は以下になります

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
[Serializable]
public class UnityEvent : UnityEventBase
{
        public void AddListener(UnityAction call)
        {
            AddCall(GetDelegate(call));
        }
}
 
public abstract class UnityEventBase : ISerializationCallbackReceiver
{
        private InvokableCallList m_Calls;
  
        internal void AddCall(BaseInvokableCall call)
        {
            m_Calls.AddListener(call);
        }
}

同じく UnityEvent.cs 内にある Addlistener を見てみると m_RuntimeCalls に追加されており、クリアされているのは Clear を呼び出したタイミングになります

    class InvokableCallList
    {
        private readonly List<BaseInvokableCall> m_RuntimeCalls = new List<BaseInvokableCall>();

        public void AddListener(BaseInvokableCall call)
        {
            m_RuntimeCalls.Add(call);
        }

        public void Clear()
        {
            m_RuntimeCalls.Clear();
        }

これを呼び出しているのは RemoveAllListeners でした

public abstract class UnityEventBase : ISerializationCallbackReceiver
{
        public void RemoveAllListeners()
        {
            m_Calls.Clear();
        }
}

つまり、UnityEvent ではデリゲートが List にキャッシュされ、RemoveAllListener もしくは RemoveListener を呼ばない限りキャッシュから消されなさそうです

これは デリゲートを内部で利用した場合 (onClick.AddListener*1;) に限らず問題がありそうなので、UnityEvent に登録した場合は 開放されるタイミングで RemoveAllListener を呼び出したほうが無難かもしれません

*1:) => Delegate?.Invoke(

【Unity】UnityEvent で 利用した Delegate は初期化しないとリークする

デリゲートを利用してスクリプトを記述することはよくあるが、UnityEvent のリスナーに登録してる場合に破棄されたタイミングでメモリに残ってしまい、GC.Collect を行っても回収されないようです

実際に試してみる

※ Unity2020.3.14f1 UnityEditor上 Memory Profilerで確認

デリゲートを利用するクラスを記述し、インスタンスを作成したのちすぐ削除してみてる

public class SampleMonoBehaviour : MonoBehaviour
{
    [SerializeField]
    private DelegateCheck _check;
      
    private IEnumerator Start()
    {
        for (int i = 0; i < 10; i++)
        {
            var instance = Instantiate(_check, transform);
            instance.Delegate += Event;
            yield return null;
            Destroy(instance);
        }
 
        GC.Collect();
    }
 
    private void Event(DelegateCheck.DelegateEvent de)
    {
        Debug.Log("log");
    }
}
 
public class DelegateCheck : MonoBehaviour
{
    public class DelegateEvent { }
    public event Action<DelegateEvent> Delegate;
 
    private void Awake()
    {
        var button = gameObject.AddComponent<Button>();
        button.onClick.AddListener(() => Delegate?.Invoke(new DelegateEvent()));
    }
}

Memory Profiler で中身を確認すると、インスタンスは破棄しているはずなのに残っていることがわかる

削除時に初期化処理を追記する

先程のクラスに削除されたタイミングで初期化を行う処理を追加して確認してみる

public class DelegateCheck : MonoBehaviour
{
    private void OnDestroy()
    {
        Delegate = null;
    }
}

するとメモリには残ってないことが確認できる

しかしこれは button.onClick.RemoveAllListeners() でも同じくメモリから開放することができているので、UnityEvent の リスナーに登録した場合参照が残っているため消えないというのが正しそうです

そのため、以下のように AddListener せず Delegate に登録して呼び出すだけであればメモリに残ることはありませんでした

public class DelegateCheck : MonoBehaviour
{
    public class DelegateEvent { }
    public event Action<DelegateEvent> Delegate;
 
    public void Invoke()
    {
        Delegate.Invoke(new DelegateEvent());   
    }

PS.

デリゲートじゃなくて直接関数を登録した場合でも同じく残っていました

    _button.onClick.AddListener(Click);
 
    public void Click()
    {
        Delegate.Invoke(new DelegateEvent());   
    }

またラムダや実機の場合に挙動が違うみたいなので、時間があるときにでももう少し詳しく調べようと思います

【C#】ビット演算を利用したフラグ管理

FlagsAttribute を利用すると Enum をビットフラグとして利用することができる

値は1,2,4... と定義するのは面倒なので、1を何ビットシフトしたかで値を管理する

[System.Flags]
public enum Flag
{
    A = 1 << 0,
    B = 1 << 1,
    C = 1 << 2,
    // 全フラグ
    ALL = 1 << 3 - 1,
}

フラグの処理に関しては以下

フラグが立っているか

// 単一フラグ
var hasFlag = (bitFlag & Flag.A) == Flag.A;
 
// 複数フラグ
var hasFlag = (bitFlag & (Flag.A | Flag.B)) == (Flag.A || Flag.B);
 
// どれかのフラグ
var hasFlag = (bitFlag & (Flag.A | Flag.B)) != 0;

フラグを立てる

bitFlag |= Flag.A;

フラグを削除する

bitFlag &= ~Flag.A;