20210108のUnityに関する記事は13件です。

Assenbly Definition Referencesを利用した状態でAddressableビルドをした際にUnityEditor云々と怒られた際の対処方法

概要

UnityEditorの拡張機能を作成した状態でAddressableのビルドをしようとしたらエラーが出ました。
その時の対応の内容です。

事象

Addressableのビルドをしようとしたら以下のエラーが発生。

image.png

error CS0234: The type or namespace name 'UI' does not exist in the namespace 'UnityEditor' (are you missing an assembly reference?)

image.png

画像にある「Assets/Plugins/Commons/Editgor/ToggleImageEditor」というのがエディタ拡張で作成してみたクラス。
それを読みにいってる。

原因と解決

ビルド効率化のためにAssenbly Definition Referencesを使ってるんだけど、そこでEditorのビルドプラットフォームにAndroid / iOSが入ってたのが原因っぽい。
どうも、Addressableのビルドする際には対象のプラットフォームでビルドするみたいですね。
ということで、PlatformsからAndroid / iOSを外したらビルドできるようになった。

image.png

多分、 Linux 64-bit以降のも全部外した方がよさそう。
というかEditorだけにしていいんじゃないかなとは思う。まぁ、実害が出るまでは放置。

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

UnityのInputSystemPackageをReactivePropertyに変換する

はじめに

新しいInputSystemへの移行をReactivePropertyを用いて行います

環境

Unity2020.2

従来のUnityEngine.Inputからの移行

InputAction自体がInputすべての汎用クラスなので
旧InputのInput.GetButton/Input.GetAxisと同様に使えるよう
3パターンに分けてReactivePropertyへ変換を行います。

  • Button 旧:Input.GetButton(Down/Up)
  • Delta 旧:Input.GetAxis(MouseX) マウスの移動量
  • Axis 旧:Input.GetAxis 例Joystick,Horizontal

変換には今回作成した拡張メソッドを使用しています。

InputActionの設定

  • Button

image.png
image.png
こちらはデフォルトで大丈夫ですがButtonにしておくとBindingがボタンのみ表示されます
image.png
Bindingには押したいボタンを
TriggerBehabiorにはPressAndReleaseを設定します

  • Delta

image.png
image.png
こちらはデフォルトで大丈夫ですがAxisにしておくとBindingがAxisのみ表示されます
image.png

  • Axis

image.png
ジョイスティックの場合はドリフトするのでDeadZoneを設定すると良いです。
image.png
キーボードからAxisを作成する場合1DAxisを行うと作成できます。

InputActionをReactivePropertyに変換する拡張メソッド

InputSystemExtension.cs
using System.Collections;
using System.Collections.Generic;
using UniRx;
using UnityEngine;
using UnityEngine.InputSystem;

public static class InputSystemExtension
{
        public static ReadOnlyReactiveProperty<bool> GetButtonProperty(this InputAction inputAction)
        {
            return Observable.FromEvent<InputAction.CallbackContext>(
                    h => inputAction.performed += h,
                    h => inputAction.performed -= h)
                .Select(x => x.ReadValueAsButton())
                .ToReadOnlyReactiveProperty(false);
        }

        public static ReadOnlyReactiveProperty<float> GetAxisProperty(this InputAction inputAction)
        {
            return Observable.FromEvent<InputAction.CallbackContext>(
                    h => inputAction.performed += h,
                    h => inputAction.performed -= h)
                .Select(x => x.ReadValue<float>())
                .ToReadOnlyReactiveProperty(0);
        }

        //Delta入力はUpdate基準なのでUpdate基準に変換(主にマウスで使用)
        public static ReadOnlyReactiveProperty<float> GetDeltaAxisProperty(this InputAction inputAction)
        {
            return Observable.EveryUpdate().Select(_ => inputAction.ReadValue<float>()).ToReadOnlyReactiveProperty(0);
        }

}

テストコード

TestInput.cs
using System;
using UnityEngine;
using UnityEngine.InputSystem;
using UniRx;

public class TestInput : MonoBehaviour,IDisposable
{
    //※今回はInputActionMapを用いず別々にInputActionを定義していますがInputActionMap

    [SerializeField]
    private InputActionMap inputAction;

    private ReadOnlyReactiveProperty<bool> attack = default;
    //ボタンに変換したIReactivePropertyを公開
    public IReadOnlyReactiveProperty<bool> Attack => attack;

    private ReadOnlyReactiveProperty<float> mouseX = default;
    //Mouseの移動量に変換したIReactivePropertyを公開
    public IReadOnlyReactiveProperty<float> MouseX => mouseX;

    /// <summary>
    /// 2ボタンで-1 0 1出力
    /// </summary>
    private ReadOnlyReactiveProperty<float> horizontal = default;
    //Mouseの移動量に変換したIReactivePropertyを公開
    public IReadOnlyReactiveProperty<float> Horizontal => horizontal;

    private void OnEnable()
    {
        inputAction.Enable();
    }

    private void OnDisable()
    {
        inputAction.Disable();
    }

    private void Awake()
    {
        attack =inputAction.FindAction("Attack").GetButtonProperty();
        mouseX = inputAction.FindAction("MouseX").GetDeltaAxisProperty();
        horizontal = inputAction.FindAction("Horizontal").GetAxisProperty();
    }

    private void OnDestroy()
    {
        Dispose();
    }

    public void Dispose()
    {
        inputAction?.Dispose();
        attack?.Dispose();
        mouseX?.Dispose();
        horizontal?.Dispose();
    }
}
Test.cs
using UnityEngine;
using UniRx;
using UniRx.Triggers;

public class Test : MonoBehaviour
{
    [SerializeField]
    private TestInput input;

    private void Start()
    {
        input.Attack.Subscribe(x=>
        {
            if (x)
            {
                Debug.Log("GetButtonDown");
            }
            else
            {
                Debug.Log("GetButtonUp");
            }
        }).AddTo(this);

        this.UpdateAsObservable().Subscribe(_ =>
        {
            Debug.Log("GetButton:" + input.Attack.Value);

            Debug.Log("Horizontal"+ input.Horizontal);

            Debug.Log("MouseX" + input.MouseX);

        }).AddTo(this);        
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityの新しいInputSystemをReactivePropertyに変換し使用する

はじめに

新しいInputSystemへの移行をReactivePropertyを用いて行います
新しいInputSystemそのままだと使いにくすぎる…

環境

Unity2020.2

従来のUnityEngine.Inputからの移行

InputAction自体がInputすべての汎用クラスなので
旧InputのInput.GetButton/Input.GetAxisと同様に使えるよう
3パターンに分けてReactivePropertyへ変換を行います。

  • Button 旧:Input.GetButton(Down/Up)
  • Delta 旧:Input.GetAxis(MouseX) 例マウスの移動量
  • Axis 旧:Input.GetAxis 例Joystick,Horizontal

変換には今回作成した拡張メソッドを使用しています。

InputActionの設定

  • Button

image.png
image.png
こちらはデフォルトで大丈夫ですがButtonにしておくとBindingがボタンのみ表示されます
image.png
Bindingには押したいボタンを
TriggerBehabiorにはPressAndReleaseを設定します

  • Delta

image.png
image.png
こちらはデフォルトで大丈夫ですがAxisにしておくとBindingがAxisのみ表示されます
image.png

  • Axis

image.png
ジョイスティックの場合はドリフトするのでDeadZoneを設定すると良いです。
image.png
キーボードからAxisを作成する場合1DAxisを行うと作成できます。

InputActionをReactivePropertyに変換する拡張メソッド

InputSystemExtension.cs
using System.Collections;
using System.Collections.Generic;
using UniRx;
using UnityEngine;
using UnityEngine.InputSystem;

public static class InputSystemExtension
{
        public static ReadOnlyReactiveProperty<bool> GetButtonProperty(this InputAction inputAction)
        {
            return Observable.FromEvent<InputAction.CallbackContext>(
                    h => inputAction.performed += h,
                    h => inputAction.performed -= h)
                .Select(x => x.ReadValueAsButton())
                .ToReadOnlyReactiveProperty(false);
        }

        public static ReadOnlyReactiveProperty<float> GetAxisProperty(this InputAction inputAction)
        {
            return Observable.FromEvent<InputAction.CallbackContext>(
                    h => inputAction.performed += h,
                    h => inputAction.performed -= h)
                .Select(x => x.ReadValue<float>())
                .ToReadOnlyReactiveProperty(0);
        }

        //Delta入力はUpdate基準なのでUpdate基準に変換(主にマウスで使用)
        public static ReadOnlyReactiveProperty<float> GetDeltaAxisProperty(this InputAction inputAction)
        {
            return Observable.EveryUpdate().Select(_ => inputAction.ReadValue<float>()).ToReadOnlyReactiveProperty(0);
        }

}

テストコード

TestInput.cs
using System;
using UnityEngine;
using UnityEngine.InputSystem;
using UniRx;

public class TestInput : MonoBehaviour,IDisposable
{

    [SerializeField]
    private InputActionMap inputAction;

    private ReadOnlyReactiveProperty<bool> attack = default;
    //ボタンに変換したIReactivePropertyを公開
    public IReadOnlyReactiveProperty<bool> Attack => attack;

    private ReadOnlyReactiveProperty<float> mouseX = default;
    //Mouseの移動量に変換したIReactivePropertyを公開
    public IReadOnlyReactiveProperty<float> MouseX => mouseX;

    /// <summary>
    /// 2ボタンで-1 0 1出力
    /// </summary>
    private ReadOnlyReactiveProperty<float> horizontal = default;
    //Mouseの移動量に変換したIReactivePropertyを公開
    public IReadOnlyReactiveProperty<float> Horizontal => horizontal;

    private void OnEnable()
    {
        inputAction.Enable();
    }

    private void OnDisable()
    {
        inputAction.Disable();
    }

    private void Awake()
    {
        attack =inputAction.FindAction("Attack").GetButtonProperty();
        mouseX = inputAction.FindAction("MouseX").GetDeltaAxisProperty();
        horizontal = inputAction.FindAction("Horizontal").GetAxisProperty();
    }

    private void OnDestroy()
    {
        Dispose();
    }

    public void Dispose()
    {
        inputAction?.Dispose();
        attack?.Dispose();
        mouseX?.Dispose();
        horizontal?.Dispose();
    }
}
Test.cs
using UnityEngine;
using UniRx;
using UniRx.Triggers;

public class Test : MonoBehaviour
{
    [SerializeField]
    private TestInput input;

    private void Start()
    {
        input.Attack.Subscribe(x=>
        {
            if (x)
            {
                Debug.Log("GetButtonDown");
            }
            else
            {
                Debug.Log("GetButtonUp");
            }
        }).AddTo(this);

        this.UpdateAsObservable().Subscribe(_ =>
        {
            Debug.Log("GetButton:" + input.Attack.Value);

            Debug.Log("Horizontal"+ input.Horizontal);

            Debug.Log("MouseX" + input.MouseX);

        }).AddTo(this);        
    }
}

最後に

InputSystemPackageをそのまま使用するよりは楽に使えるようになったと思います。

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

Unityのサブスレッドで5msごとに処理

Unityのサブスレッドで5msごとに処理
多分こんな感じ

以下を参考にしました。
https://teratail.com/questions/109591

using UnityEngine;
using System.Threading;
using System;

public class UnitySubThreadTimer : MonoBehaviour
{
    Thread thread;

    // Start is called before the first frame update
    void Start()
    {
        thread = new Thread(new ThreadStart(SubThread));
        thread.Start();
    }
    void SubThread()
    {
        //Process once every 5ms
        long next = DateTime.Now.Ticks + 50000;
        long now;

        while (true)
        {
            try
            {
                Debug.Log(Time.time);
            }
            catch
            {
            }

            do
            {
                now = DateTime.Now.Ticks;
            } 
            while (now < next);
            next += 50000;
        }
    }

    void Stop()
    {
        thread.Abort();
    }

    void OnApplicationQuit()
    {
        thread.Abort();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PUBG類ゲームの物理的な衝突の計算方法

今回の主な話題:PUBG類ゲームの物理的な衝突の計算方法、座標が比較的大きい場合のモデルのジッタとフラッシュ、BackBufferのデフォルトサイズの変更、PostProcessingBloomのパフォーマンスコスト。


サーバー同期

Q1: PUBG類ゲームに強同期は必要のため、フレーム同期が使用される場合が多く、クライアントが物理エンジンを直接使用できない場合、またはサーバーが同期状態で衝突を計算する必要がある場合があります。 この時に衝突の部分に対してどうすればよいですか? データ構造はどうですか?

技術的な解決策を選択できる場合、PUBGのような3D自由視点FPSゲームにフレーム同期を選択することは決してありません。原因は以下になります。
1、射撃ゲームのプレイヤーたちは、動き、射撃などの操作で強い手触りの要求があり、フレーム同期が即時の操作フィードバックをサポートすることは困難です。
2、自由視点なPUBG類ゲームには戦争の霧はありませんが、視距上の問題があります。フレーム同期を使用してすべての情報をプレーヤーたちにブロードキャストすれば、チートプラグインを作るには簡単すぎです。そして、PUBG Mobileなどのゲームで敵の位置を表示するプラグインには大きな利点があるので、本質的に適用しません。

問題主が言及したフレーム同期が、私が理解しているフレーム同期と一致しているかどうかはわかりません。次に、状態の同期に基づいて、サーバーは物理を実行できます。しかし、実際の物理は完全にサーバー上で実行される場合、サーバーへの圧力が高すぎ、コストが高すぎます。1台の物理マシンも多数の同時オンラインプレーヤーを当れない場合もあります。 実行又は維持用コストが許容できるかどうかを評価するため。よく見られる解決策は:
1)3D物理を単純化し、コンテキスト状態に基づいて、ヒットなどのいくつかのキー判断のみを行います。但し、物理上のデータ計算はより敏感であるため、浮動小数点エラーは一貫性のない結果を引き起こす可能性があります。ここには多くの作業とトラップがあるはずです。
2)3D物理システムの代わりに2D物理システムを使用してそれを行い、物理計算で高さを削除し、特別な検出プロセスと組み合わせて2D衝突のみを行います。これは、実行可能な検出ソリューションの一つであります。
3)クライアントの判断、サーバーの検証、最初にクライアントの判断を信じ、次にクライアントのパフォーマンスを実行し、最後にサーバーがデータに基づいて検証します。ここでは、2D物理エンジンを使用する可能性があり、高さなどの情報をコアな判断に追加する可能性もあります。
4)クライアントの判定、厳格なチートプラグイン防止策、アカウントを停止するなどの手段も追加します。もちろん、プレイヤーの不正行為を判断できる必要があります。判断方法はリアルタイムではなくでも大丈夫です、フレーム同期類似な方法で後に再生して判断しても大丈夫です。この度、3D物理を使用してやる可能性があります。(経験的にはまだ推薦しません、ただリアルタイムからオフラインになります、パフォーマンスへの要求はあまり高くません、3Dを可能になさせます)
5)まずは作りません、ゲームが人気になって、収益化ができれば追加します。人気がなければ、それを台無しにするプロのチートプラグインチームはありません。

データ構造は細かすぎますので、必要に応じて自分でデザインしてください。 2Dは、オープンソースの2Dエンジンを直接統合すれば大丈夫です。

以下の内容を補充します。

1)このゲームには、キャラクター以外、ドア、車両、資源など、同期する必要のあるサードパーティの情報がたくさんあります。 フレーム同期の場合、弾丸や消耗品などを同期させる必要もあります。PC端末に対しても膨大な情報量です。 したがって、状態同期を使用する方が現実的です。
2)UnrealEngineに余り精通していませんが、Unreal EngineはNVIDIAのPhysxを使用しています。今、市場にあるPUBG類ゲームはすべてこれに基づいているはずです。では、サーバーがリアルタイムまたはオフラインでPhysxを計算する方法も参照できるはずです。 Overwatchの同期滑らかに関する記事があったと思って、問題主はそれを参照できます。
3)実際、物理的な衝突は大きな問題ではなく、チートプラグインの自動的にねらうと透視が大きな問題です。初期のPUBGは、キャラクターの速度さえ検証もしていませんでしたが、物理的な衝突のためにフレーム同期を使ったら、他の方面に問題がより多くなる可能性があります。

前の皆さんはもう非常によく答えられていると思います。アンチートプラグイン方式を追加しましょう。
最初に、関するクライアントに、元々サーバーで実行する必要のある複雑な計算の結果を送信させ、次に結果、計算、およびパラメーターを複数のクライアントに送信して実行させ、投票検証を行うことができます。これにより、一部のサーバーの負荷を軽減できます。

ValveSoftwareからの技術ドキュメント「SourceMultiplayerNetworking」を追加します。

この記事も状態同期アーキテクチャに基づいています。 その中で言及されている遅延補償とクライアント入力予測技術は、現在、遅延によって引き起こされる悪い体験に対する主流方法です。

PUBG類ゲームのプレイヤーは一般的に100人を超えます。この状況でフレーム同期を使用すれば、同期する必要のあるデータ量は大きくで、遅延もより深刻になります。フレーム同期は通常、同じシーン内のプレーヤー全員の入力を収集し、各クライアントに配布して、各クライアントは同じロジックを使用して各プレーヤーの位置と状態を計算します。ゲームのロジックはクライアントで実行します。MOBAゲームにはプレイヤーが10人しかいないため、フレーム同期に適しています。

PUBG類ゲームは状態同期に適していると思います。状態の同期では、キャラクターが多すぎることを恐れており、同期する必要のあるキャラクターが多すぎて、ネットワーク同期データを大きくなされます。PUBG類ゲームにはモンスターもいないし、全員プレイヤーなので、シーンにいるキャラクターはプレイヤーであります。取得できるアイテムが同期する必要のデータは少ないで、基本的に位置座標だけでできますが、プレイヤーの状態データの量ははるかに多いです。クライアントが見えないプレイヤーやアイテムの同期する必要が完全にありません。ロジックは完全にサーバー上で実行されるため、クライアントはサーバーのロジックに従って描画するだけで済みます。

ゲームの手触りに関しては、FPSゲームはすべてのゲームタイプの中で最も高い遅延要件を持っています。プレーヤーのネットワークが悪くと、より良い状況に最適化することは困難です。クライアントは先に表示の予測(たとえば、ヒットされた後の血痕)を行うことができますが、数値結果は依然としてサーバー側の計算に依存しています。そうしないと、チートプラグインに利用されやすいです。サーバーが「ヒットしなかった」と判断しても、大きな問題はありません。ほとんどの場合、プレーヤーは射撃がいくつか当たって、血の効果がありましたが、相手の血は減少していませんでした。この状況は、ほとんど悪いネットワーク環境で発生する可能性があります。


計算精度

Q2: 座標が比較的大きい場合(20000、0、20000)、私たちのプロジェクトには次の2つの問題があります。

1)モデルが移動すると、わずかなジッターが発生します。
2)Androidでカメラを回転すると、モデルがフラッシュします。

Androidのみでこの状況があり、iOSとPCでは正常です。 Unityバージョンは2017.4.2、テストプロジェクトはUWA Q&Aコミュニティにアップロードされています。

1.png
http://www.songho.ca/opengl/gl_projectionmatrix.html
NDC空間で、近切断面に近い点のZの精度はより高いで、遠切断面に近い点のZの精度は低いです。遠切断面の値が大きすぎると、Z-Fightingが発生しやすくなります。透視原理に基づいて、この問題は基本的に解決できなく、回避することだけを考えられます。

現有の参照できる方法を補充します:World Streamer

これは、超大規模なワールドStreamingの完全なソリューションであり、一つのFloating Point Fix機能も実現されています。これは、過大な座標によって引き起こされる精度不十分問題を解決するためのものです。具体的な方法は皆さんがもう言及され、世界の座標を引き戻すということです。さらに、NavMeshには問題があります。最新バージョンのWorld Streamerは、Unity5.6+のNavMeshの動きをすでにサポートしています。 実現コードは、この中のWorldMover.csを参照できます。 使用法については、彼らのドキュメント「Floating Point FixSystem」を参照してください。


レンダリング

Q3: 私たちのプロジェクトは最適化しています。私たちのプロジェクトは最適化しています。以前には、ゲーム内でシーンを小さいRenderTextureでレンダリングされ、最後にBackBufferに戻されました。しかし、そうするとRenderTextureの余分なコピーが生成されます。

以前の経験から見ると、BackBufferのデフォルトサイズを直接に変更でき、RenderTextureのコピーを回避し、RenderTextureのサイズも縮小できます。UnityがAndroidでBackBufferのサイズを直接変更できるかどうかはわかりません。Unity 5.5.4を使用しています。答えて欲しいです。実には、これは上位バージョンのUnityのDPI設定に似ていますが、現在、Unityバージョンを更新することはできません。

あるアイデアは、AndroidレイヤーSurfaceのサイズを強制的に変更することであり、可能かどうか又は互換性の問題を検証する必要があります。

さらに、解像度を下げるとUnityにある出力が存在すると覚えて、ハードウェアスケーリングがサポートされていない場合は、Blitが使用されます。Logcatでの出力は次のとおりです。
Hardware resolution scaling not supported, falling back to software scaling (blit).

関する資料(後半)
https://forum.unity.com/threads/standard-shader-for-mobile.368672

解像度を直接下げることをお勧めします。Android上のUnityには、ハードウェアスケーリングを試すプロセスがあります。
さらに、Unity 5はAndroid上で常にフルスクリーンのBlitを備えており、BackBufferに直接描画されることはありません。
備考:https://forum.unity.com/threads/big-performance-issue-with-unity5-on-android.338847


レンダリング

Q4: 了解したいものがあります。PostProcessing のBloomは、1つのLayerだけを開くコストと、すべてのLayerを開くのと同じですか?ポストスクリーン効果なので、レンダリングされた画像を1回だけ処理するので、自分のテストが正しいかどうかを確認したいと思います。

Githubの公式ドキュメントには次のように書かれています。
2.jpg
このLayerは、Unityの他の部分のLayerと似ています。Filterとして使用されるため、レンダリング効率は影響を受けませんが、Bloomする必要のないカメラに対しては、ブロックするように設定できます。これにより、効率を向上できます。


UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析最適化ソリューション及びコンサルティングサービスを提供している会社でございます。

UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com

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

Unityエンジンに表示される頂点が多すぎるのはなぜですか?

今回の主な話題:UVの継ぎ目の解決する方法、Unityにインポートした後に頂点/面の数が変更された原因、アニメーションオブジェクトのスムーズさを改善する方法、Socketを使用してネットワークプロトコルの送受信戦略を処理する方法。


エディター

Q1: 同じモデルがUnityに表示される頂点と面の数は、3dsMAXに表示される頂点/面の数よりはるかに多くなりますが、これがなぜですか?

問題主はUnity公式のドキュメントを参照できます。
https://docs.unity3d.com/Manual/FBXImporter-Model.html
https://docs.unity3d.com/Manual/class-Mesh.html

最後の段落に、

The Unity Editor shows too many vertices or triangles (compared to what my 3D app says)
This is correct. What you are looking at is the number of vertices/triangles actually being sent to the GPU for rendering. In addition to the case where the material requires them to be sent twice, other things like hard-normals and non-contiguous UVs increase vertex/triangle counts significantly compared to what a modeling app tells you. Triangles need to be contiguous in both 3D and UV space to form a strip, so when you have UV seams, degenerate triangles have to be made to form strips – this bumps up the count.

1.png


制作

Q2: 3DモデルをUnityにインポートした後、UVの継ぎ目は明らかに見えましたが、モデリングソフトでの表示は正常であり、UV展開が正しいことを確認できます。これはなぜですか?

これはMipmapが原因である可能性があります。 テクスチャは自動的にスケーリングされるため、エッジの情報が不足しているため、継ぎ目が明らかになります。 インポート設定でMipmapを閉じて、まだ問題があるかどうかを確認できます。

別の方法は、UVエッジの外側に数ピクセル拡張することです。たとえば、1024 * 1024のテクスチャエッジは8ピクセル外側に拡張します。


レンダリング

Q3: アニメーションオブジェクトのエッジを滑らかにしたいので、設定は次とおります。MSAAはもうオンになっていて、Rendering設定で8xアンチエイリアスを使いました。ただし、シーン内の静的オブジェクトのエッジは比較的滑らかですが、アニメーションオブジェクトは明らかなギザギザのエッジがあります。 滑らかさを改善し続ける方法は?
2.png

問題主はHDRをオフにして、改善するかどうかを確認できます。 Intel GPUで、HDRとMSAAをともに使用する場合、いくつかの問題を引き起こす可能性があります。 さらに、MSAAの代わりにPost Processing Stackを使用できます。


アセット管理

Q4: CommandBuffer.DrawMeshを使用してもSetPass Callsを減らすことができません。Renderで直接レンダリングすることと異なり、同じマテリアルを使用しても、SetPass Callsを減らすことはできません。Unityはマテリアルによって並べ替える機能を開けますか?

メッシュは単なる仮想概念であり、本質的には点、三角形、およびその他の属性です。Rendererも「直接レンダリング」ではありません、StaticBatchingもDynamicBatchingも、Unity自体が複数のMeshを合併し、一緒にDraw Callすることであります。
したがって、問題主の質問に対して、マテリアルの同じMeshを合併し、CommandBuffer.DrawMeshをコールすれば大丈夫はずです。


ロジックコード

Q5: UnityでC#のSocketを使用してネットワークプロトコルの送受信を処理する場合、独立スレッド処理、または非同期メソッド(BeginReceiveなど)を使用することのどちらが効率的ですか?

一般的な方法は、独立スレッド+非同期です。メインスレッドに配置せれば必ず他の論理シーケンスによって制限され、処理は遅延になります。非同期性も同じで、繁な読み取りを減らし、最下層に処理により簡単な状態を与えます。

Writeは通常、メインスレッド+非同期の方法で行います。Writeにはタイムリーな処理の問題がなく、単独にスレッドを設定する必要はありません、データのメンテナンスを減らすこともできます。非同期は、異常な状況がメインスレッドで妨害するのを防ぐことができます。


UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析最適化ソリューション及びコンサルティングサービスを提供している会社でございます。

UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com

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

Unity Personalでエディタテーマを黒色にする方法

Unityのエディターのテーマ黒にしたいなーと思ったそこの君!
これを見れば黒にPersonalのライセンスでできるぞ!
(これは公式のやり方です)
これをやり遂げるとこんな感じ
Desktop Screenshot 2021.01.08 - 17.19.32.71.png

環境

Unityバージョン:2019.4.16f1
このUnityバージョンでしかできません、
空き領域に空きがある人は入れないと
できないよ!
このバージョンも入れたくないという人は
あきらめた方がいいかもしれません。

OS:Windows 10

本文

まず環境で言った通りに
Unity 2019.4.16f1を入れましょう
その後この図に従ってください
BlackThemeTutorial.gif
上の図のようにEditを押してからPreferences
を押してGeneralを押してEditor Themeという
項目を探してLight => Darkにすると黒色にできます。
それでは別の記事で

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

Shaderで縁ぼかし効果を実現

Shaderを使うと、C#のコーディングで実現できなさそうなUIを簡単にできたりします。今回は写真や動画の縁にぼかしを入れるような仕様でシェーダーを実装してみました。

完成図

ぼかし.gif
縁に近いほど透明度が高くなり、漸進的なぼかしを実現しています。ぼかしの強さと全ての方向でぼかしの影響範囲を調整できます。

コード&説明

ImageAlpha.shader
Shader "Unlit/ImageAlpha"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AlphaLX("RangeAlphaLX",Float) = 0
        _AlphaRX("RangeAlphaRX",Float) = 1
        _AlphaTY("RangeAlphaTY",Float) = 1
        _AlphaBY("RangeAlphaBY",Float) = 0
        _AlphaPower("Power",Float) = 0 //ぼかしの強さ
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Back
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _AlphaPower;
            sampler2D _AlphaTex;
            float _AlphaLX;
            float _AlphaRX;
            float _AlphaTY;
            float _AlphaBY;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            fixed4 SampleSpriteTexture (float2 uv)
            {
                fixed4 color = tex2D (_MainTex, uv);

#if ETC1_EXTERNAL_ALPHA
                // get the color from an external texture (usecase: Alpha support for ETC1 on android)
                color.a = tex2D (_AlphaTex, uv).r;
#endif //ETC1_EXTERNAL_ALPHA

                return color;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = SampleSpriteTexture(i.uv);
                //画素がぼかしの影響範囲内にあるかの判定および画素の透明度の計算
                fixed alphalx = col.a * lerp(1,_AlphaPower,(_AlphaLX-i.uv.x));
                col.a = saturate(lerp(alphalx,col.a,step(_AlphaLX,i.uv.x)));

                fixed alpharx = col.a * lerp(1,_AlphaPower,(i.uv.x-_AlphaRX));
                col.a = saturate(lerp(col.a,alpharx,step(_AlphaRX,i.uv.x)));

                fixed alphaby = col.a * lerp(1,_AlphaPower,(_AlphaBY-i.uv.y));
                col.a = saturate(lerp(alphaby,col.a,step(_AlphaBY,i.uv.y)));

                fixed alphaty = col.a * lerp(1,_AlphaPower,(i.uv.y-_AlphaTY));
                col.a = saturate(lerp(col.a,alphaty,step(_AlphaTY,i.uv.y)));

                return col;
            }
            ENDCG
        }
    }
}

使い方

1、新しいUnlitシェーダーを作成
2、以上のコードをコピペー
3、対象のgameobjectのマテリアルにシェーダーを適用
4、完成図のgifのようにぼかしの強さと影響範囲を調整すれば終わり

さて、コードの肝心な部分だけを簡単に説明します。

Property

ImageAlpha.shader
Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    _AlphaLX("RangeAlphaLX",Float) = 0
    _AlphaRX("RangeAlphaRX",Float) = 1
    _AlphaTY("RangeAlphaTY",Float) = 1
    _AlphaBY("RangeAlphaBY",Float) = 0
    _AlphaPower("Power",Float) = 0 //ぼかしの強さ
}

スクリーンショット 2021-01-08 15.37.50.png
Inspectorから調整できるパラメーターをPropertiesの中に定義しています。
Powerはぼかしの強さを意味していて、マイナス値で設定するべきです。値が小さいほど透明度が高くなり、-100ぐらいにするとぼかしの範囲内がほぼ完全に透明になります。
RangeAlphaは画像の上下左右から画像のどこまでぼかすかの範囲を決めます。例えば、RangeAlphaLX/BYを0.1に設定すると、画像の左/下側の10%の領域にぼかしをかけます。逆にRXとTYを0.9に設定すれば、画像の右/上側の10%の領域にぼかしをかけます。

透明度の計算

ImageAlpha.shader
fixed4 frag (v2f i) : SV_Target
{
    // sample the texture
    fixed4 col = SampleSpriteTexture(i.uv);
    //画素がぼかしの影響範囲内にあるかの判定および画素の透明度の計算
    fixed alphalx = col.a * lerp(1,_AlphaPower,(_AlphaLX-i.uv.x));
    col.a = saturate(lerp(alphalx,col.a,step(_AlphaLX,i.uv.x)));

    fixed alpharx = col.a * lerp(1,_AlphaPower,(i.uv.x-_AlphaRX));
    col.a = saturate(lerp(col.a,alpharx,step(_AlphaRX,i.uv.x)));

    fixed alphaby = col.a * lerp(1,_AlphaPower,(_AlphaBY-i.uv.y));
    col.a = saturate(lerp(alphaby,col.a,step(_AlphaBY,i.uv.y)));

    fixed alphaty = col.a * lerp(1,_AlphaPower,(i.uv.y-_AlphaTY));
    col.a = saturate(lerp(col.a,alphaty,step(_AlphaTY,i.uv.y)));

    return col;
}

シェーダーを適用したマテリアルの全ての画素に対して、画素を一つずつfrag関数内の処理を経て画素の透明度を計算しています。
それぞれの画素がPropertyで定義したAlphaLX,RX,BY,TYの4つのぼかし領域内にあるかを判定し、もしその領域以内にあれば、col.aでその画素の透明度を計算します。
ちなみに、シェーダー関連の処理はCPUではなく、GPUで計算されているので、if文を書くと処理がかなり遅くなります。条件文を書きたい場合はstep()関数を使うのが一般的と言われています。

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

【Unity】Shaderで縁ぼかし効果を実現

Shaderを使うと、C#のコーディングで実現できなさそうなUIを簡単にできたりします。今回は写真や動画の縁にぼかしを入れるような仕様でシェーダーを実装してみました。

完成図

ぼかし.gif
縁に近いほど透明度が高くなり、漸進的なぼかしを実現しています。ぼかしの強さと全ての方向でぼかしの影響範囲を調整できます。

コード&説明

ImageAlpha.shader
Shader "Unlit/ImageAlpha"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AlphaLX("RangeAlphaLX",Float) = 0
        _AlphaRX("RangeAlphaRX",Float) = 1
        _AlphaTY("RangeAlphaTY",Float) = 1
        _AlphaBY("RangeAlphaBY",Float) = 0
        _AlphaPower("Power",Float) = 0 //ぼかしの強さ
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Back
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _AlphaPower;
            sampler2D _AlphaTex;
            float _AlphaLX;
            float _AlphaRX;
            float _AlphaTY;
            float _AlphaBY;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            fixed4 SampleSpriteTexture (float2 uv)
            {
                fixed4 color = tex2D (_MainTex, uv);

#if ETC1_EXTERNAL_ALPHA
                // get the color from an external texture (usecase: Alpha support for ETC1 on android)
                color.a = tex2D (_AlphaTex, uv).r;
#endif //ETC1_EXTERNAL_ALPHA

                return color;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = SampleSpriteTexture(i.uv);
                //画素がぼかしの影響範囲内にあるかの判定および画素の透明度の計算
                fixed alphalx = col.a * lerp(1,_AlphaPower,(_AlphaLX-i.uv.x));
                col.a = saturate(lerp(alphalx,col.a,step(_AlphaLX,i.uv.x)));

                fixed alpharx = col.a * lerp(1,_AlphaPower,(i.uv.x-_AlphaRX));
                col.a = saturate(lerp(col.a,alpharx,step(_AlphaRX,i.uv.x)));

                fixed alphaby = col.a * lerp(1,_AlphaPower,(_AlphaBY-i.uv.y));
                col.a = saturate(lerp(alphaby,col.a,step(_AlphaBY,i.uv.y)));

                fixed alphaty = col.a * lerp(1,_AlphaPower,(i.uv.y-_AlphaTY));
                col.a = saturate(lerp(col.a,alphaty,step(_AlphaTY,i.uv.y)));

                return col;
            }
            ENDCG
        }
    }
}

使い方

1、新しいUnlitシェーダーを作成
2、以上のコードをコピペー
3、対象のgameobjectのマテリアルにシェーダーを適用
4、完成図のgifのようにぼかしの強さと影響範囲を調整すれば終わり

さて、コードの肝心な部分だけを簡単に説明します。

Property

ImageAlpha.shader
Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    _AlphaLX("RangeAlphaLX",Float) = 0
    _AlphaRX("RangeAlphaRX",Float) = 1
    _AlphaTY("RangeAlphaTY",Float) = 1
    _AlphaBY("RangeAlphaBY",Float) = 0
    _AlphaPower("Power",Float) = 0 //ぼかしの強さ
}

スクリーンショット 2021-01-08 15.37.50.png
Inspectorから調整できるパラメーターをPropertiesの中に定義しています。
Powerはぼかしの強さを意味していて、マイナス値で設定するべきです。値が小さいほど透明度が高くなり、-100ぐらいにするとぼかしの範囲内がほぼ完全に透明になります。
RangeAlphaは画像の上下左右から画像のどこまでぼかすかの範囲を決めます。例えば、RangeAlphaLX/BYを0.1に設定すると、画像の左/下側の10%の領域にぼかしをかけます。逆にRXとTYを0.9に設定すれば、画像の右/上側の10%の領域にぼかしをかけます。

透明度の計算

ImageAlpha.shader
fixed4 frag (v2f i) : SV_Target
{
    // sample the texture
    fixed4 col = SampleSpriteTexture(i.uv);
    //画素がぼかしの影響範囲内にあるかの判定および画素の透明度の計算
    fixed alphalx = col.a * lerp(1,_AlphaPower,(_AlphaLX-i.uv.x));
    col.a = saturate(lerp(alphalx,col.a,step(_AlphaLX,i.uv.x)));

    fixed alpharx = col.a * lerp(1,_AlphaPower,(i.uv.x-_AlphaRX));
    col.a = saturate(lerp(col.a,alpharx,step(_AlphaRX,i.uv.x)));

    fixed alphaby = col.a * lerp(1,_AlphaPower,(_AlphaBY-i.uv.y));
    col.a = saturate(lerp(alphaby,col.a,step(_AlphaBY,i.uv.y)));

    fixed alphaty = col.a * lerp(1,_AlphaPower,(i.uv.y-_AlphaTY));
    col.a = saturate(lerp(col.a,alphaty,step(_AlphaTY,i.uv.y)));

    return col;
}

シェーダーを適用したマテリアルの全ての画素に対して、画素を一つずつfrag関数内の処理を経て画素の透明度を計算しています。
それぞれの画素がPropertyで定義したAlphaLX,RX,BY,TYの4つのぼかし領域内にあるかを判定し、もしその領域以内にあれば、col.aでその画素の透明度を計算します。
ちなみに、シェーダー関連の処理はCPUではなく、GPUで計算されているので、if文を書くと処理がかなり遅くなります。条件文を書きたい場合はstep()関数を使うのが一般的と言われています。

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

【Unity】USSファイルはディレクトリ変更、リネームに弱いので注意

UnityのファイルはGUIDで管理されているためUnity内でファイル移動、またはファイルのリネームをしても参照が外れず安心ですがUI Toolkitは例外です。

USSファイルは文字列指定

Sample.uxml
<ui:UXML xmlns:ui="UnityEngine.UIElements" engine="UnityEngine.UIElements" editor-extension-mode="False">
    <!-- 参照USSを文字列指定 -->
    <Style src="/Assets/_Sample/Uss/Common.uss" />
    <ScreenA name="ScreenA" class="screen">
        <ui:VisualElement name="bg-a" class="screen">
            <ui:Label text="Screen A" display-tooltip-when-elided="True" name="A" class="screen-label" />
        </ui:VisualElement>
    </ScreenA>
</ui:UXML>

上記の例ではSample.uxmlがCommon.ussを参照しているのですが、<Style src="/Assets/_Sample/Uss/Common.uss" />といったように文字列で入力することになります。

UXMLはUnityにインポートする時、内部的にはScriptableObjectに変換されますが、ただのテキストファイルです。
USSへの参照は文字列指定になります。

USSのパスが変更されるとどうなるのか?

単純に参照が外れます。
Unityが気を利かせてUSSの参照を追っかけてパスを修正してくれるわけではありません。

ではどうするか?

「USSのパスが変わらないように事前に設計する」


僕の中では一旦この構成が良いかなと思ってきています。

  • Uss/Common.uss 各UXMLで必ず使用する共通USS
  • Uss/各UXML名/Hoge.uss各UXMLで使用する固有USS

USSファイル、UXMLファイル共に非エンジニアに近いクリエーターが作業することになります。1

その際、ファイルアイコンだけではUXMLとUSSの区別がしづらいため、ディレクトリを分けてしまうのが良いかと思っています。

もしくはScreenA_UXMLCommonUSSのように、UXML/USSを認識できるファイル名にするのも良いかもしれません。

※実戦投入してみないと何がベストプラクティスなのかはわからないため、あくまで想像の話です

まとめ

  • 現状のUnityではUSSのファイル移動、リネームにはコストが掛かります
  • 無駄な作業が発生しないようにUSSディレクトリ構成を予めFIXさせておきましょう

以上です。


  1. もちろんUnityエンジニアも作業対象 

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

【Unity 物理エンジン】スクリプティング(Scripting)とは【超訳 Unity チュートリアル】

概要

この記事は、

Unity Learn の公開している英語のチュートリアルを、日本の学習者のために超訳する」

というシリーズものです。

(今回の記事は他の記事を読んでいなくても問題ありません)


今回は、

Unity 物理エンジンのスクリプティングIntro to the Unity Physics Engine - Unity Learn

についてです。


Unityバージョン:Unity 2019.4.14f1
使用言語:C#

本論

他のゲームオブジェクトと接触する際のコライダーに使われる関数には、以下のものがあります。

OnCollisionEnter (Collision)

上記の関数は、衝突が起きたときに呼び出されます。

例えば、以下のように呼び出されます。

void OnCollisionEnter(Collision collision)
{
    if(collision.gameObject.CompareTag("Enemy")
    {
        //Hit the enemy
    }
}


OnCollisionStay (Collision)

上記の関数は、衝突が起きている間に呼び出され続けます。

OnCollisionExit (Collision)

上記の関数は、衝突が終わったときに呼び出されます。


重力を有効にしたり無効にしたりするには、useGravity関数を使います。

bool rigidBody.useGravity



特定の方向に力を加えるには、.AddForce()関数を使います。

*rigidBody.AddForce(Vector3)



軸の周りに回転力を与えるには、.AddTorque()関数を使います。

rigidBody.AddTorque(Vector3, Force Mode)



注意点:FixedUpdate() で AddForce と AddTorque を呼び出すと、時間の経過とともにこれらの力が加えられます(補足1)。

補足

補足1

「時間の経過とともにこれらの力が加えられる」ということはどういうことかというと、

加える力が一定の場合、ゲームオブジェクトはどんどん加速されていくということです。

FixedUpdate() が呼び出されるたびに AddForce() や AddTorque() も呼び出されるからです(ゲームオブジェクトを連打しているイメージ)。

原文

The functions used for Colliders when interacting with other GameObjects includes:

OnCollisionEnter (Collision)

called when a collision is registered.

For example:

void OnCollisionEnter(Collision collision)
{
    if(collision.gameObject.CompareTag("Enemy")
    {
        //Hit the enemy
    }
}
OnCollisionStay (Collision)

Called during a collision

OnCollisionExit (Collision)

Called when a collision has stopped

In order to enable or disable gravity, you would use the useGravity function.

bool rigidBody.useGravity

In order to add a force in a particular direction, you would use the .AddForce() function.

*rigidBody.AddForce(Vector3)

In order to add a rotational force around an axis, you would use the .AddTorque() function.

rigidBody.AddTorque(Vector3, Force Mode)

Note, calling AddForce and AddTorque in FixedUpdate() will apply this force over time.

参考文献

Unity Learn
Intro to the Unity Physics Engine - Unity Learn

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

[Unity]数秒たったら落ちる床2D,3D対応

落ちる床を作ってみよう

Image from Gyazo

キャラクターは[Medieval King Pack 2]を使っています。
背景は[Free 2D Adventure Beach Pack]を使っています。

落ちる床を準備しよう

2Dの場合は床にする画像を用意してHierarchyに置こう。

projectタブから画像をHierarchyにドラッグ&ドロップ
または、Hierarchyタブのcreate→2Dobject→SpriteからSpriteRendererのspriteから画像を選択

3Dの場合はCube(好きなモデル)をHierarchyに出そう

Hierarchyタブのcreate→3Dobjeckt→Cubeの順に選択

床オブジェクトにスクリプト作ろう

まずvoid Start()の上に必要な変数を作っていこう。

FallFloor.cs
bool floor_touch; //床に触れたかの判定`
public float downSpeed; //落ちるスピード
float fallCount; //床が落ちるまでの時間
Rigidbody2D rb; //Rigidbodyの宣言

次にvoid Start()に初期化処理を書こう。

FallFloor.cs
void Start()
    {
        rb = GetComponent<Rigidbody2D>(); //Rigidbodyの取得
        fallCount = 0; //fullCpuntを初期化
    }

void Update()に更新処理を書こう。

FallFloor.cs
void Update()
    {
        //床に触れたら
        if (floor_touch == true)
        {
            //fallCountを1秒ずつ増やす。
            fallCount += Time.deltaTime;
            //DownStart関数を使う
            DownStart();
        }
    }

DownStart()はまだ作ってないのでエラーのままで大丈夫。

当たり判定のプログラムを作ろう。今回使うのはOnCollionEnter()関数

FallFloor.cs
 //当たり判定 2D場合
    private void OnCollisionEnter2D(Collision2D col)
    {
        //プレイヤータグが付いているオブジェクトに当たったら
        if (col.gameObject.tag == "Player")
        {
            fallCount = 0; //fallCountを初期化
            floor_touch = true; //floor_touchをtrueにする。
        }
    }
FallFloor.cs
 //当たり判定 3D場合
    private void OnCollisionEnter(Collision col)
    {
        //プレイヤータグが付いているオブジェクトに当たったら
        if (col.gameObject.tag == "Player")
        {
            fallCount = 0; //fallCountを初期化
            floor_touch = true; //floor_touchをtrueにする。
        }
    }

2Dと3Dで書き方が違うので注意しよう。

void DownStart()関数を作ろう。自分で定義する関数です。

FallFloor.cs
//数秒後に床が落ちる
    void DownStart()
    {
        //fallCountが何秒かたったら
        if (fallCount >= 3.0f)
        {
            transform.Translate(0, downSpeed, 0); //Y座標をdownSpeedずつ変える。
        }
    }

3.0fの部分はfallCountと比較する用の数字なのでここを変えると落ちる時間が調整できます。
Inspectorで設定したい場合は変数に置き換えよう。

いろいろな設定をしよう

Rigidbodyの設定

Image from Gyazo
ConstraintsのFreezPositionのYのところにチェックを付けよう。そうすると乗っても落ちないようになるよ。

画像は2D用のRigidbodyを使っているけど3Dの時も同じ場所を選択すればOK

落ちる床にRigidbody,Collider(当たり判定),FallFloor(script)がちゃんと付いてるか確認したらゲームを起動してみよう!
最後にFallFloor.csのコードを全文のせるのでエラーが起きたら確認してみよう。

コード全文

FallFloor.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FallFloor : MonoBehaviour
{

    bool floor_touch; //床に触れたかの判定
    public float downSpeed; //落ちるスピード
    float fallCount; //床が落ちるまでの時間
    Rigidbody2D rb; //Rigidbodyの宣言

    // ゲーム開始
    void Start()
    {
        rb = GetComponent<Rigidbody2D>(); //Rigidbodyの取得
        fallCount = 0; //fullCpuntを初期化
    }

    //更新処理
    void Update()
    {
        //床に触れたら
        if (floor_touch == true)
        {
            //fallCountを1秒ずつ増やす。
            fallCount += Time.deltaTime;
            //DownStart関数を使う
            DownStart();
        }

    }

    //当たり判定(3Dの場合は2Dは書かない)
    private void OnCollisionEnter2D(Collision2D col)
    {
        //プレイヤータグが付いているオブジェクトに当たったら
        if (col.gameObject.tag == "Player")
        {
            fallCount = 0; //fallCountを初期化
            floor_touch = true; //floor_touchをtrueにする。
        }
    }

    //数秒後に床が落ちる
    void DownStart()
    {
        //fallCountが何秒かたったら
        if (fallCount >= 3.0f)
        {
            transform.Translate(0, downSpeed, 0); //Y座標をdownSpeedずつ変える。
        }
    }

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

【Unity】アプリ内のSEを楽に管理するManagerの設計を考えてみた

初めに

初投稿です。著者はUnityを触って3ヶ月くらいの素人なのでお手柔らかにお願いいたします。
指摘やアイデア等あればどんどんお待ちしております。

環境

Unity:2019.1.5f1
おそらくバージョン依存の処理はないと思う。

設計の動機

これまではアプリ内でボタンにSEをつけるとき、インスペクタから各ボタンのOnClickに音を鳴らすメソッドをコツコツ取り付けたり、各シーンのScript内でAddListenerする処理を散りばめて書いたりしていた。
しかしそれでは修正のコストが大きくなってしまうので、音声を鳴らす操作と鳴らしたい対象とができるだけ疎になるようなManagerの設計をしてみたいと考えた。

設計の方針

  • Managerはシングルトンにし、アプリのBoot画面などでDontDestroyOnLoadに登録しておく
  • 鳴らしたいSE(複数も可)は全てManagerにアタッチしておく(Resources.Loadで呼び出す方法もある)
  • 鳴らす対象のボタンにはタグのみを割り振り、Managerは画面ロード完了時に画面内にあるボタンのタグに応じて自動で鳴らす音声を割り振っていく

Code

実際にSEManagerにアタッチするCodeはこちら。

SEManager.cs
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class SEManager : MonoBehaviour
{
    //タグ名と各enumの値は一致させておく
    public enum SEType
    {
        audio_001, audio_002, audio_004, audio_005, audio_006
    }
    //ここに使いたい音声を追加
    public AudioSource Choice;
    public AudioSource Decision;
    public AudioSource Switch;
    public AudioSource Correct;
    public AudioSource Wrong;

    //シングルトンにしておく
    public static SEManager sEManagerInstance;

    private static Dictionary<SEType, AudioSource> soundTypeDic = new Dictionary<SEType, AudioSource>();

    private void Awake()
    {
        if (sEManagerInstance == null)
        {
            sEManagerInstance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }

        //ここに使いたい音声を追加
        DicOperate.TryAdd(soundTypeDic, SEType.audio_001, Choice);
        DicOperate.TryAdd(soundTypeDic, SEType.audio_002, Decision);
        DicOperate.TryAdd(soundTypeDic, SEType.audio_004, Switch);
        DicOperate.TryAdd(soundTypeDic, SEType.audio_005, Correct);
        DicOperate.TryAdd(soundTypeDic, SEType.audio_006, Wrong);
    }

    void Start()
    {
        //Sceneが切り替わった時にメソッドが呼ばれるように登録
        SceneManager.sceneLoaded += Initialize;
    }

    //タグ名を取得し、それに応じたSEを鳴らす
    public void playSE(SEType audioType)
    {
        soundTypeDic[audioType].Play();
    }

    //画面起動時にタグのついているボタンにSEを鳴らすメソッドを設定
    public void Initialize(Scene nextScene, LoadSceneMode mode)
    {
        List<GameObject> buttonList = new List<GameObject>();

        //ヒエラルキから、タグを見て鳴らすべきボタンを全て取得する
        foreach (SEType audioType in Enum.GetValues(typeof(SEType)))
        {
            //ボタンをリストに追加
            buttonList.AddRange(GameObject.FindGameObjectsWithTag(audioType.ToString()));
        }

        //各ボタンに音声を鳴らすメソッドを割り当てる
        foreach (GameObject button in buttonList)
        {
            AddSoundEvent(button.gameObject, (SEType)Enum.Parse(typeof(SEType), button.gameObject.tag, true));
        }
    }

    //新たに生成したボタン or 起動時は非アクティブだったボタンに音声イベントを追加
    public void AddSoundEvent(GameObject targetObj, SEType audioType)
    {
        if (targetObj.GetComponent<Button>() != null)
        {
            targetObj.GetComponent<Button>().onClick.AddListener(() => playSE(audioType));
        }
    }
}

//辞書操作用。記事の内容とは関係なし
public static class DicOperate
{
    public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue addValue)
    {
        bool canAdd = !dict.ContainsKey(key);
        if (canAdd)
            dict.Add(key, addValue);
        return canAdd;
    }
}

使い方

SEManagerに使いたい音声をアタッチしてブート画面に置いておく。
ヒエラルキにあるBootManagerの役割はとりあえず画面遷移(SampleSceneへ遷移)のみ。
スクリーンショット 2021-01-08 0.01.46.png

ボタンはタグのみを割り振る。音声を変えたくなった時はタグだけをいじればOKなので、設計としてはかなり疎にできていると思う。
スクリーンショット 2021-01-08 0.08.11.png

あとはBootから起動すれば画面遷移してシーンがロードされた段階で、タグを割り振ったボタンを押せばSEが鳴る。

問題点と今後の課題

1. FindGameObjectsWithTagが非アクティブなオブジェクトを拾えない

画面ロード完了時に非アクティブで、何らかのイベントの後にアクティブになるようなボタンは従来通りScriptからAddListenerしなければならない現状

2. 後からInstantiateで生成されるオブジェクトを拾えない

1同様、ScriptからAddListenerしなければならない現状
オブジェクトの生成を検知するデリゲートはないかと色々と検索した結果、ヒエラルキの変化を検知する方法を見つけたが、ヒエラルキに他の変化がある時でも常に音声を割り振る処理が動いてしまうので使用を控えた。
https://kan-kikuchi.hatenablog.com/entry/EditorApplication_hierarchyWindowChanged

3. タグが音声割り振りのために埋まってしまい、他の用途に使えない

タグは一つのオブジェクトに対し一つしか割り振れないため、こればかりは他の設計との兼ね合いである。
しかし、以下の有料アセット「Multiple-Tags」を使うとエディタ拡張によりオブジェクトに複数のタグを割り当てることができるらしい。
https://assetstore.unity.com/packages/tools/utilities/mulitple-tags-uses-unity-tag-system-95906?aid=1100l37E9&utm_source=aff

4. Toggleに拡張したい

OnValueChangedにAddListenerできるように音声再生のメソッドの引数をうまくとればできるのかな・・・?

最後に

ここまで読んでいただきありがとうございました。
コメントやアドバイス、指摘等お待ちしております。
この記事が何かの役に立てば幸いです。

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