20201023のUnityに関する記事は11件です。

[Unity] Hello Wireworld

UnityでWireworldを作りました

Wireworldとは?

Brian Silverman が 1987年に発表したセル・オートマトンの一種。電子論理回路のシミュレーションができる。ワイヤワールド内で完全なコンピュータを構築することも可能。見てて楽しい。https://en.m.wikipedia.org/wiki/Wireworld

image.png

ワイヤワールドのセルは以下の4種類の状態のいずれかである:
1.空(黒)
2.導体(黄色)
3.電子の頭(青)
4.電子の尾(赤)

空のセルは常に空である。他のセルは以下のように変化する:
Ⅰ電子の頭 ⇒ 電子の尾
Ⅱ電子の尾 ⇒ 導体
Ⅲ導体 ⇒ 近傍セルのうち1または2セルが電子の頭であった場合、電子の頭に変化する。

このような単純な規則だけで論理回路を構築できる。

実装

Textureを準備してください。
texture.png
↑↑↑ご自由にどうぞ

Unity上でPlaneを作成してください。次にShaderのタイプをUnlit/Textureに変更してください。そしたら準備したTextureを適用してください。

最後にPlaneに以下のScriptをアタッチしてください。

WireWorld
using System;
using UnityEngine;

public class WireWorld : MonoBehaviour
{
    [Range(2, 256)] public int size;
    [Range(0f, 1f)] public float speed;
    public int[,] cells, temp_cells;
    public bool Play;
    public int control;

    Renderer render;
    Texture2D drawTexture;
    Color[] buffer;
    Texture2D texture;

    private float now_time;

    public string key = "CELLS";


    void Start()
    {

        cells = new int[size, size];
        temp_cells = new int[size, size];

        render = GetComponent<Renderer>();

        Texture2D mainTexture = (Texture2D)render.material.mainTexture;

        mainTexture.Resize(size, size, TextureFormat.RGB24, false);

        Color[] pixels = mainTexture.GetPixels();

        buffer = new Color[pixels.Length];

        pixels.CopyTo(buffer, 0);

        drawTexture = new Texture2D(mainTexture.width, mainTexture.height, TextureFormat.RGB24, false);
        drawTexture.filterMode = FilterMode.Point;

        now_time = 0;
    }

    private void CellReset()
    {
        for (int y = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++)
            {
                cells[x, y] = 0;
            }
        }
    }

    void Update()
    {
        Array.Copy(cells, temp_cells, cells.Length);

        if (Play && now_time >= speed)
        {
            NextState();
            now_time = 0;
        }
        else now_time += Time.deltaTime;

        SetTexture();

        if (Input.GetMouseButton(0)) CellControl();

        if (Input.GetKeyDown(KeyCode.C)) CellReset();

        if (Input.GetKeyDown(KeyCode.S))
        {
            Debug.Log("セーブ中です");
            Invoke("CellSave", 0.5f);
        }

        if (Input.GetKeyDown(KeyCode.L))
        {
            Debug.Log("ロード中です");
            Invoke("CellLoad", 0.5f);
        }

        if (Input.GetKeyDown(KeyCode.D))
        {
            Debug.Log("データ消去中です");
            Invoke("CellDelete", 0.5f);
        }
    }

    private void CellLoad()
    {

        for (int y = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++)
            {
                cells[x,y] = PlayerPrefs.GetInt(key + x.ToString() + y.ToString(), cells[x, y]);
            }
        }

        Debug.Log("ロード完了");
    }

    private void CellSave()
    {

        for (int y = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++)
            {
                PlayerPrefs.SetInt(key + x.ToString() + y.ToString(), cells[x, y]);
            }
        }

        Debug.Log("セーブ完了!");
    }


    private void CellDelete()
    {
        for (int y = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++)
            {
                PlayerPrefs.DeleteKey(key + x.ToString() + y.ToString());
            }
        }

        Debug.Log("削除完了!");
    }
    private void CellControl()
    {

        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, out hit, 20))
        {
            Vector2 hit_point = hit.point;
            int hit_point_x = Mathf.FloorToInt(-(hit_point.x - 5) / 10 * size);
            int hit_point_y = Mathf.FloorToInt(-(hit_point.y - 5) / 10 * size);

            cells[hit_point_x, hit_point_y] = control;
        }
    }

    private void SetTexture()
    {
        if (cells == temp_cells) return;

        for (int y = 0; y < size; y++)
        {
            for (int x = 0; x < size; x++)
            {
                if (cells[x, y] == 2)
                {
                    buffer.SetValue(Color.blue, y * size + x);
                }
                else if(cells[x, y] == 3)
                {
                    buffer.SetValue(Color.red, y * size + x);
                }
                else if(cells[x, y] == 1)
                {
                    buffer.SetValue(Color.yellow, y * size + x);
                }
                else
                {
                    buffer.SetValue(Color.black, y * size + x);
                }
            }
        }

        drawTexture.SetPixels(buffer);

        drawTexture.Apply();

        render.material.mainTexture = drawTexture;
    }

    private void NextState()
    {
        for (int x = 0; x < size; x++)
        {
            for (int y = 0; y < size; y++)
            {
                int state = temp_cells[x, y];

                if (state == 3) cells[x, y] = 1;
                else if (state == 2) cells[x, y] = 3;
                else if (state == 1)
                {

                    int up = (y == size - 1 ? 0 : y + 1);
                    int down = (y == 0 ? size - 1 : y - 1);
                    int right = (x == size - 1 ? 0 : x + 1);
                    int left = (x == 0 ? size - 1 : x - 1);

                    int neighbors = 0;

                    neighbors += temp_cells[right, up] == 2 ? 1 : 0;
                    neighbors += temp_cells[right, y] == 2 ? 1 : 0;
                    neighbors += temp_cells[right, down] == 2 ? 1 : 0;
                    neighbors += temp_cells[x, up] == 2 ? 1 : 0;
                    neighbors += temp_cells[x, down] == 2 ? 1 : 0;
                    neighbors += temp_cells[left, up] == 2 ? 1 : 0;
                    neighbors += temp_cells[left, y] == 2 ? 1 : 0;
                    neighbors += temp_cells[left, down] == 2 ? 1 : 0;

                    cells[x, y] = (neighbors == 1 || neighbors == 2) ? 2 : 1;
                }

            }
        }
    }
}

クリックした場所に導体などを設置できます。なのでカメラを動かすことができたら便利だと思います。

最後に

Wireworld流行ってほしいなぁ

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

Unityゲームの停止と最初から

起こった事

ゲームのリトライを行った時に全てのオブジェクトが停止した状態になっていた

リトライのコード

SceneManager.LoadScene(0);

また、全てのオブジェクトのstart()などは正常に動作していた。

エラー原因のコード

ゲーム終了時の停止処理で以下のコードを実行していた

Time.timeScale = 0.0f; //時間停止

これが内部的にはstaticな値のようで、ゲームをリトライする前には時間停止を解除する必要があるようだった。

解決策

以下のコードでリトライ処理を行なった

Time.timeScale = 1.0f; //時間起動
SceneManager.LoadScene(0); //シーンの再読み込み
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityメモリ最適化に関して、あう可能性のある問題ーーメモリ編

キーワード

メモリ使用
メモリリーク
アセット冗長

一、メモリ使用

Q1: Unityのメモリ管理メカニズムでは、Reserved TotalとUsed Total間の関係は何ですか?

Reserved TotalとUsed Totalとは、Unityエンジンがメモリ方面の全体的な割り当てと全体的な使用量です。一般的に、エンジンがメモリを割り当てるとき、オペレーティングシステムに「取ったらすぐに使う」ではなく、最初に一定量の連続メモリを取得してから内部で使用します。メモリ残量が不足の時、エンジンはシステムに再び一定量の連続メモリを申し込んで使用します。だから、グラフから見えるのは、Reserved Totalのメモリ占有はUsed Totalより少しだけ大きく、傾向は基本的に同じであることがわかります。
1-1.png
注意:ほとんどのプラットフォームに対しては、Reserved Totalメモリ = Reserved Unityメモリ + GFXメモリ + FMODメモリ + Monoメモリ。「パフォーマンス最適化、歩きは止まらないーーメモリ編」を参照できます。


Q2: UWAの助けを借りて、一つのReserved GFXメモリ占用を追跡しましたが、表示は比較的高かったです。どうすればこのメモリ占用を減らせられますか?

一般的に、Reserved GFXのメモリは、主にテクスチャとメッシュアセットです。テクスチャ形式を検査して、出来る限りにハードウェアがサポートしている圧縮テクスチャを使用してみます。メッシュアセットに対して、頂点の減少または頂点属性から始めることができます。

さらに、テクスチャとメッシュアセット冗長(複数の同じアセット)であるか、リーク(たとえば、メインシティーの大きなテクスチャが戦闘シーンに表示される)であるかを検出することがより重要であり、回避する必要があります。セット冗長やメモリリークに関しては、開発者のみなさんは前の記事「パフォーマンス最適化、歩きは止まらないーーメモリ編2」を参照できます。


Q3: ProfilerにあるNot Savedはどういう意味ですか?
2.png

ProfilerにあるNot Savedとは、プロジェクトにコードによって生成されるさまざまなアセットレコードを指します。上図のように、すべてのMeshは、NGUIプラグインがスクリプトを介して生成したUIインターフェイスMeshアセットであります。


Q4: 図に示すように、EditorでProfilerにあるメモリの詳細情報を確認しますと、Used Totalに「Unity」があることを見つけました。これはどういう意味ですか?なぜこんなに大きいですか?
3.jpg

Editorで実行する時に、「Unityが大きい」は正常な状況です。その原因は、Editorでプロジェクトを実行する場合、エンジンにはアセットが占有するすべてのメモリが含まれ(GFXの一部のテクスチャとメッシュを除く)、同時に多くのサポート操作を行いて各種のゲーム実行データを記録します。一般的に、ゲーム実行中の実際のメモリ消費量を確認する場合は、リリースされたゲームのProfilerから直接観察することをお勧めします。Editorで観察されたメモリは実際より大きくなります。


Q5:ProfilerのManagedHeap.UsedSizeとは何か、このパラメーターの意味は何ですか? 重要ですか?

ManagedHeap.UsedSizeは、プロジェクトロジックコードが実行する時に申し込んだヒープメモリです。このオプションは、コードを最適化することによってのみ減らすことができます。一般的な最適化方法は下記のように。

1、newの数を減らすために、変数を可能な限り再利用します。
2、String接続の代わりにStringBuilderを使用し、foreachの代わりにforを使用します。
3、局部変数または非常駐変数の場合は、可能な限りClassの代わりにStructを使用します。

ManagedHeap.UsedSizeが大きすぎると、一方ではGCの時間に影響する可能性があり、他には、スクリプト内の不合理なGCAllocを反映する可能性もあります。


Q6: System.ExecutableAndDllsは大量のメモリを占有し、増え続けます。これは正常ですか?

System.ExecutableAndDllsアイテムは、実行ファイルとコールされたライブラリ(物理、レンダリング、IO、およびその他のシステムライブラリ)の合計を示します。開発者たちはこのオプションの値に心配する必要はありません。その原因は、多くのアプリケーションがこれらのライブラリを共有しており、実際のプロジェクトに対するメモリの負荷は非常に小さく、ほとんど影響がありません。OSはこのメモリのためにゲームやアプリケーションを強制終了しません。


Q7:下図のように、Profilerにこのようなコールされていないアセットを見られますが、まだメモリに残っていますか?
4.jpg

そうです、Unity Profilerに見られるアセットは全てメモリに残っています。このアセットに対して、シーンを切り替える時にUnloadUnusedAssetsAPIをコールしたら解放できます。


Q8: ゲーム内のMonoのメモリ占用を減らす方法はありますか?ゲームテーブルを読んだ後は60MB、他のゲームの設定テーブルを読んだ後は10MBくらいだったと思いますが、どうしてですか?

ゲームに入りばかり時に、Monoヒープメモリが60MBに達すると、ゲームプロジェクトは最初に非常に大きな配置テーブルをロードする可能性が高いです。これに対して、開発チームに配置ファイルの初期ロードを詳細に検査して、簡単化できるかどうかを確認することをお勧めします。できない場合には、配置ファイルを分解して要求次第でロードし、Monoメモリの上昇が速すぎる回数を減らします。


Q9: Profiler.BeginSampleで統計したデータは、直接にMemoryで見たものと違います。前者は後者より大きいです、これはどうしてですか?ProfilerのAPIで取得した当該フレームのメモリコストは85.6MBですが、ヒープメモリでは62.9MBを示しています。
5.jpg
Profiler中のデータ
6.jpg

この状況は確かに遭う可能性があります。一つのフレームにこんな高いメモリを割り当てするとGC.Collectをトリガーする可能性もあります。Monoに表示する数値はGC後のMonoメモリの値です。


Q10:通常の状況で、ゲームをプレイし続けると、Monoは増え続けますか?たとえば、一つのインターフェイスを頻繁に開く場合、インターフェイスにあるスクリプトは何かを作成し続けますと、Monoは増え続けますか? パフォーマンスに影響しますか?

IL2CPP機能を開くアプリケーション以外、Monoは確かに低下しませんが、常に上昇するわけではありません。

作成されたものは一つの容器にコールしますと、またはあるスクリプトの変数にコールしますと、この部分のヒープメモリを解放することはできません。ただし、容器または変数にコールしていない場合(例えば、一時的に一つのStringを作る)、この部分のヒープメモリはGCの時に解放します。(解放とは空きヒープメモリになることを指し、ヒープメモリの合計量は減少しません)。

後者の場合、頻繁にオブジェクトをnewすることはヒープメモリを増やしませんが、GCコールの頻度が速くなさせるため、可能な限り回避する必要があります。


Q11:下の図に示す関数では、毎回一つのList temp = list();を申し込んで6KBのデータを保存しますが、GC処理をしないと、この6KBは、GC処理をするまでに蓄積続けませんか?コール回数が多い場合、毎回少しずつコールすると、メモリ使用量も増加しますか?
7-1.png

そうです、この6KBのヒープメモリは、Updateの実行によって常にメモリを割り当てし、蓄積されたヒープメモリはGCがトリガーされる時に破棄されます。一般的に、開発チームは頻繁にコール関数でヒープメモリの割り当て操作を出来る限りに回避します。


Q12:メモリを最適化するとき、Unity Profilerが提供するデートとAndroidシステム(adb dumpsys meminfo、memtrackの影響もう考えた)のデータの差が少し大きいです(Profiler自体のメモリ使用量もう分析されています)。この部分のさはどうやって考えられますか?例えば、ビデオメモリコストの正確な統計、OSコストの統計など?

メモリの差が大きいのは正常です。一般的に、Profilerが統計したメモリは比較的一貫していますが、AndroidシステムがADBを介してフィードバックするPSSとPrivate Dirtyなどの値は大きく異なります。これは主にチップとOSの違いによるものです。具体的のAndroidメモリについては、Google Android OSの関連ドキュメントを直接確認することをお勧めします。

Unity Profilerのフィードバックは、エンジンが使用する実際の物理メモリです。一般的に、Profilerを使用して、メモリが冗長であるかリークしているかを確認することをお勧めします。


Q13:モンスターをプリロードした後、モンスターPSSが上昇と表示しています。モンスターを非表示にさせる後も下降しません。これは何が原因ですか? ビデオメモリはアップしていますか?

モンスターを隠すだけでは、メモリは落ちません。非表示にすると、GameObjectの状態が変更されるだけで、メモリ内のObjectとアセットは削除されないためです。同時に、モンスターが事前にロードされている場合でも、Meshなど、一部のアセットは表示されたときにのみGPUに転送されるため、上記の問題が依然として存在する可能性があります。通常の状況では、ビデオメモリはすぐには削減されません。これはGraphics Driverによって管理されます。 Profilerが大きくなるかどうかを確認することをお勧めします。Profilerに問題がなく、PSSが大きくなり続けると、メモリリークが発生する可能性があります。

この問題に関して、「パフォーマンス最適化、歩きは止まらないーーメモリ編2」を参照できます。


Q14:オープニングアニメーションを再生するためのUnity APIであるHandheld.PlayFullScreenMovieに関して、メモリの問題はありますか?たとえば、私のmp4アニメーションは20MBですが、このアニメーションはMonoヒープメモリを増やしますか?

AndroidでのPlayFullScreenMovieの実現は、実際にはAndroidのネイティブインターフェイスを介して直接再生されます。再生中にUnityの更新も停止します。ですから、理論的にこの部分のメモリはUnityに記録されず、Monoにも影響しません。


Q15:テクスチャは常に2倍のメモリを占有しますが、これは私たち自身の問題ですか、それともUnityエンジンのメカニズムですか?

この状況には2つの理由があります。1つは、実機実行中にRead&Writeをオンにしていることです。もう1つの可能性はUnityのbugです。現在のUnity5.2.3release noteは次のとおりです。

(735644) – OpenGL: Fixed texture memory usage reporting in profiler, was twice the actual size for most textures.

開発者は自分の開発バージョンに注意を払う必要があります。5.2.3より前の同様の状況のプロジェクトを参照できます。


Q16:スクリプトがGameObjectを引用している場合、シーンを変更すると、スクリプトとGameObjectの両方が失われます。ヒープメモリは生成されますか?

スクリプトがMonoBehaviourであり、シーンが切り替えられた後にハングしたGame Objectが解放される場合、このスクリプトオブジェクトによって引用されるヒープメモリはGC中に解放されます。ただし、例外が1つあります。ヒープメモリがStatic変数を介して引用されている場合、依然として解放できません。手動で引用を解放すべきです、例えば変数をNullにするや配列クリアなど。

二、メモリリーク&冗長

Q1: テストしたところ、Aという名前のMonoBehaviourには、Bという名前のMonoBehaviourオブジェクトの引用を格納するための配列があることがわかりました。他のロジックがBオブジェクトが配置されているGameObjectをDestroyする後に、Aオブジェクトの配列に遍歴してプリントし、これら(Bの引用)はすべてNullであり、Inspectorパネルで見ればmissingであります。このとき、GCを実行しても、ヒープメモリは実際にはこれらのBオブジェクトを解放していません。Aオブジェクトの配列が空になり、GCをコール場合にのみ、これらのオブジェクトによって占有されていたメモリを解放できます。この現象は正常ですか?値がNullであるのに、まだ引用されており、GCによって解放できないのはなぜですか?

まずに、この現象は正常です。これは、UnityでNull検出に特別処理をすることが導きます。UnityでのMonoBehaviourオブジェクトはManaged Heapに(「シェル」として)存在するだけではなく、Nativeメモリにも対応する「エンティティ」があります。Destroyをコールすると、実際に解放されるのはこの「エンティティ」です。MonoBehaviourオブジェクトがNullであるかどうかを判断するとき、Unityは最初に「エンティティ」が破棄されたかどうかを検出し、破棄された場合はtrueを返しますが、この時点では、マネージドヒープの「シェル」は実際には引き続き引用されます。だから、オブジェクトのNull判断は「true」ですが、実際には引用されており、GCによって解放できない問題があります。

詳細は公式blogで「UnityのNull判断」の説明を参照できます。
http://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/


Q2:複数のアセットの依存パッケージとして、フォントはゲーム内で複数回ロードされます。現在、一つの問題があります。AssetBundle Aアセットはこのフォントに依存し、Aをロードする時にフォントもロードされます。次にBアセットもこのフォントに依存しますが、Bをロードする時にフォントは再びロードしていません。この度、Bアセットでのフォントがなくなることがわかりました。ですから、アセットをロードする時に、Unityはメモリにアセットの依存関係があるかどうかを自動的に識別しますか?もしそうなら、Bがロードされたときにメモリ内のフォントが見つからないのはなぜですか? ここに手動で何かをする必要がありますか?
同時に、依存パッケージアセットがbundle.m_AssetBundle.Unload(false)を行ったら、このパッケージに依存する他のアセットは引用できません。プロセスでは、読み込まれたAssetBundleごとに、ロードした後すぐにUnload(false)を行います。依存パッケージの場合、この手順を実行できますか?

Unityエンジンは、依存関係に基づいて依存アセットを自動的に検索しますが、依存AssetBundleファイルが存在する必要があることに注意してください。つまり、依存関係パッケージを後でまだ使用する場合は、アンロードしないでください。そうしないと、後続のAssetBundleがロードされた後、依存アセットが見つかりません。Unity 5.3より前のバージョンの場合、メモリを考慮して、開発チームはCreateFromFileまたはLoadFromCacheorDownloadを介してAssetBundleをロードできます。AssetBundle間の依存関係を保持しながら、Webstreamも生成させません。


Q3:私たちのゲームを20分間プレイした後、Texture2Dのメモリが60MBを超え、繰り返しアセットもたくさんありますが、完全アンインストールされていないためですか?それとも、パッケージAssetBundleの依存関係の問題ですか?使用していますのはUGUIです。
8.jpg
9.jpg

2つの理由があります:

1、AB依存関係のパッケージ化に問題があります。つまり、atlasは依存パッケージ化をしていません。
2、ロードとアンロードの管理上の問題があります。一度ロードした後に一つのContainerにインデックスされます。次に同じAssetBundleをロードした後に再びインデックスされます。これらが解放していない場合、問題を発生する可能性があります。


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

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

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

WaitForGPUを最適化する方法

今回の主な話題:WaitForGPUを最適化する方法、DrawMeshInstancedで各オブジェクトに異なる色を指定する方法、UnityがモデルをインポートするときにオプションSwap UVsの役割、キャラクターの服替えモデルの合併問題。


レンダリング

Q1: 最近、Unity 5.6.3バージョンを使用して開発したVRデモを作成しましたが、モデルの詳細を近距離で観察すると、突然非常に重くなることがわかりました。フレームレートが95FPSから45FPSに低下しました。

下図には、一般的な距離でモデルを観察すると問題なく、フレームレートが正常であります。
1.png
下図には、近距離でモデルを観察すると、フレームレートが大幅に低下し、VR.WaitForGPUの割合は非常に高くなります。
2.png
これはどうしてですか?

ALUの問題により、GPUの圧力が高くなりすぎると推定します。根本的な原因は、モデルのFragment Shaderの命令セットが高すぎることです。その結果、UWA Day 2018でやったStandard Shader画面占有実験のように、レンダリングされるピクセル数が多いほど、GPUオーバーヘッドが高くなります。ローエンドデバイスでは、画面が20%以上を占め、GPUに20ミリ秒以上かかります。
3-1.png
4-1.png
WaitForGPUが高すぎるため、現時点でGPUがパフォーマンスのボトルネックになっていることを示しています。 これに関して、問題主に自分が遭った状況はこのようなことかどうかを確認することをお勧めします。


制作

Q2: Unity公式に下記の内容が書いてあります。

Swap UV channels in your Meshes. Use this option if your diffuse Texture uses UVs from the lightmap. Unity supports up to eight UV channels but not all 3D modeling applications export more than two.

但し、この説明によくわかりません。自分が試してみると、ライトマップを使用しているかどうかに関係なく、このオプションをオンまたはオフにしてもモデルは変更されませんでした。どのような状況でそれをチェックする必要がありますか?UVチャンネルを交換するとはどういう意味ですか?

前のドキュメントにある説明:Swap UV:Use this if lightmapped objects pick up the wrong UV channels. This will swap your primary and secondary UV channels.
今のドキュメントにある説明:Use this option if your diffuse Texture uses UVs from the lightmap.

そして、こちらにこのオプションについての議論を参照できます:https://forum.unity.com/threads/poll-is-swap-uvs-mesh-import-setting-useful-for-anyone.173468

見るだけではLightmappedモデルのテクスチャが混乱してしまった時にのみ使用します。例えば一部のモデリングソフトウェア(blenderなど)からインポートされた複数のUVチャネルを持つモデルなど。


レンダリング

Q3: DrawMeshInstanced APIを使用して複数のオブジェクトをレンダリングする場合、各オブジェクトに異なる色を指定する方法はなんですか?このインターフェイスのパラメータにはMaterialPropertyBlockがありますが、これは配列ではなく、複数の色を指定することはできません。

このインターフェイスを使用せずにシーンにオブジェクトを配置する場合は、確かにオブジェクトごとにカラープロパティが異なるMaterialPropertyBlock指定することで実現させられます。
ただし、このインターフェイスを使用したら、指定できるMaterialPropertyBlockは1つだけなので、SetFloatArrayなどの配列を準備して、Shader内で配列内の値を使用して対する値を色として取得(または計算)することができます。


制作

Q4:モデルの合併またはキャラの服替えに知っていますか?合併を2回した後、2回目合併後のUVが間違っていますが、何か解決する方法はありませんか?

たとえば、A(aMat)、B(bMat)、C(cMat)、D(dMat)の4つのコンポーネントがあります。
1)CombineMesh(coms、true、false)を初めて使用して、A、B、およびCメッシュ(combineMesh1)を合併し、マテリアルatlasMatと統合します。
2)2回目のCombineMesh(coms、false、false)を使用して、combineMesh1、Dメッシュ(combineMesh2)を合併し、マテリアルを同じRendererに移動します。

最後に、ABCDはすべてcombineMesh2に統合され、Rendererのテクスチャは2(atlasMat + dMat)になります。dMatのUV表示が正常ではありません、これはどうしてですか?

Meshが1つしかない場合、複数のMaterialが問題を引き起こします。
5.png
また、1セットのUVが2セットのテクスチャに対応しているため、表示が異常になります。DメッシュのUVをUV1(UV2、UV3、UV4)に変更できますが、dMat Shaderを変更する必要があり、DrawCallも追加されます。
または、dMatテクスチャと共にatlasMatに合併することもできます。


パーティクルシステム

Q5: Unityバージョン2017.1.3を使っています。特殊効果スタッフがパーティクルシステムのtrailsモジュールを使用し、テールを形成するためにworld spaceを使用しました結果、ゲーム内でパーティクルバウンディングボックス全体的な計算に問題があって、カリング操作中のエラーを起こしました。
6.png
7.png

一つのCullingGroupを使用して解決策を見つけました。CullingGroupの可視性を使用して、パーティクルシステムを調整することです。具体的な方法は次のとおりです。
1)パーティクルシステムの座標位置にCullingGroupを配置し、包囲球の半径を設定します。
2)CullingGroupの可視性およびonStateChangedイベントを使用して、パーティクルシステムの可視性を更新します。


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

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

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

[Unity]Hierarchy順に選択オブジェクトを並び替える

大変処理がダサいので、だれか添削してください。

コード

GetOrder.cs
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;

public class GetOrder: Editor
{

    [MenuItem("Tools/GetOrder")]
    static void Create()
    {
        if(Selection.activeGameObject == null)
            return;

        var objlist = Selection.gameObjects;
        int numlen = 0; //ヒエラルキー順IDの最大桁数
        List<string> orderstr = new List<string>();
        List<int> orderint = new List<int>();
        Dictionary<int, GameObject> intdct = new Dictionary<int, GameObject>();

        for(var i = 0; i < objlist.Length; ++i){
            var numstr = GetHierarchyOrder(objlist[i].transform);
            orderstr.Add(numstr);
            if(numstr.Length > numlen){
                numlen = numstr.Length;
            }
        }

        for (var i = 0; i < orderstr.Count; ++i){
            var temp = orderstr[i];
            if(orderstr[i].Length < numlen){
                for (var j = 0; j < (numlen - orderstr[i].Length); ++j){
                    temp += "0";
                }
            }
            int num_var;
            var result = Int32.TryParse(temp, out num_var);
            //orderが何らかの理由でParse出来なかったら追加されません
            if(result)
                orderint.Add(num_var);
                intdct.Add(num_var,objlist[i]);
        }

        orderint.Sort();

        foreach(var oint in orderint){
            Debug.Log(intdct[oint]);
        }
    }

    static string GetHierarchyOrder(Transform trans)
    {
        var translist = trans.GetComponentsInParent<Transform>();
        string numstr= "";

        for (var i = 0; i < translist.Length; ++i)
        {
            var p = translist[translist.Length - i - 1];
            //+1しているのは0を出さないため ケタ数を揃えるとき0を追加するのでここで0が来ないように
            var temp = p.GetSiblingIndex() + 1;
            numstr += temp.ToString();
        }

        return numstr;
    }
}

解説

GetSiblingIndex()だと、兄弟間での順番しか返ってこないので全体の比較ができない。
なんで、一旦Stringにしてネストされた順に変数に追記していく。つまりネストが深くなるだけ桁数が増える。(GetHierarchyOrderの部分)
ネストの状態によっては桁数が違うので、あとで桁数を揃える必要があるが、揃えるために0を追加すると被るオブジェクトが出てきてしまう。
なんで、GetSiblingIndex()した時点で+1しておいて被らないようにする。

あとは、取得した番号のStringをSelection.gameObjectsで取得したゲームオブジェクトリストの順にリストに格納して、桁数を揃えて、Intにパースして、Intをキーにしてオブジェクトをディクショナリに突っ込んで、Intをソートして、順番に取り出すだけ。

もっといい感じに書けそうな気もする・・・。

参考

https://qiita.com/naoK/items/22b58c5b14a8406f538b
参考にさせて頂きました。

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

Unityでの開発において、マウス入力でオブジェクトを回転させる

マウスで3Dオブジェクトを回転させることは、3Dオブジェクトビューアやゲームなどの3Dソフトでよくある機能です。例えば、ゲームを進めるためには、プレイヤーがオブジェクトを詳細に調査する必要があるかもしれません。このような機能は、社内で開発しているUnityプロジェクトで必要とされていました。オンラインで検索すると、いくつかのチュートリアルや記事を見つけましたが、それらはすべて一定のカメラビューを前提としていました。利用者がオブジェクトを回転する時に、オブジェクトに対して相対的に位置やビューを移動したかどうかを考慮していませんでした。そのため、カメラビューが正確に期待した通りではない場合には、正しく回転しませんでした。

その次の問題は、プロジェクトがWebGLと互換性があり、ウェブブラウザ内で実行されなければならないことでした。ブラウザではマウスカーソルの制限があるため、カスタムのマウス入力ハンドラを作成しなければなりませんでした。これについては、今後の記事で取り上げます。

今回紹介したいのは、Unityでマウスを使ってオブジェクトを回転させる方法と、回転しているオブジェクトを見る角度を考慮した方法です。この方法は、一人称視点のコントローラーや、3人称視点のコントローラーにも使えます。

Untitled.png

回転可能なオブジェクトを作成

まず、オブジェクトを回転させるためのスクリプトを作成します。FirstPersonControllerスクリプトはプレイオブジェクトに付いているコンポーネントで、一人称視点で動き回れるようにしています。この文脈で注意すべき唯一のことは、"IsLookingWithMouse "というブール変数です。オブジェクトを回転させているときはfalseに設定され、回転が終わったら再びtrueに設定されます。これでオブジェクトの回転中時に、カメラの動きを簡単に無効にできます。MonoBehaviourメソッド OnMouseDown() と OnMouseUp() で設定します。

最も重要なメソッドは OnCursorDrag() で、実際の回転を処理します。オブジェクトをクリックしたままに、ドラッグするフレームごとに呼び出されます。まず、"Mouse X" と "Mouse Y" の入力をそれぞれ取得して、オブジェクトの回転に適用する X と Y の回転を計算します。入力値は両方とも、自由に設定できる速度値とラジアン変換のための Mathf.Deg2Rad を掛け合わされます。 そして、オブジェクトに X と Y の回転を適用するために、transform.Rotate() メソッドを個別に呼び出します。カメラの位置を基準にすると、X の軸は上、Y の軸は右になります。相対空間を「ワールド」とします。

RotatableObject.cs
using UnityEngine;

public class RotatableObject : MonoBehaviour
{
   [SerializeField] private float userRotationSpeed = 100f;
   [SerializeField] private FirstPersonController characterController;

   private void Awake()
   {
       characterController = FindObjectOfType<FirstPersonController>();
   }

   public void OnMouseDown()
   {
       if (characterController == null)    return;
       characterController.IsLookingWithMouse = false;
   }

   public void OnMouseUp()
   {
       if (characterController == null)    return;
       characterController.IsLookingWithMouse = true;
   }

   public void OnMouseDrag()
   {
       if (characterController == null || Camera.main == null)   return;

       float rotX = Input.GetAxis("Mouse X") * userRotationSpeed * Mathf.Deg2Rad;
       float rotY = Input.GetAxis("Mouse Y") * userRotationSpeed * Mathf.Deg2Rad;

       transform.Rotate(Camera.main.transform.up, -rotX, Space.World);
       transform.Rotate(Camera.main.transform.right, rotY, Space.World);
   }
}

あとは Unity シーンにボックスコライダーと RotatableObject スクリプトが付いているオブジェクトを作成するだけです。内蔵のキューブで十分です。これで、何らかの移動コントローラスクリプトがあれば、シーンの周りを移動したり、好きな角度でキューブを回転させることができます。また、画面の中央にシンプルなターゲットアイコンを作成すれば選択しやすくなります。それは、階層ビューで右クリックしてUIイメージを作成することで簡単にできます。

Capture.JPG

以上です。後日、この機能をWebGLで動作するように拡張する方法を説明します。

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

ローグライクRPG開発日記_0

かねてより制作してみたかったローグライクRPGに着手しようと思い、今、こうして書かせてもらっています。
自分の備忘録のように書き込んでいますので、多少読みづらいのはご了承ください。
とりあえず、色々メモしておきます。

<メイン>
・「風来のシレン」のような2D見下ろし型
・ただし、レベルは1には戻らない(なぜこのようにするのかは後述、一応Lv1ダンジョンも別途作りたい)
・主人公候補は3人、その中から一人を選んでゲームスタート(周回要素あり)
・主人公それぞれにストーリーが有り、ダンジョンクリアによって進行
・主人公たちの拠点となる町には様々な施設があり、施設ごとに恩恵がある
(素材やお金を寄付することで施設レベルが上がり、より良いサービスを受けることができる)
[施設例]
○商店…アイテム売買
○酒場…依頼受領、情報収集
○鍛冶屋…武器の強化、売買
○倉庫…アイテムの保管
○道場…スキルの習得

・ダンジョンは他の冒険者も探索しており、誰かがダンジョンを踏破すると、そのダンジョンの情報が酒場で売られるようになる。
・他の冒険者が最終ダンジョンを踏破してしまうとゲームオーバー(実質的な時間制限)
 LVや倉庫にあるアイテムを保持したまま周回する事もできる

<要素>
・アイテム、敵の図鑑機能(アイテムは入手、敵は倒すことで図鑑に登録される)
(項目ごとに主人公たちのコメントを入れれたらなあ)
・依頼(アイテム納品、敵の討伐など。クリアすると報酬がもらえる)
・モンスターハウス
・施設レベルアップの素材(敵、又は床から入手可)

実装したいと思っているのはこの辺りです。施設育成に重きを置きたいので、いかにリソースを効率よく集め、施設の人たちに貢ぎ物をし、他の冒険者を出し抜いてダンジョン最深部にある宝を入手できるか、をコンセプトとして制作しようと思っています。

<懸念事項>
・Unityの知識不足(まだまだわからないことが多い)
・広告、販売の仕方
・モチベーション
・制作期間の不明瞭さ(何ヶ月かかるかわからない)
・イラスト

中々考えることが多いです。特に、イラストに関しては全くのド素人で、頭の中で想像はできてもアウトプットが出来ないもどかしさがあります。なんとか描けるように努力するか(正直どれくらいかかるかわからない…)、外注で探すか(相場がどんなものか不安)、どちらにしようか悩んでいる状態です。

こんな感じでなんとも見切り発車的なスタートですが、見守っていただけると幸いです。

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

[Unity][自分用メモ]2D Pixel Perfect, Light 2D 初期設定

[2D Pixel Perfect]
1. Main Camera にPixel Perfectのコンポーネントを追加
2. Pixels per Unit → 32 (解像度による)
3. Reference Resolution X 480 Y 270

[2DLight]
https://kan-kikuchi.hatenablog.com/entry/Light2D

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

UnityのARFoundationのARPlaneManagerを使ってみた

「AR Plane Manager」試してみたりcube置いてみたりした
ノーコーディングでできるけど、色んなところで詰まったのでメモ

参考にしたページ
AR tutrials

実行結果
Screenshot_20201022-163238 (2).png
テーブルの平面と床の平面を検出しているような
上側にある灰色は適当に置いたcube

環境
・Unity2019.4.12f1(LST)
・Unity Hub 2.4.1
・Windows10
・Pixel3a(Androidバージョン:10)

手順
①ひたすら設定
OtameshiAR - MainScene - Android - Unity 2019.4.12f1 Personal _DX11_ 2020_10_22 18_08_05.png

OtameshiAR - MainScene - Android - Unity 2019.4.12f1 Personal _DX11_ 2020_10_22 17_54_37.png

OtameshiAR - MainScene - Android - Unity 2019.4.12f1 Personal _DX11_ 2020_10_22 17_58_51.png

OtameshiAR - MainScene - Android - Unity 2019.4.12f1 Personal _DX11_ 2020_10_22 18_33_14.png

②Hierarchyに3つ追加
OtameshiAR - MainScene - Android - Unity 2019.4.12f1 Personal _DX11_ 2020_10_23 10_33_55.png
 ・「AR Session Orign」のInspectorに「AR Plane Manager」追加
 ・「AR Plane Manager」のPlane PrefabにPrefab化した「AR Defalt Plane」追加
 「AR Defalt Plane」はHierarchyのcreate->XR->AR Defalt Planeにある。Prefab化は、HierarchyからProjetへドラックすればできる。Prefabsフォルダをつくって中に格納するのがいいらしい。

③ビルド
「Build Settings」で「Build」ボタン押してもエラーがたくさん出てしまう->エラーメッセージを読むand以下のぺージを参考にした
  https://teratail.com/questions/187693
  ↓以下解決方法
・「Build Settings」でExportしてAndroidStudioでビルドする
(AndroidStudioでもエラーが出た場合はSDKのバージョンが合ってないっぽい。エラーを読んだ感じ28.0.3が必要らしい(このときP10Liteを使っていた))
・AndroidStudioのSDK Maneger(?で検索すれば開ける)でInstall
ExportpProject Android Studio 2020_10_23 10_55_55.png
エラーに書いてあったURL先で、「license」フォルダをProjectのホームディレクトリに置けば必要なものを自動でInstallしてくれるよと書いてあるので(ホームディレクトリってどこなん?と思いつつも)
・「OtameshiAR」Projectのフォルダ内に「licence」フォルダをコピペ
「license」フォルダは上画像の「Android SDK Location」に書かれているところにあるはず(自分の場合は始めはなかったが、SDKをinstallしたら出来てました)
・AndroidStudioで再ビルド
 (自分の場合はこれでやっとビルドできた)

④実行
・Android端末(ARCoreに対応しているやつ 自分はPixel3a)をPCにつなぐ
 Android端末をデバックモードにする、ファイル転送モードにする、USBデバックをONにするを忘れずに
・UnityのBuild Settingsで「Build and run」で実行
 Android端末で実行されるまで気長に待つ

実行時はまったこと
・「Build and Run」で「No Android device」といわれる->ケーブルがデータ転送できないタイプだった
・「build and run」で実行できても黄土色の画面のままだった->ARCoreに対応していないAndroid端末(P10Lite)を使っていた

学んだページ(udemy)
ユニティちゃんが教える!初心者向けUnity講座

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

Unity HDRPで透過スクショを撮る

こんにちはっ?八ツ橋まろんです

今回はUnityのHDRPで透過のスクリーンショットを撮る方法とスクリプトを紹介していきます。HDRPでより綺麗な立ち絵を撮りたいVtuberなどに有効です(超限定的)
※以前『UnityのGame画面をスクショするスクリプト』を紹介しましたが、Unityのバージョンによっては再生中でないと使えないことがあったので、今回は再生中でなくとも使えるように改良しています。よってスクリプトは上記記事の上位互換です。(スクリプトはHDRPでなくとも使えます)

HDRPって何?って方はこの単語で検索してみてください。たくさんの方が記事を書いてくださっています。乱暴に説明すると、
『高品質なライティングができる、より美しいUnity』
です。

ただし、
『PCの負荷が増えます、つよつよPCを使いましょう』
『従来のshaderはほとんどが使えません。HDRP専用のshaderが用意されているので、それを使う必要があります』

HDRPはUnityHubでプロジェクトを作成する際にここ↓↓↓↓を選択することで使用可能です
image.png

HDRP環境の基本的な構築はこの記事では解説しませんので、基礎的な部分は他の記事を参照してください。

環境

Windows10
Unity 2019.4.4f
HDRP 7.3.1

必要な設定

・HDRPで透過背景を扱うには、HDRenderPipelineAssetのColor Buffer Formatを R16G16B16A16にする必要があります(デフォルトではR11G11B10となっていてA成分が無い)
image.png

・Volumeの設定のうち、BloomとVignetteを使っている場合はオフにしておきましょう(どちらも画面全体に適用されるエフェクトなので、透過画像全体に効果が残るため)

・Volumeの設定のうち、Fogを使っている場合はオフにしておきましょう(透過せず背景として映り込んでしまうため)

・Cameraの設定のうち、Background TypeはColorにしておきましょう(Skyだと透過せず背景として映り込んでしまうため)

撮影方法

・以下のスクリプトを空のGameObjectにアタッチし、コンポーネントの右上のアイコンを開いてScreenShotを選択するとGame画面に映っている画面が透過pngとしてResourcesフォルダに保存されます。(Game画面が見えない状態では保存されません)

image.png

ScreenShot.cs
using System;
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class ScreenShot : MonoBehaviour
{
    public bool Alpha = true;
    public string Folder = "Assets/Resources";

    IEnumerator CaptureWithAlpha()
    {
        yield return new WaitForEndOfFrame();

        var rtex = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Default);
        //var tex = ScreenCapture.CaptureScreenshotAsTexture();
        ScreenCapture.CaptureScreenshotIntoRenderTexture(rtex);

        var width = rtex.width;
        var height = rtex.height;
        var texNoAlpha = new Texture2D(width, height, TextureFormat.RGB24, false);
        var texAlpha = new Texture2D(width, height, TextureFormat.ARGB32, false);

        if (Alpha)
        {
            // Read screen contents into the texture
            texAlpha.ReadPixels(new Rect(0, 0, width, height), 0, 0);
            texAlpha.Apply();
        }
        else
        {
            // Read screen contents into the texture
            texNoAlpha.ReadPixels(new Rect(0, 0, width, height), 0, 0);
            texNoAlpha.Apply();
        }

#if UNITY_EDITOR
        SafeCreateDirectory(Folder);
#endif
        // Encode texture into PNG
        var bytes = texAlpha.EncodeToPNG();
        if (!Alpha)
        {
             bytes = texNoAlpha.EncodeToPNG();
        }
        DestroyImmediate(rtex);
        File.WriteAllBytes(Folder + "/" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".png", bytes);
        AssetDatabase.Refresh();
    }
    public static DirectoryInfo SafeCreateDirectory(string path)
    {
        return Directory.Exists(path) ? null : Directory.CreateDirectory(path);
    }
    [ContextMenu("Screenshot")]
    public void Screenshot()
    {
        StartCoroutine(CaptureWithAlpha());
    }
}

以下、補足です。
・InspectorのAlphaのチェックを外すと透過なしのpngになります

・pngの解像度はGame画面の画面設定と同じになります

・Resourcesフォルダが無い場合は自動で作成されます

・保存フォルダを変えたい場合はInspectorのFolderを適当に変えてください

以上になります。快適なHDRPライフを~?
999999.png

八ツ橋まろん

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

VRMのアバターをQuest2用にVRChatへアップロードする方法

はじめに

Cluster用に作成したリアルアバターのガチ本をQuest2用にVRChatへアップロードする方法をまとめました。VRM形式のアバターなのですが、Quest2用に5000ポリゴン以下にする必要があり、Blenderでのポリゴン削減やリグの修正が面倒だったので、Unityだけで完結したく、お金(Mesh Simplifyを用いて)で解決しました。

システム環境

  • Windows 10
  • Oculus Quest2
  • Unity 2018.4.20f1
  • VRCSDK2
  • VRM Converter for VRChat

導入

1.Unityプロジェクトを作成します。

2.VRCSDK2をインポートします。

3.【¥0】 VRM Converter for VRChatのVRM Converter for VRChat-24.0.2 + UniVRM-0.61.1.unitypackageをインポートします。

4.VRMアバターをProjectビューへD&Dします。ガチ本のリアルアバターを使用しています。

5.VRMアバターのプレハブをHierarchyビューへD&Dします。
image.png

6.VRM->Duplicate and Convert for VRChatをクリックし、VRMアバターをVRChat用に変換します。
image.png

7.複製して変換をクリックします。
image.png

8.VRChat SDKのControl Panelを開き、VRChatにサインインします。
image.png

9.Builderを開きます。Windows用だと問題なくそのままアップロードできます。ポリゴン数は3万ポリゴン程度です。Quest2用にアップロードするため、Switch Build Target to Androidをクリックします。
image.png

10.ポリゴン数が多すぎる警告が出ています。
image.png

11.Quest2用にポリゴン数を5000ポリゴン以下に削減します。Mesh Simplifyをインポートします。
image.png

12.BodyにMesh SimplifyをAdd Componentし、5000ポリゴン以下になるようにVertex %を設定し、Enable Prefab Usageにチェックを入れ、Compute meshをクリックします。
image.png

13.なんじゃこりゃああ!!!顔だけは削減しないように工夫する必要がありますね… 取り急ぎこれでww やっぱりBlenderか…
image.png

14.アップロードしようとするとMesh Simplifyがあることでエラーが出てしまいます。
image.png

15.Mesh Simplifyを用いたプレハブは一旦削除して、ポリゴン削減する前のプレハブをHierarchyビューに持ってきて、Body->Skinned Mesh Renderer->Meshを生成されたmesh_Body-xxxxに置き換えます。あとBodyについてるAnimatorをRemove Componentします。これでAnimator Count:2の警告がなくなり、Overall Performance:Excellentになります。
image.png

16.VRC Avatar DescriptorのView Positionを設定します。
image.png

17.VRChat SDKのControl Panelを開いて、アップロードしましょう。
image.png

image.png

18.Quest2でVRChatを起動し、アバターを確認します。
122502595_675025096773945_6506687710201066345_n.jpg

できました!

追記

19.顔だけポリゴンを残すために、Vertex Relevance Modifiers->Add New Sphereをクリックし、青透明の球体が顔に入るようにSphere情報を変更します。Relevanceを1.0にして、Compute Meshをクリックしましょう。すると、顔だけ保持されていい感じになりました!
image.png

20.逆はこちらw
image.png

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