20190710のUnityに関する記事は6件です。

Shader入門メモ

Shader基礎

・UnityにおけるGPU制御処理をシェーダープログラミングと呼んでいます。

・シェーダープログラミングを習得するためには、「行列とベクトルの数学知識」「3DCGの基礎知識」「レンダリングパイプラインの仕組み」など広範囲を学ぶ必要がある。

・レンダリングパイプラインとは、GPU内で頂点情報が色情報に変わるまでの工程をいいます。レンダリングパイプラインは複数のシェーダーステージから構成されている。

・レンダリングパスとは、実際にどのタイミングでどんな演算を行うかのソフトウェア上の機能の流れを示すものがある。

・プリミティブとは、複数の頂点から構成されるポリゴンの最小単位のこと。ゲームでは通常。3つの頂点から構成される三角形のプリミティブを使用している。
プリミティブは厳密に表と裏が決まっていて、カメラに対して裏向きになっているプリミティブは画面に表示しない。

・uv座標とは、あるモデルのプリミティブが、そのモデルに貼り付けるテクスチャのどの部分に相当するのかは、そのモデルのデータ内にマッピング情報として同梱されている。これを「uv情報」「uvテクスチャマップ」 と呼びます。

・uvというのは、このテクスチャへのマッピングをuv座標系で指定することに由来します。uvテクスチャマップはモデルごとに複数持つことが可能で、Unityでは一つのモデルにつき4個まで保持できる。


ライティング

・Foward Renderingはレンダリングパスの種類の一つ。
全てのオブジェクトについて、シーン内にあるライト一個ごとに一回レンダリングします。例えば、シーン内にライトが4つあれば同じプリミティブが4回レンダリングされる。

・Pass
上記の1回分のレンダリング処理のことを呼びます。ユーザーは個々のPassをシェーダーコードをプログラミングできます。
Passが実行されるたびにフレームバッファに値が書き込まれます。次のPassでは前のPassの結果を踏まえて値を更新していき、それによって全てのライトの効果が反映される。

・あるオブジェクトを複数のライトが照らしている時、それらのライトはオブジェクトに対して照度の高い順に3種類に分類されます。

種類 その種類に分類されるライトの個数
ピクセル単位ライト 環境設定のPixel Light Countで指定された数(最大)
頂点単位ライト 4つ(最大)
SH(球面調和)ライト 上記以外全て

例えば、オブジェクトに影響しているライトが9個あり、それらのライトをオブジェクトへの照度が強い順にA、B、C、D、E、F、G、Hとした場合、それぞれのライトは以下のように分類されます。
・ピクセル単位ライト:A B C D
・頂点単位ライト:D E F G
・SH(球面調和)ライト:G H
この重複は近似値の使用によって見た目が変らないように、ライト間の値をなじませる為の処置になります。

・ピクセル単位ライト
ライトに応じたPassが実行されます。Passには「Foward Base Pass」と「Foward Add Pass」の2種類があり、オブジェクトにもっとも強い効果を発しているピクセル単位ライト1個に対して「Foward Base Pass」が実行され、それ以外のピクセル単位ライトに対しては「Foward Add Pass」がライト1個につき実行されます。

・頂点単位ライト
これに分類されたライト情報は、「Foward Base Pass」実行時に取得できます。頂点単位ライトは最大4個まで設定され、その個々の情報を取得できます。

・SH(球面調和)ライト
頂点単位ライトよりも効果が小さいライトは、それら全てを1つに合成して、球面調和関数という特殊な式の係数に変換します。これをSHライトと呼びます。
SHライトの情報はFoward Base Pass実行時に、取得できます。複数のライトの情報をひとまとめにしている為に精度は落ちますが、非常に高速に動作する。

Shaderの構文

Shader "Unlit/NewUnlitShader"
{
    Properties
    {

    }
    SubShader
    {

        }
        
        FallBack "ShaderName"
        
        CustomEditor "EditorName"
}

・Properties宣言
Inspectorビューに表示する項目


Cg/HLSLについて

構文仕様

シェーダーコードでは、関数の仮引数や戻り値を介してアプリケーションやGPUと値をやりとりします。シェーダーからGPUに値を返す時、通常は戻り値を使うが、仮引数を介してGPUに値を返す事もできる。
仮引数名にin/out/inout演算子を付与することで、どの引数が入出力のいずれかで使われるかを明示できる。

演算子 説明
in 引数を入力として扱う(デフォルト。)
out 引数を出力として扱う
inout 引数を入出力として扱う(GPUから受け取る値と返す値が同じ要素(セマンティクス)を指す場合に使用できる)
float4 frag(in float a, out float b, inout float c) {

}

データ型

テクスチャサンプル型
シェーダープログラミングでテクスチャのデータを参照する場合、テクスチャサンプル型という特殊な型を使用します。
テクスチャサンプル型は、シェーダーコードの中では生成できず、プロパティを通じてのみ受け取ることができる。

ベクトル型と行列型

シェーダープログラミングの中で行われる演算のほとんどは「ある座標Aに、変換行列Bを左辺から乗算して、座標Cに変換する」というものになる。このような演算を「座標変換」と呼ぶ。

上記の説明の中に登場する「ある座標A」は4要素からなるベクトル。
「変換行列」は4行4列の行列表すのが一般的で、GPUはこの2つの要素を乗算するのにハードウェア設計を最適化しています。そのため可能な限り、GPU上で行う演算は「4行4列の変換行列 × 4要素のベクトル」という形で記述するのがいい。

行列型

複数のベクトル型をひとつにまとめた物が行列型です。シェーダープログラミングでは、主に座標を変換するための変換行列を格納するために使用します。
行列型は、スカラー型(float、halfなど)の名前の後に"A×B"という形式の識別子を付与することで表します。
Aは行数、Bは列の数を指し、それぞれ1から4まで任意に指定できます。

float1×4 test1;  // 1行4列のfloatからなる行列型 
float4×2 test2;  // 4行2列のfloatからなる行列型

座標変換に必要な行列型はfloat4×4が主です。

行列スウィズル型演算子

行列の各要素へのサクセスには行列スウィズル演算子を使用する。
行列スウィズル演算子は、".(ドット)"の後に"_mAB"という文字列を設定する。Aは行数、Bは列数を示し、それぞれ1から4の数字を設定します。

float4の行列変数から3行2列の要素を取得したい場合以下のように記述します。

float myFloatScalar = myMatrix._m32;

行列スウィズル演算子は連結して記述できる。以下では、myMatrixから4つの要素を取得し、float4に格納しています。

float4 myFloatVec4 = myMatrix._m00_m11_m22_m33;

プロパティから変数へのマッピング

ShaderLab言語のPropertiesブロックで宣言したプロパティを経由して、アプリケーションからシェーダーコードに値を渡す場合は、プロパティと対応する変数をCg/HLSL内で宣言する必要がある。

以下のようにPropertiesブロックで宣言した場合、

_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
_Snow("Snow", Range(0, 2)) = 0.0

シェーダーコードで以下のように変数を宣言すると、同名のプロパティに設定された値が、各変数に自動的にマップ(対応付け)され、参照できるようになる。他のメソッド内とかで使用できる。

fixed4 _Color;
sampler2D _MainTex;
half _Glossiness;
half _Metallic;
half _Snow;

セマンティクス

各シェーダーステージは、GPUから複数の値を受け取り、それを演算して結果として出力される複数の値を再びGPUに返します。例えば頂点シェーダーでは、GPUから頂点のモデル空間座標を受け取り、それをクリップ空間座標に変換してGPUに返しています。

GPUとシェーダーがやりとりする値には幾つもの種類があり、シェーダーコードによって必要とする物が変わってきます。GPUとシェーダーコードが効率的にやりとりするために、Cg/HLSLでは「セマンティクス」という仕組みが用意さえている。

セマンティクスは、Cg/HLSL内の変数宣言に定義済みの名前を付記することで、その変数がGPUから値を入力される、あるいは、GPUへ値を出力する役割を持つことを明示します。
このように変数に役割を結びつける(バインディングする)仕組みを「バインディングセマンティクス」と言います。

バインディングセマンティクスは「関数の仮引数」、「関数の戻り値」、「構造体の変数」に対して行える。
セマンティクスには「入力セマンティクス」、「出力セマンティクス」の2種類があり、あるセマンティクスが入力/出力のどちらでバインディングされるかは、そのセマンティクスに結び付けられた変数がin/out/inoutのいずれに相当するかに依存する。

頂点シェーダー

全てのポリゴンの頂点ごとに、#pragma vertxtで定義された頂点シェーダー関数が呼び出される。
頂点シェーダー関数の主な役割は、入力されたモデル空間座標をワールド空間座標 -> ビュー空間座標 -> クリップ空間座標に変換して出力することにあります。この座標変換処理は行列とベクトルの乗算を複数回行うことで実現している。

頂点シェーダー関数では、頂点座標の他に、テクスチャのUV座標や、頂点を照らしている光源を計算した結果のライティング情報などをレンダリングパイプラインに出力できます。

ある変数にバインディングされたセマンティクスが入力と出力のどちらの扱いになるかは、その変数が頂点シェーダー関数の入力に使われるか、出力に使われるかで決まります。

mul(UNITY_MATRIX_MVP, v.vertex) // 第二引数の頂点座標を第一引数の指定によりモデル空間座標からクリップ空間座標に変換する。

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

[ViveProEye] SRanipal SDKで取得可能な眼球運動情報

はじめに

ViveProEyeとUnityを使って眼球運動を計測する際にSRanipal SDKを使用します
同封されているドキュメントを読みながらまとめました
不明な点については今後追記していきます

環境

SRanipal SDK 1.0.1.0
SR_Runtime 1.0.3.0

眼球運動の取得

ViveSR.Error SRanipal_Eye.GetEyeData(ref EyeData eyeData)

眼球運動の取得ができているかが返ってくる
ViveSR.Error.WORK:取得できている
その他:取得できていない
(DEVICE_NOT_FOUNDやTIMEOUTなど詳細な情報が返ってくるように設計されている)

取得可能な情報

EyeData : 眼球情報

  • int frame_sequence : フレームシーケンス
  • bool no_user : ?
  • int timestamp : フレームがキャプチャしていた時間(ミリ秒)
  • VerboseData verbose_data : 詳細情報

VerboseData : 詳細情報

  • CombinedEyeData combined : 両目?
  • SingleEyeData left : 左目
  • SingleEyeData right : 右目
  • TrackingImprovements tracking_improvements

CombinedEyeData : 両眼情報?

  • float convergence_distance_mm : 収束距離(mm)
  • bool convergence_distance_validity : ?
  • SingleEyeData eye_data

SingleEyeData : 単眼情報

  • ulong eye_data_validata_bit_mask : 計測データが有効であるか
  • float eye_openness [0-1] : 目の開閉具合(0:閉じている, 1:開いている)
  • Vector3 gaze_direction_normalized : 正規化された視線ベクトル(右手座標系)
  • Vector3 gaze_origin_mm : 注視点の原点となる眼の中の位置(メートルマイル)(右手座標系)
  • float pupil_diameter_mm : 瞳孔径(メートルマイル)
  • Vector2 pupil_position_in_sensor_area : 正規化された瞳孔の位置

TrackingImprovements : トラッキング改善
- int count : itemsの数?
- TrackingImprovement[] items

TrackingImprovement
?

関連リンク

公式のスライド

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

【VFX Graph】宝箱をキラキラ光らせてみた。

はじめに

宝箱をキラキラさせるエフェクトをVFX Graph(Visual Effect Graph)で作ってみました。

sparkle_scene.gif

今回の記事では、上記のエフェクトの作り方を解説したいと思います。

環境

【Unityバージョン】
Unity 2019.1.1f1

【パッケージ】
Lightweight RP - Version 5.7.2
Visual Effect Graph - Version 5.13.0(Preview)

【OS】
Windows 10

STEP1 : テクスチャの準備

今回は以下の四角星のテクスチャを使用しました。(解像度:128x128)
四角星テクスチャ(透過png)

Star_no_alpha.png

上記のテクスチャはSubstance Designerを使って作成しています。
image.png

STEP2 : VFX Graphの作成

今回は以下のようなVisualEffectGraphを作成しました。
image.png

VFX Graph解説 : Spawnノード(パーティクルの生成)

Constant Spawn Rateブロックを使って毎秒32個のパーティクルが出るようにしています。

VFX Graph解説 : Initializeノード(パーティクルの初期化)

Position(Sphere)ブロックでは、パーティクルの初期位置が球状に配置されるようにしています。

Set Lifetime Randomブロックではパーティクルの寿命を設定されるようにしています。
image.png

VFX Graph解説 : Updateノード(パーティクルの毎フレーム処理)

今回はUpdateノードには何も行っていません。

image.png

VFX Graph解説 : Outputノード(パーティクルの描画)

Set Size over Lifeブロックでは、時間経過で徐々にパーティクルサイズが小さくなるような変化を与えています。パーティクル生成時は1で、最後には0になるようになっています。

次にMultiply Sizeブロックを使い、パーティクルサイズを0.2倍にしています。

最後にMultiply Color randomizedブロックを使ってパーティクルの色をランダムにしています。

image.png

STEP3 : VFX Graphの配置

STEP2のエフェクトを配置すると以下のようになります。
result.gif

VFXGraphのパラメータは以下のように設定しました。
image.png

STEP4 : 宝箱の配置

宝箱を配置し、Post Processing StackのBloomを適用すると以下のような見た目になります。
result2.gif

宝箱について

宝箱は下記のアセットを使用させていただきました。

Treasure Set - Free Chest
https://assetstore.unity.com/packages/3d/props/interior/treasure-set-free-chest-72345

参考URL

【Unity】Visual Effect Graph(VFX Graph)をLWRPで使用する
http://tsubakit1.hateblo.jp/entry/2019/04/03/233240

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

Monobit Unity Networkingを使ってネットワークマネージャクラスを作る【モノビットエンジン入門】

はじめに

これまで、MUNの記事を色々とまとめてきましたが、公式リファレンスがかなり充実していることもあり、
基本的な内容は飛ばして紹介することもしばしばでした。

しかし、いくらリファレンスがあるとは言え、一から読むのもなかなか苦ですから、
サーバ接続/切断、ルーム入室/退室という機能に絞り「とりあえずこれを書けば動かして確認できる」という所までを一度まとめることにしました。
MUNを使ったルームマッチング機能を作る上で参考程度に読んでいただければと思います。

※対象読者さんはオンライン開発を始めたばかりの方ですので初心者向けです。

モノビットエンジンを使う準備がお済でない方は、まずはこちらをご覧ください。
モノビットエンジンクラウドでリアルタイム通信のオンラインゲームを作る:準備編
モノビットエンジンでかんたんサーバ接続

環境は以下の通りです
win10 64bit, Unity2018.3.3f1, Monobit Unity Networking2.0 v2.6

結果

サーバ未接続状態
1.png
サーバ接続状態
2.png
他クライアントから見たロビー画面
3.png

作るもの

まず、以下の3つのスクリプトからなるネットワークマネージャクラスを作ります。
NetworkManager.cs … ネットワーク管理クラス
 |ーServerManager.cs … サーバ接続/切断を管理するクラス
 |ーMatchingManager.cs … ルーム入室/退室、マッチングを管理するクラス

次に、使い方と動作確認のためにuGUI表示クラスを作ります。
InGameGUI.cs
こちらはあくまで確認用なので必要のない方は読み飛ばしてもらって大丈夫です。

注意点として、NetworkManagerクラスはシングルトンクラスなので、Monobehaviourを汎用シングルトン用に改造したSingletonMonobehaviourクラスを継承させます。別の方法でシングルトンにしても全く問題ありません。
書くのが面倒な方はページの最後のほうにSingletonMonobehaviourクラスを載せているので合わせて使ってください。

実装

NetworkManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MonobitEngine;

public class NetworkManager : SingletonMonoBehaviour<NetworkManager> {

    ServerManager m_ServerManager = null;
    MatchingManager m_MatchingManager = null;

    void Awake () {
        m_ServerManager = gameObject.AddComponent<ServerManager>();
        m_MatchingManager = gameObject.AddComponent<MatchingManager>();
    }

    /// <summary>
    /// サーバに接続
    /// </summary>
    public void ConnectServer(string playerName) {
        if (m_ServerManager == null) return;
        m_ServerManager.ConnectServer(playerName);
    }

    /// <summary>
    /// サーバから切断
    /// </summary>
    public void DisconnectServer() {
        if (m_ServerManager == null) return;
        m_ServerManager.DisconnectServer();
    }

    /// <summary>
    /// ルーム作成
    /// </summary>
    public void CreateRoom(string roomName) {
        if (m_MatchingManager == null) return;
        m_MatchingManager.CreateRoom(roomName);
    }

    /// <summary>
    /// ルーム入室
    /// </summary>
    public void JoinRoom(string roomName) {
        if (m_MatchingManager == null) return;
        m_MatchingManager.JoinRoom(roomName);
    }

    /// <summary>
    /// ルーム退室
    /// </summary>
    public void LeaveRoom() {
        if (m_MatchingManager == null) return;
        m_MatchingManager.LeaveRoom();
    }

    /// <summary>
    /// サーバからの切断要求がある
    /// </summary>
    /// <returns></returns>
    public bool IsDisconnectRequest() {
        bool res = false;
        if (m_ServerManager != null) {
            res = m_ServerManager.m_IsDisconnectRequest;
        }
        return res;
    }

    /// <summary>
    /// オブジェクトが削除されたタイミングで呼ばれる
    /// </summary>
    protected override void OnDestroy() {
        base.OnDestroy();
    }
}

実際のゲームからはこのNetworkManagerクラスのメソッドを呼び出してサーバ接続/切断、ルーム入室/退室を行っていきます。
このクラスは見ての通り下位クラスメソッドを呼び出すための仲介役です。
細かい処理は下位クラスで行います。見ていきましょう。

ServerManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MonobitEngine;

public enum ConnectionState {
    Connect,            // 接続中
    Disconnect,         // 切断中
}

public class ServerManager : MonobitEngine.MonoBehaviour {

    public ConnectionState m_State = ConnectionState.Disconnect;

    public bool m_IsDisconnectRequest = false;

    /// <summary>
    /// サーバに接続
    /// </summary>
    public void ConnectServer(string playerName) {
        if(!MonobitNetwork.isConnect) {
            MonobitNetwork.autoJoinLobby = true;
            MonobitNetwork.playerName = playerName;
            MonobitNetwork.ConnectServer("v1.0");
            m_State = ConnectionState.Disconnect;
        }
    }

    /// <summary>
    /// サーバを切断
    /// </summary>
    public void DisconnectServer() {
        if (MonobitNetwork.isConnect) {
            m_IsDisconnectRequest = true;
            if (MonobitNetwork.inRoom) {
                MonobitNetwork.LeaveRoom();
            } else if (MonobitNetwork.inLobby) {
                MonobitNetwork.LeaveLobby();
            }
        }
    }

    //--------------------------------------------------
    // 以下、MUNコールバック関数
    //--------------------------------------------------
    /// <summary>
    /// サーバに接続したときに呼ばれる
    /// </summary>
    public void OnConnectedToServer() {
        Debug.Log("OnConnectedToServer");
        m_State = ConnectionState.Connect;
    }

    /// <summary>
    /// サーバから切断されたときに呼ばれる
    /// </summary>
    public void OnDisconnectedFromServer() {
        Debug.Log("OnDisconnectedFromServer");
        m_State = ConnectionState.Disconnect;
        m_IsDisconnectRequest = false;
    }

    /// <summary>
    /// ロビーに入室したときに呼ばれる
    /// </summary>
    public void OnJoinedLobby() {
        if (MonobitNetwork.autoJoinLobby) {
            Debug.Log("OnConnectedToServer");
            m_State = ConnectionState.Connect;
        }
        Debug.Log("OnJoinedLobby");
    }
}

サーバ接続/切断を行うクラスです。
ConnectServer()はサーバ未接続なら接続要求を出します。
サーバがその要求を受理するとOnConnectedToServer()コールバックメソッドが呼ばれます。
(サーバへの接続 - MUN公式リンク)
(コールバック関数一覧 - MUN公式リンク)

DisconnectServer()は反対に切断の要求を出しますが、注意点として入室状態ならルーム退室→ロビー退室→サーバ切断という順に処理を行います。(サーバからの切断 - MUN公式リンク)

MatchingManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MonobitEngine;

public enum RoomErrorType {
    CROWDED_ROOM,       // 部屋が満員
    NOT_EXIST_ROOM,     // 部屋が存在しない
    FAILED_CREATE_ROOM, // 部屋の作成に失敗
    ALREADY_JOIN_ROOM,  // 既に入室済み
    UNKNOWN_ERROR,      // その他のエラー
    NONE,               // エラー無し
}

public class MatchingManager : MonobitEngine.MonoBehaviour {

    Dictionary<RoomErrorType, string> m_RoomErrorMsgDict = new Dictionary<RoomErrorType, string>() {
        {RoomErrorType.CROWDED_ROOM,        "Crowded room."},
        {RoomErrorType.NOT_EXIST_ROOM,      "Not exist room."},
        {RoomErrorType.FAILED_CREATE_ROOM,  "Failed create room."},
        {RoomErrorType.ALREADY_JOIN_ROOM,   "Already join room."},
        {RoomErrorType.UNKNOWN_ERROR,       "Unknown error."},
    };

    /// <summary>
    /// ルームを作成する
    /// </summary>
    public RoomErrorType CreateRoom(string roomName) {
        var ret = RoomErrorType.NONE;

        do {
            if (MonobitNetwork.inRoom) {
                ret = RoomErrorType.ALREADY_JOIN_ROOM;
                break;
            }

            RoomSettings roomSetings = new RoomSettings() {
                isVisible = true,
                isOpen = true,
                maxPlayers = (uint)DefineValue.MAX_ROOM_PLAYERS_COUNT,
                roomParameters = null,
                lobbyParameters = new string[] { }
            };

            if (!MonobitNetwork.CreateRoom(roomName, roomSetings, null)) {
                ret = RoomErrorType.FAILED_CREATE_ROOM;
                break;
            }

        } while (false);

        ParseRoomErrorLog(ret);

        return ret;
    }

    /// <summary>
    /// ルームに入室する
    /// </summary>
    public RoomErrorType JoinRoom(string roomName) {
        var ret = RoomErrorType.NONE;

        ret = EnableJoinRoom(roomName);

        if (ret == RoomErrorType.NONE) {
            MonobitNetwork.JoinRoom(roomName);
        }

        ParseRoomErrorLog(ret);

        return ret;
    }

    /// <summary>
    /// 入室可能かどうか調べる
    /// </summary>
    public RoomErrorType EnableJoinRoom(string roomName) {
        var ret = RoomErrorType.NONE;
        bool isExist = false;
        foreach (RoomData roomData in MonobitNetwork.GetRoomData()) {
            if (roomData.name == roomName) {
                isExist = true;
                if (roomData.playerCount >= DefineValue.MAX_ROOM_PLAYERS_COUNT) {
                    ret = RoomErrorType.CROWDED_ROOM;
                }
                break;
            }
        }

        if (!isExist) {
            ret = RoomErrorType.NOT_EXIST_ROOM;
        }

        ParseRoomErrorLog(ret);

        return ret;
    }

    /// <summary>
    /// ルームを去る
    /// </summary>
    public void LeaveRoom() {
        if (MonobitNetwork.inRoom) {
            MonobitNetwork.LeaveRoom();
        }
    }

    /// <summary>
    /// エラー内容を解読して表示
    /// </summary>
    /// <param name="errorType"></param>
    public void ParseRoomErrorLog(RoomErrorType errorType) {
        if (m_RoomErrorMsgDict.ContainsKey(errorType)) {
            string log = m_RoomErrorMsgDict[errorType];
#if DEBUG_LOG
            Debug.LogWarning("ROOM_ERROR:" + log);
#endif
        }
    }

    //--------------------------------------------------
    // 以下、MUNコールバック関数
    //--------------------------------------------------
    /// <summary>
    /// ルーム作成が成功したときに呼ばれる
    /// </summary>
    public void OnCreatedRoom() {
        Debug.Log("OnCreatedRoom");
    }

    /// <summary>
    /// ルームに入室したときに呼ばれる
    /// </summary>
    public void OnJoinedRoom() {
        Debug.Log("OnJoinedRoom");
    }

    /// <summary>
    /// ルームを退室したとき呼ばれる
    /// </summary>
    public void OnLeftRoom() {
        Debug.Log("OnLeftRoom");
        if (NetworkManager.Instance.IsDisconnectRequest()) {
            MonobitNetwork.LeaveLobby();
        }
    }

    /// <summary>
    /// ロビーから退室したときに呼ばれる
    /// </summary>
    public void OnLeftLobby() {
        Debug.Log("OnLeftLobby");
        if (NetworkManager.Instance.IsDisconnectRequest()) {
            MonobitNetwork.DisconnectServer();
        }
    }
}

ルームの入退室に関する処理をするクラスです。
CreateRoom()のなかではMonobitNetwork.CreateRoom()を使ってルームを作成をしています。第2引数にルーム設定情報を指定することができます。(ルームの作成 - MUN公式リンク)

JoinRoom()のなかではEnableJoinRoom()を使って入室可能かどうか調べています。
foreach (RoomData roomData in MonobitNetwork.GetRoomData())で公開中の全ルームを取得し、ルーム名を比較しています。
(ルームへの入室 - MUN公式リンク)

InGameGUI

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MonobitEngine;

public class InGameGUI : MonobitEngine.MonoBehaviour {

    string m_PlayerName = "player1";
    string m_RoomName = "room1";

    void OnGUI() {
        if (!MonobitNetwork.isConnect) {
            OnGUI_Connect();
        }
        else if (!MonobitNetwork.inRoom) {
            OnGUI_RoomCreate();
            OnGUI_ChooseRoom();
        }
        else {
            OnGUI_LeaveRoom();
        }
        OnGUI_Disconnect();
    }

    void OnGUI_Connect() {
        GUILayout.BeginHorizontal();
        GUILayout.Label("Your Name");
        m_PlayerName = GUILayout.TextField(m_PlayerName, GUILayout.Width(150));

        if (GUILayout.Button("Connect Server", GUILayout.Width(150))) {
            if (!string.IsNullOrEmpty(m_PlayerName)) {
                NetworkManager.Instance.ConnectServer(m_PlayerName);
            }
        }
        GUILayout.EndHorizontal();
    }

    void OnGUI_Disconnect() {
        if (GUILayout.Button("Disconnect Server", GUILayout.Width(150))) {
            NetworkManager.Instance.DisconnectServer();
        }
    }

    void OnGUI_LeaveRoom() {
        if (GUILayout.Button("Leave Room", GUILayout.Width(150))) {
            NetworkManager.Instance.LeaveRoom();
        }
    }

    void OnGUI_RoomCreate() {
        GUILayout.BeginHorizontal();
        GUILayout.Label("Room Name");
        m_RoomName = GUILayout.TextField(m_RoomName, GUILayout.Width(150));

        if (GUILayout.Button("Create", GUILayout.Width(75))) {
            if (!string.IsNullOrEmpty(m_RoomName)) {
                NetworkManager.Instance.CreateRoom(m_RoomName);
            }
        }
        GUILayout.EndHorizontal();
    }

    void OnGUI_ChooseRoom() {
        foreach (RoomData roomData in MonobitNetwork.GetRoomData()) {
            if (roomData.playerCount < roomData.maxPlayers) {
                GUILayout.BeginHorizontal();
                GUILayout.Label("Room Name: " + roomData.name + "(" + roomData.playerCount + "/" + ((roomData.maxPlayers == 0) ? "-" : roomData.maxPlayers.ToString()) + ")", GUILayout.Width(200));

                if (GUILayout.Button("Join", GUILayout.Width(75))) {
                    MonobitNetwork.JoinRoom(roomData.name);
                }
                GUILayout.EndHorizontal();
            }
        }
    }
}

SingletonMonoBehaviour

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SingletonMonoBehaviour<T> : MonoBehaviour where T : MonoBehaviour {

    private static T m_Instance = null;

    /// <summary>
    /// インスタンス取得
    /// </summary>
    public static T Instance
    {
        get
        {
            if(m_Instance == null) {
                GameObject obj = new GameObject(typeof(T).Name);
                m_Instance = obj.AddComponent<T>();
                DontDestroyOnLoad(obj);
            }
            return m_Instance;
        }
    }

    /// <summary>
    /// 既にインスタンスが存在するか
    /// </summary>
    public static bool IsInstance() {
        var ret = true;
        if (m_Instance == null) {
            ret = false;
        }
        return ret;
    }

    /// <summary>
    /// オブジェクト削除処理
    /// </summary>
    protected virtual void OnDestroy() {
        m_Instance = null;
    }
}

最後に

いかがだったでしょうか。
結果確認についてはビルド後に複数クライアントを立ち上げて確認することをお勧めします。
これで一通りMUNでネットワーク処理を作る土台はできたと思います。
続きが気になる方は以下のリンクから同期処理に挑戦してみてください。

MUN関連記事

かんたんなPinpong対戦ゲームを作った記事はこちらです

リアルタイムシューティングゲームを作った記事はこちらです

機能に関する記事はこちらです

参考リンク

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

プロファイラーを使ってOculus Quest実機での性能を計測する

はじめに

Oculus Questの実機でボトルネックになってる原因を確認したいなと思ったので、手順を残しておきます。
計測されたデータの分析については書きません。
参考:最適化をする前に覚えておきたい技術

前提

自分で作成したapkをOculus Questで実行できていること

環境

・Unity 2018.3.8f1
・USBでQuestに接続

手順

1. Autoconnect Profilerにチェックを入れてビルド

計測したい対象のアプリケーションをビルドする際にDevelopment BuildとAutoconnect Profilerにチェックを入れてビルドします。
image.png

2. apkファイルをQuestにインストール

作成したapkファイルをQuestにadb installでインストールします

3. プロファイラの情報に接続するためのコマンドを実行

adb forward tcp:34999 localabstract:Unity-{insert bundle identifier here}のコマンドを実行
※1 bundle identifierはPlayer Settings → Other SettingsのPackage Nameを設定(下の図の場合com.Sample.Test)となり、adb forward tcp:34999 localabstract:Unity-com.Sample.Testのコマンドになる
image.png
※2 Build And Runでインストール、アプリ起動する場合は内部的にこのコマンドを実行してくれますが、私は普通にインストールしてこのコマンドを実行する方が好きです。

4. プロファイラウィンドウを開く

Window → Analysis → Profilerを選択し、プロファイラウィンドウを開きます
image.png

5. アプリを実行、計測する

USBを接続したままでアプリを実行します。そしてプロファイラウィンドウのEditor → AndroidPlayerを選択します。あとは計測したいタイミングでRecordのボタンを押せば計測された情報が流れてきます。
image.png
image.png

あとはプロファイラを操作してボトルネックの箇所を特定しましょう!

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

覚書 BlenderでVoxelとRigfyをFBXでエクスポートしてUnityに取り込む

完全な覚書

前提条件
・MagicaVoxelですでにモデルは作った
・Rigfyのボーンをモデルに合わせた

自分の場合は別のアーマチュアにモデルが紐づいている状態、これを新しいBornHmdに紐づける

190710-0001.png

モデルを選択し、オブジェクトモードで複製

190710-0002.png

複製が出来上がったがまだ旧アーマチュアと紐づいている

190710-0003.png

旧アーマチュアと親子関係を解消

190710-0004.png

これで外れた

190710-0005.png

名前とかをわかりやすく(必要ならば)

190710-0006.png

オブジェクトモードでモデル>ボーンの順番で選択

shiftを押しながら選択すると複数選択できる、順番が大切
190710-0007.png

新しいアーマチュアと親子関係になる

190710-0008.png

すぐにこの画面が出るので自動ウェイトを選択する
190710-0009.png

BornHmdの子として登録された

190710-0010.png

ポーズモードに変更し、ボーンにウェイトが乗っているか確認

190710-0011.png

FBXでエクスポート

190710-0012.png

設定周り

190710-0013.png
190710-0014.png
190710-0015.png
190710-0016.png

Unityに持っていく

190710-0019.png

ボーンやアーマチュアがエクスポートされない場合はJoinを試してみる.
オブジェクトモードでモデル>ボーンの順番で選択し、Join(Cntl + j)

190710-0017.png

ちょっと最近暇つぶしに昔の資産を引っ張りだしていたりする

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