ユニツール

無知の知。

【Unity】Vector3.normalizedは無駄な負荷が掛かっている

概要

Vector3.normalizedは、2点の差を正規化して速度を掛けたり、内積を求める前に正規化したり、Vector操作の前処理で良く使わる機能です。 今回は、UnityのVector3.normalizedは値渡しが多くて無駄に負荷が掛かっているという話を聞いたので、Editor拡張の勉強がてら検証してみました。

検証

テストには、UnityEngine.Vector3にデフォルトで実装されているnormalizedと、自作のNormalizedExという2つを比べています。 テスト方法は、まず、10,000回実行した合計時間を1回分として算出します。
それを、1,000回繰り返し、1回に掛かった平均時間を結果とします。

以下、検証用コード

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Extentions.UnityEngine.Vector;

public class TestWindow : EditorWindow
{
    // 実行時間履歴
    List<System.TimeSpan> data = new List<System.TimeSpan>();
    int execute_count = 1;

    // GUIパラメータ
    Vector2 scroll_position = new Vector2(0, 0);

    GUILayoutOption[] button_option =
{
        GUILayout.Width(200),
        GUILayout.Height(25)
    };

    [MenuItem("Tools/テストウィンドウ")]
    static void Open()
    {
        var window = CreateInstance<TestWindow>();
        window.Show();
    }

    private void OnGUI()
    {
        // グループ開始
        GUILayout.BeginVertical();
        {
            // ボタン表示
            GUILayout.BeginHorizontal();
            {
                if (GUILayout.Button("通常Normalized実行", button_option))
                {
                    for (int i = 0; i < execute_count; ++i)
                        data.Add(TestExec1());
                }
                if (GUILayout.Button("軽量化Normalized実行", button_option))
                {
                    for (int i = 0; i < execute_count; ++i)
                        data.Add(TestExec2());
                }
                if (GUILayout.Button("リセット", button_option))
                {
                    data.Clear();
                }
            }
            GUILayout.EndHorizontal();
            GUILayout.Label("実行回数を入力");
            execute_count = EditorGUILayout.IntField(execute_count);
            
            var avg = 0.0d;// 平均値
            scroll_position = GUILayout.BeginScrollView(scroll_position);
            {
                for (int i = 0; i < data.Count; ++i)
                {
                    GUILayout.Label(data[i].TotalMilliseconds.ToString());
                    avg += data[i].TotalMilliseconds;
                }
                avg = avg / data.Count;
            }
            GUILayout.EndScrollView();

            // 結果表示
            GUILayout.BeginVertical();
            {
                GUILayout.Label(string.Format("平均値:{0}ms", avg.ToString("0.0000")));
                GUILayout.Label(string.Format("試行回数:{0}",data.Count));
            }
            GUILayout.EndVertical();
        }
        GUILayout.EndVertical();
    }
    
    /// <summary>
    /// 通常Normalizedテスト
    /// </summary>
    /// <returns></returns>
    public static System.TimeSpan TestExec1()
    {
        int TEST_COUNT = 10000;
        Vector3 hoge = new Vector3(100, 100, 100);
        Vector3 temp = Vector3.zero;
        var sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        {
            for (int i = 0; i < TEST_COUNT; ++i)
            {
                temp = hoge.normalized;
            }
        }
        sw.Stop();
        return sw.Elapsed;
    }

    /// <summary>
    /// 軽量化Normalizedテスト
    /// </summary>
    /// <returns></returns>
    public static System.TimeSpan TestExec2()
    {
        int TEST_COUNT = 10000;
        Vector3 hoge = new Vector3(100, 100, 100);
        Vector3 temp = Vector3.zero;
        var sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        {
            for (int i = 0; i < TEST_COUNT; ++i)
            {
                temp = hoge.NormalizedEx();
            }
        }
        sw.Stop();
        return sw.Elapsed;
    }
}



namespace Extentions.UnityEngine.Vector
{
    public static class Extention
    {
        // 正規化(軽量版)
        public static Vector3 NormalizedEx(this Vector3 vec)
        {
            float len = Mathf.Sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
            return new Vector3(vec.x / len, vec.y / len, vec.z / len);
        }
    }

}

検証結果

比較対象 結果
UnityEngine.Vector3.normalized(デフォルト) 0.8505ms
NormalizedEx(自作) 0.5645ms

という結果になりました。 処理の内容自体はほぼ同じですが、約1.5倍の短縮になるのが分かりました。

UnityEngine.Vector3.normalized(デフォルト) f:id:sheena-hikari-games:20181221221357p:plain NormalizedEx(自作) f:id:sheena-hikari-games:20181221221400p:plain

まとめ

これだけで、劇的に早くなる!というほどではありませんが、他にも、事前に計算して置いたり、参照をキャッシュしたり、といった事を意識して作ればそれなりの効果にはなるのかな、と思います。 塵も積もれば、の精神で気を付けていきたい。

【Unity】当たり判定が反応しない原因

概要

Unityで衝突時の処理を書く場合はOnCollisionEnterなどのイベント関数を使用しますが、関数を書いても処理が呼ばれないことがあります。

今回はその原因について紹介します。

 

2D用と3D用は別の判定

 接触判定用のイベント関数は、2D用と3D用で別々に用意されているので、アタッチしてあるCollider系コンポーネントが2Dなのに3D用の関数を書いている場合や、Collider系コンポーネントが3Dなのに2Dの関数を書いている場合は反応しません。

●3D用のイベント関数

void OnCollisionEnter(Collision collision)
void OnCollisionExit(Collision collision)
void OnCollisionStay(Collision collision)

●2D用のイベント関数 

void OnCollisionEnter2D(Collision2D collision)
void OnCollisionExit2D(Collision2D collision)
void OnCollisionStay2D(Collision2D collision)
RigidBodyが付いていない

接触判定を行うには接触する2つのオブジェクトのうち、どちらか、あるいは両方にRigidBody(2Dの場合:RidigBody2D)を付ける必要があります。

isTriggerの設定

 Collider系コンポーネントのisTriggerにチェックが入っていると、OnCollision系のイベントは反応しなくなります。

isTriggerを使用して衝突判定をしたい場合は、OnTrigger系のイベント関数を使います。

 

●3D用のイベント関数

void OnTriggerEnter(Collider collision)
void OnTriggerExit(Collider collision)
void OnTriggerStay(Collider collision)

●2D用のイベント関数 

void OnTriggerEnter2D(Collider2D collision)
void OnTriggerExit2D( Collider2D  collision)
void OnTriggerStay2D(Collider2D  collision)
イベント関数を書いたスクリプトがアタッチされていない

衝突判定のイベント関数は、衝突したオブジェクトにアタッチされていなければ呼び出されません。

 

 まとめ

Unityは衝突判定のメンドクサイ部分を内部でやってくれてとても助かるのですが、触り始めたばかりの人には、前提条件があったり、オプション一つ変えるだけで当たらなくなったりして分かりにくいかも、と思って記事にしました。

参考になれば幸いです。

Unityの新機能「ParticleSystemForceField」

概要

「ParticleSystemForceField」はUnity2018.3から新たに追加された機能です。
この機能は、ParticleSystemに力場の概念を加えるものになります。

準備

この機能を使うためにはParticleSystem側でも準備をする必要があります。

ParticleSystemのオプションに「ExternalForces」というものが追加されているのでチェックを付けてください。

これはParticleSystemForceFieldの影響を受けるか、という設定です。


Shape(力場の形状)

球体、半球、筒、箱の4種類から選べる
StartRange:力場が発生する半径
EndRange:力場が終了する半径

 

f:id:sheena-hikari-games:20181215175020p:plain

Direction(力場に接触したときに発生する移動ベクトル)

X軸、Y軸、Z軸それぞれに速度を設定できる。

力場内では指定した方向に力が発生する。

 

youtu.be

Gravity(力場に触れた際に力場中心に引っ張られる力)

Strength:引っ張る力
Focus:重力場が占める割合

 Focusは力場の外側を1で力場の中心を0として、何割を重力場にするかを設定します。

ここは少し複雑なんですが、重力の掛かり方は重力場と力場の関係によって変わります。

パーティクルが力場と重力場の両方に入っている場合は重力場は押し出す力が加わります。

逆に、パーティクルが力場に入っていて重力場から出ている場合は引っ張る力が働きます。

この時、Strengthの数値をマイナスにすると挙動が反転します。

youtu.be

Rotation(力場を中心にして回転する)

Speed:回転の速度
Attraction:パーティクルの中心への興味度
0から1で設定しますが、1の場合は力場に入った瞬間に力場を回り始めます。
逆に0の場合は全く興味を示さず影響を受けません。
Randomness:回転方向にランダム性を持たせます。

 

youtu.be

Drag(制動を掛ける)

Strength:止めようとする力の大きさ
MultiplyBySize:サイズの動きを抑えるか
MultiplyByVelocity:移動を抑えるか

 

youtu.be

VectorField(立体的な方向を持った力場)

これについてはあまり理解出来ていないのですが、3DTextureという
立体的なテクスチャに方向を描いて、その方向に力を加える機能だと思います。
VolumeTexture:移動方向を描いた立体テクスチャ
Speed:移動速度
Attraction:指定された方向への興味度

3Dテクスチャについてはこちらから

https://docs.unity3d.com/ja/current/Manual/class-Texture3D.html

これは使いこなせば面白い表現が出来そうなので時間があったら別の記事でまとめます。

 

まとめ

今回のアップデートでは、ParticleSystemForceFieldの他にもVisualEffectGraphというものも追加されていたり、演出回りがかなり強化されてきた感じがします。

ShaderGraphも楽しそうだし、いろいろやってみたいですね!

 

youtu.be

【Unity】SpriteAtlasの表示がおかしい

概要

共同開発しているプロジェクトで筆者のSpriteAtlasだけ表示が狂っていて、Atlas全体が表示されたり、何も表示されなかったりしたので原因と解決策をまとめました。

原因はLibraryフォルダ

Unityのメタデータを管理している、Libraryフォルダ内のキャッシュデータがズレてしまったのが原因でした。
何故ズレたのか心当たりは無いんですが、Unityを開かずにエクスプローラーから編集したとかだと思います。

解決策

プロジェクトフォルダ内の「Library」というフォルダを消せば解決します。
ただ、Libraryを消していいのは、EditorSettingsのVersionControl-ModeをVisibleMetaFilesで管理している場合だけです。 f:id:sheena-hikari-games:20181214224637p:plain 詳しくは下記のリンクから docs.unity3d.com

まとめ

分かってみれば単純なことだったんですが、Atlasのデータをちょっと変えると直ったかのような挙動をするのが曲者でした。
設定オプションのTightPackingのチェックを切り替えるとちゃんとパッキングされたり。
パッキング対象のTextureSizeを変えるとちゃんとパッキングされたり。
Gitを最新にするたびに毎回設定し直してたので、地味にストレスでした・・・。

今回の教訓、「プロジェクトが同じなのに挙動が違ったらLibraryを疑え」