- 投稿日:2019-09-09T20:46:22+09:00
UnityでLWRP+PostProcessingStackをVRで使う際の注意点
はじめに
こんにちは、最近ゲームエフェクトの勉強をしているZeniZeniです。
ゲームエフェクトの見栄えをよくするためにはPostProcessは非常に便利なんですが、LWRP(Light Weight Render Pipeline)と併用して、かつVRでやりたい!となると、かなりつまづくところが多いです。
この記事では、LWRP+PostProcessingStackをVRで使う場合の設定方法と注意点を説明します。動作環境
まず、現行はどのバージョンでもAndroidではLWRP+PostProcessingStackをVRで使うことはできないようなので、OculusQuestでやりたいと思っている方々はUnityの正式対応を待ちましょう。
その他のPCVRなども、Unity2019以上でないと動作はしません。
たとえsceneビューや(実行前の)Gameビューでポストエフェクトがかかっていたとしても、実行中にVR画面から見た映像にはポストエフェクトはかかりません。VRではない(CameraのTarget EyeがNoneになっている)カメラならちゃんと映ります。またUnity2018以下のLWRP対応したプロジェクトを、Unity2019.1以上のバージョンにアップグレードすると、大量のエラーが発生しどうしようもなかったので、おとなしくUnity2019.xで新規に作成するのが良いと思います。
今回は
- Unity 2019.1.14f1
- Lightweight RP version 5.7.2
でやっていきます。
初期設定
まず新規プロジェクトを作成します。versionは2019.1.14f1。TemplateはVR Lightweight RPです。
そして最初に開かれる下の画像のようなSampleSceneでは、もうすでにLWRP+PostProcessingStackをVRで見ることができます。
Post Process VolumeのBloomの値を変えたりして試してみてください。
注意点
Color Gradingがうまくいかない
ポストエフェクトのColor Gradingは明るさと色調の調整を行うエフェクトですが、HDR(High Dinamic Range)設定をちゃんとしてやらないとうまく動作しません。詳しい説明は【Unite 2017 Tokyo】ゲームの見た目も盛ったら変わる!!!!ヤバい!!ポストプロセス!!入門!!!!!!!!! を見てみてください。
まずEdit>Project Settings>Graphics
のScriptable Render Pipeline Settings
を確認しましょう。
そこで設定されているLWRP assetを選択します。
そこのQualityのHDRのチェックボックスを付けておかないと、HDRが動作しません。
ここでは影の細かい設定なんかも行えます。
またCameraのHDR設定もOffになっていると使えないので、Use Pipeline Settings
にしておきましょう。
一部動作しないエフェクトがある
こちらのPost Processing in the Lightweight Render Pipelineで説明されていますが、以下のエフェクトはLWRP環境では使えません。
- Motion Blurや一時的なアンチエイリアスを含むモーションベクトルベースのエフェクト
- Screen Space Reflectionsといった高価なレンダリング計算が必要なエフェクト
- ターゲットハードウェアがCompute Shaderをサポートしていない場合は Auto-exposureやAmbient Occlusion (MSVO)、Debug Monitorsといったエフェクト
VR環境ではVignetteエフェクトをつけるとよい
上述したPost Processing in the Lightweight Render Pipelineで、
VRアプリでは、特定のポストエフェクトが吐き気と見当識障害を引き起こす可能性があります。 テンポの速いゲームまたは高速ゲームで乗り物酔いを軽減するために、UnityではVRにVignette効果を使用することをお勧めします。
と説明されています。
Vignetteとはなんぞやと思うかもしれませんが、要は視界の縁まわりを少し暗くするエフェクトのことです。Unityのドキュメントにわかりやすい説明が書いてあります。その他Tips
LWRP対応していないアセットを一括でLWRP対応させる。
LWRPに対応していない(LWRPのShaderを使っていない)マテリアルは真っピンクで表示されてしまいます。これは
Edit>Render Pipeline>Upgrade Project Materials To LightweightRP Materials
で一括でいい感じに変更してくれます。開発、執筆にあたり、下記のサイト様を参考、引用させていただきました。
- 投稿日:2019-09-09T18:24:25+09:00
Unity玉転がしをスマホにビルド
はじめに
Unityの玉転がしをスマホに入れる。
Unity Hubを使ってUnityを入れているとAndroidのビルドがすごく簡単。
Unity Hubをダウンロード:https://unity3d.com/jp/get-unity/download
iOSのビルドもしますが基本的にMacが必要
なので注意。
また、私はAndroidなのでiOSの説明は雑になります注意。スマホ操作用にスクリプトの書き換えもします。
完成したUnityチュートリアルの玉転がしを書き換える。
玉転がし:https://unity3d.com/jp/learn/tutorials/s/roll-ball-tutorialUnityバージョンは2019.2.1f1
Unity Hubは 2.1.0準備
Unity Hubを開いてインストール>3つの点>モジュールを加える。
Androidなら
iOSなら
Android Build Support
Android SDK & NDK Tools
OpenJDK
の3つを選択して実行。iOS Build Support
を選択して実行。
何か聞かれるかもしれないけど同意してインストール。インストールが終わったら準備ok。
プロジェクトを開く。スクリプト書き換え
キーボードで操作していた部分をスマホでも操作できるようにする。
だいたいUpdate()の中身を書き換える。PlayerController.cspublic class PlayerController : MonoBehaviour { //重力加速度 const float GRAVITY = 9.81f; // 追加 public float speed; public Text scoreText; public Text winText; private Rigidbody rb; private int score; private void Start() { rb = GetComponent<Rigidbody>(); score = 0; SetCountText(); winText.text = ""; } void Update() { Vector3 vec = new Vector3(); // 追加 //エディターか実機か判定する if (Application.isEditor) { // こちらが元々の操作 // ここに元々Update()に入っていたコードを移す var moveHorizontal = Input.GetAxis("Horizontal"); var moveVertical = Input.GetAxis("Vertical"); var movement = new Vector3(moveHorizontal, 0, moveVertical); rb.AddForce(movement * speed); } else { // こちらがスマホでの操作 vec.x = Input.acceleration.x; vec.z = Input.acceleration.y; vec.y = Input.acceleration.z; //シーンの重力を入力ベクトルの方向に合わせて変化させる Physics.gravity = GRAVITY * vec.normalized * speed/10; } }キーボードでの操作から端末の傾きでの操作に変える。
タッチでの操作。
if (Input.GetTouch(0).phase == TouchPhase.Ended) { // タッチで行いたい処理、ジャンプとか }これでスマホで操作できるはず。
設定
メニューからFile>Build Settingsを開く。
使うシーンを入れる
Scenes In Build
に使うシーンをAssetsからドラッグドロップ。
シーンが複数あるなら最初に来て欲しいシーンを一番上にする。
玉転がしならシーンはひとつのはず。プラットフォームを切り替える
iOS
かAndroid
を選択してSwitch Platform
。
現在選んでいるプラットフォームにはUnityアイコンがついている。Player Settings
Player Settings
を開く。
◆
Company Name
とProduct Name
を適当に変える。
変えないとビルドに失敗する。
◆Resolution and Presentation>Orientation>Default Orientation
をLandscape Right
にする。
スマホの向きを横向きで固定している。
他の向きにしたり傾き検知で回転もできる。
◆Other Settings>Identification>PackageName
を一応確認する。iOSはBundle Identifier
。
先ほど変えたCompany NameとProduct Nameの組み合わせになっていれば○
例:com.watasi.ball
これで設定ok。
ここからAndroidとiOSで分かれる。Android
端末側の設定を変える。
◆ 設定>端末情報>ソフトウェア情報 からビルド番号を7回タップし開発者モードにする。
◆ 設定>開発者向けオプション からUSBデバッグをオンにする。
◆ 端末とPCをデータ通信が可能なケーブルでつなぐ。これで端末側の準備はok。
ケーブルを繋いだらBuild Settings
のBuild And Run
をクリック。
適当に名前をつけ
Save
。
保存場所もそのままで良い。これで自身のスマホにゲームをビルドできたはず。
できなかった人は解決策をまとめるので見てみて。iOS
Macがないとダメよ?
とりあえず
Xcodeが必要になるのでApp Storeからインストールする。Build Settings
のBuild And Run
をクリック。
適当に名前をつけ
ビルド後にXcodeが開くはず。Save
。
保存場所もそのままで良い。
左の一番上(Unity-iPhone)をクリック。
タブのGeneral
を選択。
Teamを選んでAdd An Account
、自身のApple IDでログイン、戻る。
またTeamを選んで先ほど追加したアカウントを選択。端末とPCをケーブルで繋ぎ、上側の
~~ Generic iOS Device
から自身の端末を選ぶ。再生マークのボタンを押す。
端末側でMacと制作者を信用する。
ビルド成功でok。
エラーが起きたら
体験したビルドの失敗と解決策を書いておく。
Androidエラー
No Android devices connected
◆端末をPCと繋がずにビルドした
繋ぐ。◆ケーブルがデータ通信に対応していない
ケーブルがデータ通信に対応している場合、PCと繋いだ時端末側のステータスバーに「通常充電」の他に、「USBをファイル転送に使用」という項目が出てくるはず。◆端末側でUSBデバッグがオンになっていない
上のAndroidを見てもう一度。◆逆に繋いでいないのにこのエラーが出ない
プラットフォームがAndroidに切り替わってないかも。CommandInvokationFailure: Unable to install APK to device. Please make sure the Android SDK is installed and is properly configured in the Editor. See the Console for more details.
実際に体験していないエラーなので解決策だけをつらつらと。
◆タスクマネージャーでadb.exeのプロセスを終了する
よく分からないけどこれでビルドが通るようになるらしい。◆Package Nameを変える
Player SettingsでCompany Name
とProduct Name
を変え、Package Name
が変わっていることを確認したはず。
それでもダメならさらにPackage Name
を変えてみる。◆保存したビルドのパスに日本語を入れない
C:aaa/bbb/ccc/ddd.apk
ビルドすると拡張子がapkで保存されると思いますが、そこまでのパスに日本語が入っているとダメだそうです。iOSエラー
なんのエラーがあったかふわっと忘れました。
https://qiita.com/segur/items/bef54efa7764885173bb
この方の「ケース:プライバシー情報がロックされてしまった」の症状でした。おわり
だんだんと雑になっていく記事になりました。
大変ですがスマホに入れられるとわくわくしますね。
- 投稿日:2019-09-09T17:52:08+09:00
【Unity(C#)】自作VIVEコンテンツでスタート位置を気にしなくて済む実装
VIVEのリセンター機能
Standing Mode
やSeated Mode
にはSteamVRが用意している機能で
リセンター(再トラッキングしてポジションを中央に戻す)が可能ですが、Room-Scale
でのプレイには対応していません。なので、VIVEを担いで営業デモに行った際にはスタート位置をバミるという手法を使っていました。
バミらないと、いきなりポリゴンにめり込んだ状態から始まったり、
明後日の方向を向いてしまって、後ろからしゃべりかけられてストーリーが展開する...なんてことが起きます。位置と向きを補正
しかし、バミリは複数のコンテンツを体験していただく際には厄介です。(あれがこっち、これはあっちと余計なことに気を使います)
さらに、コンテンツの内容、作り方によってはスタート位置がまちまちになってしまうので、
部屋の大きさによっては、
・コンテンツAでは前方のスペースに十分なエリアが必要
・コンテンツBでは後方のスペースに十分なエリアが必要
といった具合に、それぞれのコンテンツで必要なエリアの幅が異なることで正しく体験できないという現象が起きかねません。また、私は製作者側なのでデモ時に上記のような状況でも何をどう対処すればいいか瞬時に判断できますが、
コンテンツを使うユーザーからしたら意味不明で、ストレスでしかありません。なので、コンテンツにリセンター機能を実装して組み込むことで、開始位置の固定を取り払いました。
コード
やっていることはシンプルで、
スタートと同時にコルーチン内でCameraRigの角度を回転させてCamera(プレイヤー)の向きを補正し、
その後にCameraRigの位置にCamera(プレイヤー)のトラッキング位置を考慮した補正をかけています。using UnityEngine; using System.Collections; using UnityEngine.Events; /// <summary> /// Recenter your camera when you start VR. Attach to CameraRig. /// </summary> public class ReCenterCamera : MonoBehaviour { [SerializeField, Tooltip("Set this child camera")] GameObject eyeCamera; [SerializeField,Tooltip("This event is skiped one frame")] UnityEvent startEvent; void Start() { StartCoroutine(ReCenterCoroutine()); } IEnumerator ReCenterCoroutine() { Vector3 cameraRig_Angles = this.gameObject.transform.eulerAngles; Vector3 eyeCamera_Angles = eyeCamera.transform.eulerAngles; this.gameObject.transform.eulerAngles += new Vector3(0, cameraRig_Angles.y - eyeCamera_Angles.y, 0); Vector3 cameraRig_StartPos = this.gameObject.transform.position; Vector3 eyeCamera_Pos = eyeCamera.transform.position; this.gameObject.transform.position += new Vector3(cameraRig_StartPos.x - eyeCamera_Pos.x, 0, cameraRig_StartPos.z - eyeCamera_Pos.z); yield return null; startEvent.Invoke(); } }位置の補正に関しては前回の記事の内容をそのまま転用しています。
今回苦労したのは角度に関してです。
角度の調整
図解します。
黒い四角がカメラ(プレーヤー)、青い四角がCameraRigだとします。
それぞれのY軸回りのローテーションはy₁
,y₂
です。
カメラ(プレーヤー)をVR起動時に、必ずA地点の方に向けた状態でスタートしたいという想定です。シンプルな計算で、実装できます。
カメラ(プレーヤー)とCameraRigのY軸回りのローテーションの差を計算して、CameraRigのY軸回りのローテーションに足すだけです。今回のプログラムでは加算代入によって
y₂+y₂-y₁
の部分を実装しています。this.gameObject.transform.eulerAngles += new Vector3(0, cameraRig_Angles.y - eyeCamera_Angles.y, 0);なぜコルーチンなのか
なぜ、コルーチンにしているのかにも、きちんとした理由があり、今回苦労した原因です。
その原因とはトラッキングが完了してから補正する必要があるということです。
自分の今いる座標、回転座標が定まっていない(トラッキングが完了していない)状態で補正をかけても
意味がありませんよね。なので、
①Updateでトラッキングの処理が終わる
②Updateより後に実行されるコルーチン内で位置、角度の補正
という流れになっているということです。※コルーチンのUnityの実行順についてはこちらで図解してあります。
この実装の欠点
この実装にはいくつか不具合の元がありまして、既存のコンテンツに後入れで導入した場合、バグを生む可能性があります。
例えば、カメラの座標がワールド座標の○○を超えたらイベント発生、などの実装があった場合、
最初の1フレームでそのイベントが発生してしまう恐れがあります。なぜそんなことが起きる可能性があるかというと、最初の1フレームをトラッキングに使用するので、
実際に目に見えて始まる位置(補正後の位置)とは異なる座標を1フレーム分通過してしまうからです。なので、最初の1フレームの間、そのイベントのトリガーはオフにしておいて、
位置補正終了と同時にオンにするなどの工夫が必要です。
今回のコードにもイベントを登録できる形で組み込んでいます。yield return null; startEvent.Invoke(); //ここにトリガーをオンにするイベントを登録また、トラッキングがMissingになった状態でUpdateの最初の1フレームを通過してしまうと
補正が正しくかからない等の問題点も残しており、まだまだ改善の余地はありそうです。あと今回の内容はOculusコンテンツに導入しても正しく動きます。
むしろ、最初はOculus向けに作ってた機能で、VIVEにつっこんだらなんか動いたからヨシ!って感じです。
- 投稿日:2019-09-09T16:35:12+09:00
Unityでオンラインマルチプレイなゲームを作りたい その8 プレイヤーの同期
前回の記事でゲーム開始前の準備を行うところまで作りました。
今回からはようやくゲーム部分に取り掛かっていきます。今回からの目標
赤い〇で囲っている部分を実装していきます。
その3の最終目標で書いてる通り、ローグライク * アクションな2Dのゲームを作っていきます。プレイヤーの同期
とりあえず、最初は自分が操作するプレイヤーとその同期を実装していきます。
実装内容としては、
- プレイヤーオブジェクトの生成
- 移動の処理の同期
- 弾の発射と同期
- パラメータの同期
- プレイヤーオブジェクトの削除
の5つになります。
1.プレイヤーオブジェクトの生成
まず、操作するプレイヤーのプレハブを作っていきます。
Hierarchyで空のオブジェクトを生成し、わかりやすいような名前に変更します。(図ではPlayerという名前にしてます)
作成した空のオブジェクトに同期を行なうための機能を追加します。
MonobitViewコンポーネント
ネットワーク越しでオブジェクトの通信/同期を行なうために必要な機能です。
MonobitTransformViewコンポーネント
空のオブジェクトに元からついているTransform(主にPosition, Rotate, Scale)をネットワーク越しで同期を行なってくれるコンポーネントです。
これを追加することで、位置, 向き, 大きさの同期を行なってくれます。この二つを先ほど作ったオブジェクトにAdd Componentして設定していきます。
こんな感じですね。 次に、追加したMonovitViewコンポーネントの設定を行います。
MonovitViewコンポーネントのAdd Observed Component List Columnボタンを押します。
押した後に表示されたところにMonobitTransformViewをアタッチします。
これで下準備が出来たので、このオブジェクトを操作するためのPlayer.csスクリプトを作成します。Player.csusing UnityEngine; using MonobitEngine; public class Player : MonobitEngine.MonoBehaviour { /// <summary>移動方向</summary> private Vector3 m_MovementAmount = Vector3.zero; /// <summary>移動速度</summary> private float m_Velocity = 1.0f; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { Move(); } /// <summary>移動</summary> private void Move() { m_MovementAmount.x = Input.GetAxis("Horizontal"); m_MovementAmount.y = Input.GetAxis("Vertical"); transform.localPosition += m_MovementAmount * m_Velocity * Time.deltaTime; } }MUNの機能を利用すために、
public class Player : MonobitEngine.MonoBehaviour
と継承元をMUNの物に変えています。
後は、とりあえずシンプルな移動操作のみ実装しています。
このスクリプトを先ほどのオブジェクトに追加し、プレハブ化しておいてください。プレイヤーのプレハブが作成できたので、このプレハブをネットワーク越しに生成してみます。
ネットワーク越しのオブジェクトの生成/MonobitEngine.MonobitNetwork.Instantiate メソッド(1)
この機能を使ってネットワーク越しにプレイヤーオブジェクトを生成させます。
適当に空のオブジェクトを作り、新しくSceneGameというスクリプトを作成し追加します。
SceneGame.csusing UnityEngine; using MonobitEngine; public class SceneGame: MonobitEngine.MonoBehaviour { /// <summary>移動方向</summary> private GameObject m_MinePLayerObj; // Start is called before the first frame update void Start() { m_MinePLayerObj = MonobitNetwork.Instantiate("Player", Vector3.zero, Quaternion.identity, 0); } // Update is called once per frame void Update() { } }これでネットワーク越しにプレイヤーの生成が行えました。
2.移動の処理の同期
続いて、移動の同期処理を実装していきます。
とはいったものの、実はMonobitTransformViewを追加していることにより位置の同期は既に行われるようになっています。
ただ、これだけだと自分が操作しているプレイヤー以外の他のプレイヤーが操作しているはずのオブジェクトまで操作できてしまい、思ったような実装にはなりません。
ということで、自分が操作するプレイヤーのみを操作できるようにPlayer.csに処理を追加します。オブジェクト所有権チェック/MonobitEngine.MonobitView.isMine プロパティ
monobitView.isMine生成したオブジェクトが自身で生成したものか、他のルームメンバーがネットワーク越しに生成してきたものかを判別することができます。
MonobitViewコンポーネントを追加し、MonobitEngine.MonoBehaviour
を継承しているもので利用できます。
これを使用して、自身のみが移動操作でオブジェクトを動かせるように変更します。Player.csusing UnityEngine; using MonobitEngine; public class Player : MonobitEngine.MonoBehaviour { /// <summary>移動方向</summary> private Vector3 m_MovementAmount = Vector3.zero; /// <summary>移動速度</summary> private float m_Velocity = 1.0f; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if (!monobitView.isMine){ return; } Move(); } /// <summary>移動</summary> private void Move() { m_MovementAmount.x = Input.GetAxis("Horizontal"); m_MovementAmount.y = Input.GetAxis("Vertical"); transform.localPosition += m_MovementAmount * m_Velocity * Time.deltaTime; } }
Updare()
内の処理をオブジェクトの所有権が自分にあるとき以外は処理させないようにしました。これで移動の同期がきちんとできるようになりました。
3.弾の発射と同期
弾を発射するタイミングを同期させます。
弾の移動同期については、別途弾の記事で説明するため、今回は省きます。ということで弾を飛ばす処理を書いていきます。
c#Player.csusing UnityEngine; using MonobitEngine; public class Player : MonobitEngine.MonoBehaviour { /// <summary>移動方向</summary> private Vector3 m_MovementAmount = Vector3.zero; /// <summary>移動速度</summary> private float m_Velocity = 1.0f; /// <summary>発射間隔</summary> private float m_ShotInterval = 0.1f; /// <summary>発射間隔のカウント</summary> private float m_ShotIntervalCount = 0.0f; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if (!monobitView.isMine){ return; } Move(); Shot(); } /// <summary>移動</summary> private void Move() { m_MovementAmount.x = Input.GetAxis("Horizontal"); m_MovementAmount.y = Input.GetAxis("Vertical"); transform.localPosition += m_MovementAmount * m_Velocity * Time.deltaTime; } /// <summary>弾発射</summary> private void Shot() { if (!Input.GetMouseButton(0)) { m_ShotIntervalCount = 0.0f; return; } m_ShotIntervalCount += Time.deltaTime; if (m_ShotIntervalCount < m_ShotInterval) { return; } m_ShotIntervalCount = 0.0f; Debug.Log("Shot Bullet!!!"); } }マウスの左クリックが押されている間
m_ShotInterval
間隔で弾を発射する処理を追加しました。では、この発射する処理をルームメンバー間で同期させる処理に変更していきます。
RPC(Remote Procedure Call)/送信制御:MonobitEngine.MonobitView.RPC メソッド (1)
この機能を使い、発射タイミングの同期を実装します。
Player.csusing UnityEngine; using MonobitEngine; public class Player : MonobitEngine.MonoBehaviour { /// <summary>移動方向</summary> private Vector3 m_MovementAmount = Vector3.zero; /// <summary>移動速度</summary> private float m_Velocity = 1.0f; /// <summary>発射間隔</summary> private float m_ShotInterval = 0.1f; /// <summary>発射間隔のカウント</summary> private float m_ShotIntervalCount = 0.0f; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { if (!monobitView.isMine){ return; } Move(); Shot(); } /// <summary>移動</summary> private void Move() { m_MovementAmount.x = Input.GetAxis("Horizontal"); m_MovementAmount.y = Input.GetAxis("Vertical"); transform.localPosition += m_MovementAmount * m_Velocity * Time.deltaTime; } /// <summary>弾発射</summary> private void Shot() { if (!Input.GetMouseButton(0)) { m_ShotIntervalCount = 0.0f; return; } m_ShotIntervalCount += Time.deltaTime; if (m_ShotIntervalCount < m_ShotInterval) { return; } m_ShotIntervalCount = 0.0f; /// <summary>自分も含めたルームメンバーに弾の発射を行わせる</summary> monobitView.RPC("RecvShot", MonobitTargets.All, transform.localPosition); } /// <summary>弾発射RPC</summary> [MunRPC] private void RecvShot(Vector3 position) { Debug.Log("Shot Bullet!!! Position(" + position.x + ", " + position.y + ")"); } }
Shot()
内に記述しているmonobitView.RPC("RecvShot", MonobitTargets.All, transform.localPosition);
でルームメンバーに対して、発射のタイミングを通知しています。
発射のタイミングを受け取ったメンバーはprivate void RecvShot(Vector3 position)
が呼ぶようになっています。これで、弾の発射を同期することが出来るようになりました。
4.パラメータの同期
体力やプレイヤーの状態等、ルームメンバー間で共有しておきたい数値を同期させる処理を作っていきます。
今回は、体力、自身のスコア、状態の3つを同期させるように作ります。[Tips] RPCメッセージの送信量・頻度の軽減/Tips(2) : ある特定のオブジェクト(prefab)の情報同期が目的である場合、RPCメッセージを使わず、OnMonobitSerializeView() を使う
こちらを参考に、数値の同期を実装していきます。
OnMonobitSerializeView メソッド
public void OnMonobitSerializeView( MonobitEngine.MonobitStream stream, MonobitEngine.MonobitMessageInfo info )この機能をPlayer.cs内で使用し、体力、スコア、状態をルームメンバー間で共有します。
Player.csusing UnityEngine; using MonobitEngine; /// <summary>プレイヤーの状態</summary> public enum PlayerState { Active = 0, Deactive = 1, Unrivaled = 2, Dead = 3 } /// <summary>プレイヤーの制御</summary> public class Player : MonobitEngine.MonoBehaviour { /// <summary>移動方向</summary> private Vector3 m_MovementAmount = Vector3.zero; /// <summary>移動速度</summary> private float m_Velocity = 1.0f; /// <summary>発射間隔</summary> private float m_ShotInterval = 0.1f; /// <summary>発射間隔のカウント</summary> private float m_ShotIntervalCount = 0.0f; /// <summary>プレイヤーの状態</summary> private PlayerState m_State; /// <summary>体力</summary> private int m_HealthPoint; /// <summary>体力の上限</summary> private int m_MaxHealthPoint = 10; /// <summary>スコア</summary> private int m_Score; // Start is called before the first frame update void Start() { m_State = PlayerState.Deactive; m_HealthPoint = m_MaxHealthPoint; } // Update is called once per frame void Update() { if (!monobitView.isMine){ return; } switch (m_State) { case State.Active: Move(); Shot(); break; case State.Deactive: break; case State.Unrivaled: break; case State.Dead: break; default: break; } } /// <summary>移動</summary> private void Move() { m_MovementAmount.x = Input.GetAxis("Horizontal"); m_MovementAmount.y = Input.GetAxis("Vertical"); transform.localPosition += m_MovementAmount * m_Velocity * Time.deltaTime; } /// <summary>弾発射</summary> private void Shot() { if (!Input.GetMouseButton(0)) { m_ShotIntervalCount = 0.0f; return; } m_ShotIntervalCount += Time.deltaTime; if (m_ShotIntervalCount < m_ShotInterval) { return; } m_ShotIntervalCount = 0.0f; /// <summary>自分も含めたルームメンバーに弾の発射を行わせる</summary> monobitView.RPC("RecvShot", MonobitTargets.All, transform.localPosition); } /// <summary>弾発射RPC</summary> [MunRPC] private void RecvShot(Vector3 position) { Debug.Log("Shot Bullet!!! Position(" + position.x + ", " + position.y + ")"); } /// <summary></summary> /// <param name="stream">MonobitAnimatorViewの送信データ、または受信データのいずれかを提供するパラメータ</param> /// <param name="info">特定のメッセージやRPCの送受信、または更新に関する「送信者、対象オブジェクト、タイムスタンプ」などの情報を保有するパラメータ</param> public void OnMonobitSerializeView(MonobitEngine.MonobitStream stream, MonobitEngine.MonobitMessageInfo info) { if (stream.isWriting) { stream.Enqueue(m_HealthPoint); stream.Enqueue(m_MaxHealthPoint); stream.Enqueue(Score); stream.Enqueue((int)m_State); } else { m_HealthPoint = (int)stream.Dequeue(); m_MaxHealthPoint = (int)stream.Dequeue(); m_Score = (int)stream.Dequeue(); m_State = (State)stream.Dequeue(); } } }単に体力, スコア, 状態の変数を用意し、
OnMonobitSerializeView
内でデータの書き込み/読み込みを行っているだけです。
これで数値を変化させても常に情報を共有してくれます。5.プレイヤーオブジェクトの削除
最後に、自身で生成したプレイヤーオブジェクトの後片付けを行います。
とはいえ、ルームを抜けた際に所持者が居なくなったオブジェクトは自動的に削除されるため、自分が操作するプレイヤーなど、ゲーム中に削除を行わないようなものに関しては削除の処理は実装する必要がありません。
ここでは、任意で削除したい、というときに使用する機能を記載しておこうと思います。ネットワーク越しのオブジェクトの破棄/MonobitEngine.MonobitNetwork.Destroy メソッド(1)
MonobitNetwork.Destroy();この関数に削除したいオブジェクトのmonobitViewもしくはGameObjectを引数に渡してあげることで、ネットワーク越しに生成されたものを含めて削除してくれます。
これで、簡単ではありますが、プレイヤーに必要な処理の同期が実装できたかと思います。
次回はプレイヤーが発射する弾の実装を行おうと思います。
- 投稿日:2019-09-09T11:32:44+09:00
Azure PlayFabでPlayStreamを使ってユーザーのBANを実装する
まえがき
Azure PlayFab を使用すると、ユーザーの操作やステータスの変化が PlayStream イベントとして PlayFab に記録されます。
今回はこの PlayStream をフックしてユーザーの BAN を実装してみます。
シナリオ
ゲーム内通貨の無限増殖バグなどが利用されたことを想定して、所持通貨が 10,000,000 を超えたユーザーをリアルタイムに BAN してみます。
※こんなものを実装するイメージです。手順
1. 新しいルールを作成する
GameManager へログインし、 自動化 > ルール > 新しいルール を押します。
2. 名前、イベントタイプ、条件、アクションを設定する
それぞれの項目を設定します。
名前:
任意のわかりやすい名前を設定します。イベントタイプ:
com.playfab.player_virtual_currency_balance_changed
を選択します。条件:
VirtualCurrencyName
を選択して、チェックしたい通貨の名称を指定します。今回は GO (ゴールドの略)という通貨をチェック対象にします。
さらに AND 条件でVirtualCurrencyBalance
を選択して、 BAN 対象の閾値を指定します。今回は 10,000,000 を閾値にします。アクション:
種類
にプレイヤーをプレイ禁止にする
を指定します。
禁止の理由にはわかりやすい任意のメッセージを入力します。PlayFab 側の設定はこれで完了です。
簡単ですね。3. Unity 側で BAN されている場合のエラーメッセージを表示する
ユーザーが BAN されている状態で PlayFab の API を呼び出した場合、
AccountBanned
というエラーコードが返ってきます。これを利用して例えば雑に IF 文を挟んでエラーメッセージを表示することができます。
var request = new LoginWithCustomIDRequest { CustomId = ApplicationContext.PlayFab.PlayFabDebugUserId, CreateAccount = true }; PlayFabClientAPI.LoginWithCustomID(request, OnLoginSuccess, OnLoginFailure); void OnLoginSuccess(LoginResult result) { // ログイン成功したときの処理 } void OnLoginFailure(PlayFabError error) { // ログイン失敗したときの処理 if (error.Error == PlayFabErrorCode.AccountBanned) { // BAN されているときの処理 } }4. 動作確認
ユーザーに 10,000,000 ゴールドを付与したところ、その瞬間に自動的に BAN されたことが PlayStream モニターで確認できました。
Unity 側でも意図したメッセージが表示されました。
あとがき
PlayFab の PlayStream は強力なので使いこなして行きたいですね。
実績解除やログイン報酬を配ったりもできますし、アウトゲームのロジックはこれにどんどん寄せて行きたいなと思います。
- 投稿日:2019-09-09T09:15:10+09:00
【ReSharper】ReSharper の使い方に関する記事まとめ(75個)
備考
この投稿は自分のブログの記事の転載になります
http://baba-s.hatenablog.com/entry/2019/06/07/090000はじめに
自分のブログで公開した ReSharper の使い方に関する記事を75個まとめました
おすすめ
- 後置テンプレートを使う
- Visual Studio を軽量化する
- Jenkins と ReSharper を使用して静的解析を行う方法
- よく記述するコードをライブテンプレートに登録してコーディングを楽にする方法
Unity
- Unity との連携を有効にする方法
- Unity におけるコードの問題点を見つける
- Unity においてパフォーマンスがあまり良くないコードを削除したり書き換えたりする
- Unity においてパフォーマンスがあまり良くないコードをハイライト表示する
- Unity の SerializeField 属性が適用された変数の命名規則を設定する
- Unity で暗黙的に使用されるキーワードの太字を解除する方法
コードクリーンナップ
- 型指定を var に置換する
- クラスや構造体にアクセス修飾子を自動で付与する
- クラスや構造体のメンバーにアクセス修飾子を自動で付与する
- メンバの修飾子の順番を自動で並べ替える
- 名前付き引数を使用するかどうかを自動で適用する
- 不要な括弧を自動で削除する
- 実行順がわかりづらい処理に自動で括弧を付けて実行順をわかりやすくする
- 一行のステートメントに自動で波括弧を付けたり、
一行にまとめられるステートメントから自動で波括弧を削除したりする- 複数の属性の指定を自動で1つにまとめたり、自動で分割したりする
- 単純なプロパティとバッキングフィールドを自動プロパティに置き換える
- 変数に自動で readonly を適用する
- 自動実装プロパティから不要な setter を自動で削除する
- 関数呼び出しの型指定を自動で削除したり基底クラスに変更したりする
- 組み込み型の記述を自動で C# 型に変更する
- 未使用の using を自動で削除する
- using を自動で region で囲む
- 名前空間付きの呼び出しを自動で using に置き換える
- Formatting Style に従ってコードを自動で整形する
- コードフォーマットする時に名前付き引数を使うかどうかは変更しないようにする
- ReSharper の Code Cleanup をコマンドラインから実行する方法
自動生成
Inspect
- 変数がどこに渡されていくのか追跡する
- 関数がどこから呼び出されてくるのか追跡する
- 関数の引数がどこから渡されてくるのか追跡する
- 関数がどのように関数を呼び出していくのか追跡する
- ソリューションやプロジェクトの問題を一覧で確認する
ファイルレイアウト
ショートカットキー
- bool を反転する
- タイプミスを修正する
- 関数を式形式に変更する
- 行や選択範囲を複製する
- 完全修飾名をコピーする
- 行コメントを切り替える
- 引数の順番を変更する方法
- コードの位置合わせを行う方法
- クリップボードの履歴から貼り付ける
- Ctrl+E, F で Full Cleanup を実行する方法
- オブジェクト初期化子を自動で記述する
- クラスや構造体を新しいファイルに移動する
- Ctrl + Alt + / で選択範囲を一行コメントに切り替えられる
- Ctrl + Shift + / で選択範囲を複数行コメントに切り替えられる
オプション
トラブルシューティング
- 命名規則が適用されない場合
- ReSharper を一時的に無効化する方法
- パラメータヒントを非表示にする方法
- 宣言へ移動する時の挙動を変更する方法
- 名前空間をフォルダ構成に合わせないようにする
- タブキーを押した時の挙動をタブの挿入に戻す方法
- 関数をオーバーライドしたい時のコード補完の方法
- ReSharper をインストールしても正常に動作しなかった時に、
キャッシュを削除したら正常に動作するようになった拡張機能
- 関数を複製できる拡張機能
- 関数の複雑度が簡単に閲覧できるようになる拡張機能
- ツールチップやパラメータ情報に色付けできる拡張機能
- コーディングを楽にするテンプレートを使用できる拡張機能
- ヒープ割り当てが発生しているコードを可視化できる拡張機能
- 関数の引数を改行できる、一行にまとめることができる拡張機能
その他
- 投稿日:2019-09-09T02:57:02+09:00
【Unity】BurstCompilerをJobSystem以外でも使いたい
BurstCompiler(以降、
Burst
と省略して記載)に関する小ネタです。
※BurstCompilerとは?と言う方はこちらを参照
BurstCompilerとは?
UnityにはPackageManagerから取得可能な拡張機能として、BurstCompilerと言うパッケージがあります。
概要を簡単に説明すると、JobSystemで走らせるコードを特定の条件下の時に最適化(SIMD化、一部の計算の精度を調整)してくれるパッケージになります。
※その為に何でもかんでも早くなってくれるわけではない。特定の条件下として挙げられる内容としては主に以下のものが有り、いずれもC#を書く上では厳しい制約となりますが...処理が適していれば劇的なパフォーマンス改善が期待できるものであり、最適化の性質上から特に計算処理とは相性が良いかと思われるので、使えるなら積極的に使っていきたい機能ではあります。
- マネージドオブジェクト(参照型とか)アクセス不可
- static変数アクセス不可
- 副作用のないコード
- ※状態変化のないコード。言わば参照透過性の高いコードとも言い換えられるかも
※「なんで上記の制約があるのか?」について、CEDEC2018の以下の講演にて少し触れられているので興味のある方は御覧ください。
- 【CEDEC2018】CPUを使い切れ! Entity Component System(通称ECS) が切り開く新しいプログラミング
- → 「7. ザ・Burst」の章を参照
設定するだけなら簡単に設定可能
名前に「コンパイラ」とか付いているので小難しそうな印象を受けるかもしれませんが...実際にはそんなことはなく、設定自体は非常に簡単であり、一言で言うと「コンパイル対象のJob構造体やメソッドに
[BurstCompile]
と言う属性を付けるだけ」と言えるかもしれません。Burstの機能としては↑の属性を付ける以外にも計算精度の指定と言った細かいオプションの方も用意されているので、先ずは公式ドキュメントの方をざっくりと目を通してみることをおすすめします。
→ 実装例の他にも内部挙動の話やビルド要件についてなど記載されている。(AndroidならNDKが必要とか)こちらのBurstですが、Jobベースの処理を最適化する機能となるために基本的にはJob以外の処理(例えば通常のMainThreadで行う計算とか)に適用すると言ったことが出来ません。
そこで「何とかBurstを通常のMainThreadで行う計算処理などにも適用できないか?」と思い、調べてみた感じだと、それっぽいやり方を見つけたので備忘録序にメモしていければと思います。※注意点
まだ実戦投入や様々なケースでのテストなどは出来てません。。
ひょっとしたら地雷が埋まっている可能性も無きにしもあらずなので...実戦投入される方はご注意ください。。
(今後問題点など見つけたら随時更新予定)バージョン情報
- Unity
- 2018.4.7f1
- Packages
- Burst 1.1.2
検証環境
- Standalone(Windows) + IL2CPP
- CPU : Intel Core i7-8700K
Burst適用前について
先ずはBurst適用前のコード及び実行速度から載せていきます。
内容としては負荷計測用に用意した「千万回近く適当に演算して値(float3)を返すだけ」のコードになります。(もっと良い例を用意したかったが...思いつかなかった..)
public static float3 Calc() { var vec = new float3(); for (var i = 0; i < 10000000; i++) { var mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); i += 1; mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); i += 1; mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); i += 1; mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); i += 1; mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); } return vec; }上記の
Calc
関数をUI.Button
から実行できるようにして冒頭の検証環境で実行したところ、以下の計測結果が出ました。MainThread上で1803.28ms(大体1.8秒近く)掛かっていることが確認できます。
Burstの適用方法/計測結果について
次に本題であるMainThread上からBurstを適用できるようにしたパターンについて解説していきます。
やり方は調べた範囲だと2つほどありました。
順に解説していきます。
BurstCompiler.CompileFunctionPointer
でコンパイル1つ目に紹介するのは
BurstCompiler.CompileFunctionPointer
と言うAPIを用いてコンパイルした関数ポインタを受け取る方法についてです。先ずは実装コードから載せます。
[BurstCompile] // 必要 public static class Math { // CompileFunctionPointerに渡すデリゲート. delegate void CalcDelegate(ref float3 vec); static CalcDelegate _calcDelegate; [RuntimeInitializeOnLoadMethod] static void Initialize() { // コンパイルした計算処理の関数ポインタを受け取ってデリゲートに保持. var funcPtr = BurstCompiler.CompileFunctionPointer<CalcDelegate>(CalcBurstImpl); _calcDelegate = funcPtr.Invoke; } public static float3 CalcBurst() { var vec = new float3(); _calcDelegate(ref vec); return vec; } // コンパイル対象のメソッド // ※現状は戻り値を返すことが出来ないので、「参照渡し」か「ポインタ渡し」にする必要がある. [BurstCompile] static void CalcBurstImpl(ref float3 vec) => vec = Calc(); // Calcは上述のと同じ public static float3 Calc() { var vec = new float3(); for (var i = 0; i < 10000000; i++) { var mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); i += 1; mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); i += 1; mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); i += 1; mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); i += 1; mat = new float4x4(quaternion.Euler(i, i, i), new float3(i, i, i)); vec = math.transform(mat, vec); } return vec; } }上記の
CalcBurst
と言う関数を呼び出すことでBurstで最適化されたCalc
関数を呼び出すことが可能です。
詳細についてはコメントにも記載してますが、順に解説していきます。コンパイル手順
コンパイルまでの手順を以下に載せます。
[BurstCompile] // 必要 public static class Math { // CompileFunctionPointerに渡すデリゲート. delegate void CalcDelegate(ref float3 vec); static CalcDelegate _calcDelegate; [RuntimeInitializeOnLoadMethod] static void Initialize() { // コンパイルした計算処理の関数ポインタを受け取ってデリゲートに保持. var funcPtr = BurstCompiler.CompileFunctionPointer<CalcDelegate>(CalcBurstImpl); _calcDelegate = funcPtr.Invoke; } // コンパイル対象のメソッド // ※現状は戻り値を返すことが出来ないので、「参照渡し」か「ポインタ渡し」にする必要がある. [BurstCompile] static void CalcBurstImpl(ref float3 vec) => vec = Calc(); // Calcは上述のと同じ肝となるのは表題にもある「
BurstCompiler.CompileFunctionPointer
」と言うAPIです。こちらにデリゲートの型を指定した上で対象の関数を渡すことで、コンパイルした関数ポインタを受け取ることが可能です。
※対象のクラスやメソッドにはBurstCompile
属性を付ける必要があるので注意。
※「関数ポインタ→デリゲート」の変換はCompileFunctionPointer
の実際の戻り値であるFunctionPointer<T>
の中で行われている。ここについては記事後半の「おまけ」章で解説。補足として、現時点ではコンパイル対象の関数は戻り値を返すことが出来ないらしく...返そうとすると以下のエラーが返ってきました。
error: The struct
Unity.Mathematics.float3
cannot be returned by value from an external function in burst. The workaround is to return it by reference or pointer. This restriction will be removed in a future version of burst将来的には制限は無くなるみたい?ですが、現状は「参照渡し」か「ポインタ渡し」を経由する必要があったので、
CalcBurstImpl
と言う関数を経由する形で渡してあります。呼び出しと実行結果
呼び出しとしてはコンパイル済み関数を保持しているデリゲートを呼び出すだけです。
今回の例ではCalcBurst
内部でローカル変数を宣言 → コンパイル済み関数に参照渡しで計算 → 結果を返すと言う流れにしてます。public static float3 CalcBurst() { var vec = new float3(); _calcDelegate(ref vec); return vec; }こちらを
UI.Button
から実行できるようにして冒頭の検証環境で実行したところ、以下の計測結果が出ました。結果としてはMainThread上で551.27(大体0.55秒近く)になりました。
適用前と比べると1252.01ms(大体1.25秒近く)高速化出来ていることが確認できます。Jobとは違い、Profiler上に明示的に
(Burst)
と言った表記は出ていないものの...結果の方から最適化された処理がMainThreadで呼び出されていることが確認できます。Burstを有効にしたJobをMainThreadで実行
2つ目のやり方です。
正直1つ目のやり方で正しく動いてくれれば問題なさそう感ありますが...ちょっとした補足レベルで解説して行ければと思います。詳細について解説していくとこちらも大体はコメントに記載してますが、やっている事としては極端な話「計算処理をBurstを適用したJobで動かすようにした」だけです。
肝となるのは
IJobExtensions.Run
と言うJob向けの拡張メソッドであり、こちらを用いることでJobをScheduleせずに同スレッドで直接実行するようにしてます。後はJobで動かす都合上、戻り値を直接受け取ることが出来ないので、こちらに関してはローカル変数のポインタをJobに渡して実行後に結果を入れると言った形で対応してます。
※故にこのやり方で動かす場合には
Allow unsafe code
を有効にする必要あり。public static unsafe float3 CalcBurst() { // 結果を入れる変数 var ret = new float3(); // Jobから直接戻り値を受け取れないので結果のポインタを渡して入れる var job = new CalcJob() {Result = &ret}; // MainThreadで実行 job.Run(); // IJobExtensions.Run return ret; } [BurstCompile] // Burst適用 unsafe struct CalcJob : IJob { [NativeDisableUnsafePtrRestriction] public float3* Result; public void Execute() { var ret = Calc(); *Result = ret; } }上記の
CalcBurst
関数をUI.Button
から実行できるようにして冒頭の検証環境で実行したところ、以下の計測結果が出ました。結果としてはMainThread上で549.13ms(大体0.55秒近く)になりました。
適用前と比べると1254.15ms(大体1.25秒近く)高速化出来ていることが確認できます。他にもMainThread上で
(Burst)
と付いた処理が走っているので、目的であるMainThread上での実行も出来ているように見受けられます。おまけ
BurstCompiler.CompileFunctionPointer
の中身についてこちらの実装としてはパッケージの中身を見ることで確認可能です。
現時点でのバージョンでは以下のようになってました。BurstCompiler.cs/// <summary> /// Compile the following delegate into a function pointer with burst, only invokable from a burst jobs. /// </summary> /// <typeparam name="T">Type of the delegate of the function pointer</typeparam> /// <param name="delegateMethod">The delegate to compile</param> /// <returns>A function pointer invokable from a burst jobs</returns> public static unsafe FunctionPointer<T> CompileFunctionPointer<T>(T delegateMethod) where T : class { // We have added support for runtime CompileDelegate in 2018.2+ void* function = Compile(delegateMethod); return new FunctionPointer<T>(new IntPtr(function)); }コンパイルしてますね。(見たままの感想)(深そうだったのでこれ以上深追いしてない)
他にも今回は2018.4で動かしているので普通に使えましたが、Unityのバージョンが古いと使えないと言ったようにも見受けられました。
※全部追えてはいないので..ひょっとしたら別名の代替関数があったりするかもしれない。古いバージョンで動作させる方は要チェック。ちなみに戻り値である
FunctionPointer<T>
について、こちらは元からC#にあるクラスではなく、同じくBurst内で定義されているものとなります。
やっている事としては単純であり、関数ポインタ(IntPtr)をGetDelegateForFunctionPointer
でデリゲートに変換する為のサポートクラスのように見受けられました。実装としては以下。
FunctionPointer.csnamespace Unity.Burst { /// <summary> /// Base interface for a function pointer. /// </summary> public interface IFunctionPointer { /// <summary> /// Converts a pointer to a function pointer. /// </summary> /// <param name="ptr">The native pointer.</param> /// <returns>An instance of this interface.</returns> IFunctionPointer FromIntPtr(IntPtr ptr); } /// <summary> /// A function pointer that can be used from a burst jobs. It needs to be compiled through <see cref="BurstCompiler.CompileFunctionPointer{T}"/> /// </summary> /// <typeparam name="T">Type of the delegate of this function pointer</typeparam> public struct FunctionPointer<T> : IFunctionPointer { [NativeDisableUnsafePtrRestriction] private readonly IntPtr _ptr; /// <summary> /// Creates a new instance of this function pointer with the following native pointer. /// </summary> /// <param name="ptr"></param> public FunctionPointer(IntPtr ptr) { _ptr = ptr; } /// <summary> /// Invoke this function pointer. Must be called from a Burst Jobs. /// </summary> public T Invoke => (T)(object)Marshal.GetDelegateForFunctionPointer(_ptr, typeof(T)); IFunctionPointer IFunctionPointer.FromIntPtr(IntPtr ptr) { return new FunctionPointer<T>(ptr); } } }参考/関連サイト
Forum
- Burst 1.1.1 new release
- Scheduling jobs from NOT a MainThread
- Burst and thread safe API outside Unity Jobs system
API
- 投稿日:2019-09-09T00:29:30+09:00
【Unity】謎のアサーション'IsNormalized(dir)'&'IsNormalized(ray.GetDirection())'
バージョン
Unity 2019.1.9f1
はじめに
こんなアサーションに遭遇しました。
Assertion failed on expression: 'IsNormalized(dir)' Assertion failed on expression: 'IsNormalized(ray.GetDirection())'クリックしてもそれ以上の情報はなく、どこに問題があるのかどころか、誰がこのアサーションを吐いているのかさえ分からず。
とりあえず調べてみると公式フォーラムでFlare Layerを切ると消えたという情報が出てきます。
切ってみると確かに出なくなりましたが、このままではレンズフレアが使えない、ということで色々試して回避できそうな方法が分かったので共有します。再現手順
- 空のシーンを作成します。
- Directional LightにLens Flareを追加します。
- Main CameraにFlare Layerを追加します。
ここまではレンズフレアを使うときにやる普通の作業かと思います。
このあと下記の2パターンで確認できました。パターン1(Orthographicカメラ)
- Main CameraのProjectionをOrthographicにします。
- 再生します。
- Main CameraとDirectional LightのPositionを一致させます。
パターン2(Perspectiveカメラ)
- Main CameraのProjectionをPerspectiveにします。
- 再生します。
- Main CameraとDirectional LightのTransformを一致させつつ、PositionとRotationを適当に動かします。 (例えばPosition: -2, -13, -10、Rotation: 77.2, 112.32, 0とか)
原因ははっきりとは分からないのですが、カメラとLens Flareの位置関係によってはレンズフレアの計算ができない状況になるのでしょう。
対処法
このアサーションに遭遇する原因として一番ありそうなのがカメラにLens Flareが付いてしまっているパターン。
この場合、Lens Flareの使い方が間違っていると思われますので、まずは使い方が合っているか確認するのがいいかと思います。
こちらやこちらを参考にさせて頂くと、空のオブジェクトやLightに付けるのが正しいかと思いますので、そのようにしましょう。それでも、再現手順でやったように、カメラとLens Flareの位置が一致する状況があれば発生してしまうことでしょう。
その場合は両者の位置関係を意識したうえで、スクリプトやオブジェクト配置を見直してみるのがいいかと思います。おわりに
すぐに原因箇所が分からないアサーションなので、解決に時間を取られてしまわないよう、一助になれば幸いです。