20210501のUnityに関する記事は7件です。

ShaderGraphでCADライクな断面表示

↓のような表示をするため紆余曲折あったので、共有します 自分のレベルは ・シェーダー言語さっぱり分からない ・Unity歴は1年 ・今回初めてShaderGraphを触った です。このくらいの人向けの記事です(なのでもっといい方法があるかも) データはこちら 目的 ・切断部に面がある(ように見える)断面表示 ・サブメッシュのあるオブジェクトに対応するため、1マテリアルで断面表示をする ・ぴったり重なった面や多少のめり込みに対応する 環境 Unity2019.4.2(おそらくこれ以降も可) 今回はライトの情報を取得したいので、URPです 前知識 ShaderGraphの概要と使い方は↓の動画を見てください 方針決め 「Unity 断面表示」などで検索すると、↓がヒットします これらは深度バッファなどを利用してクリッピングし、その後断面部分を塗っている…ようです。 切断部に面を張っていないのが参考になりました。 しかしこれらは「マスク用」「断面用」のように、複数のシェーダーで組みになっていて、 今回は1つのシェーダーにしたいので、使えませんでした。 また、切断部に色をつけないのであれば、ディソルブシェーダーというジャンルが 断面表示に近い感じです。クリッピングにはアルファを使っているみたいです。 この方法は1つのマテリアルでも使えます。 以上から、クリッピングにはアルファを使い、 切断部を単色塗りつぶしをすることにしました。 試作(Unlit) はじめPBRのライティングを無効にする方法がわからず、 Unlitにライティングを追加することにしました。 試作の過程 (参考1)https://qiita.com/o_s_t/items/c546962bbd9f064c9908 UnlitからPBRを再現している方がいました(すごい)。 これを全部見ていくと、PBRの処理がよく理解できそうです。 (参考2)https://blogs.unity3d.com/jp/2019/07/31/custom-lighting-in-shader-graph-expanding-your-graphs-in-2019/ こちらは公式で、ライトの情報を取得する方法が書いてあります。 これは後で断面のライティングをするために使います。 (参考3)https://noahbannister.blog/2019/09/23/unity-lwrp-pbr-shader-node/ こちらは通常のPBR処理を呼び出しています。 今回はこちらを使用。 このままのコードではエラーが出たため、修正 SHADERGRAPH_PREVIEWを使って、プレビュー用の値を設定してやらないといけないみたいです test.hlsl void LightweightPBR_float (float3 Albedo, float Metallic, float3 Specular, float Smoothness, float Occlusion, float3 Emission, float Alpha, float3 PositionWS, float3 NormalWS, float3 ViewDirectionWS, float FogCoord, float3 VertexLighting, float3 BakedGI, out float4 fragOut) { #if SHADERGRAPH_PREVIEW fragOut = float4 (1, 1, 1, 1); #else InputData inputData; inputData.positionWS = PositionWS; inputData.normalWS = NormalWS; inputData.viewDirectionWS = ViewDirectionWS; inputData.shadowCoord = TransformWorldToShadowCoord(PositionWS);// GetShadowCoord(GetVertexPositionInputs(PositionWS)); inputData.fogCoord = FogCoord; inputData.vertexLighting = VertexLighting; inputData.bakedGI = BakedGI; fragOut = UniversalFragmentPBR(inputData, Albedo, Metallic, Specular, Smoothness, Occlusion, Emission, Alpha); #endif } 結果がこちら おおよそ最終版と同じ見た目ですが、他のオブジェクトからの影を受けられていません。 ↑こちらの記事を参考すれば実現できそうですが、 この辺りでPBRもmetallic、normal、smoothneesを0にし、emissionを指定すればUnlitのようになることがわかり、PBRにまき直しました。 最終版(PBR) やっていることは 1.スクリプトから断面用プレーンのtransformをマテリアルに送る 2.アルファで切断、 3.断面部から見える裏面をmetallic、normal、smoothneesは0にして真っ黒に 4.断面色+偽のライティングをemissionに設定 です。 まず、スクリプトです。 [ExecuteInEditMode]と書くとplay時以外にも実行してくれます。 スクリプトからレンダラー経由でマテリアルの情報を書き換えると インスタンス化(マテリアルがオブジェクトごとに独立)されるので注意。 断面指示.cs using System.Collections; using System.Collections.Generic; using UnityEngine; [ExecuteInEditMode] public class 断面指示 : MonoBehaviour { public Renderer[] レンダラー; void Update() { for (int i=0;i< レンダラー.Length; i++) { Material[] M = レンダラー[i].materials; for(int j = 0; j < M.Length; j++) { M[j].SetVector("_DANMEN_Pos", transform.position); M[j].SetVector("_DANMEN_Vec", transform.up); } レンダラー[i].materials = M; } } } スクリプトからパラメーターを変更したい場合は、Referenceの名前を"_パラメーター"などに変更しておきます。 次にグラフの説明です ①重なったメッシュが汚くなるため、法線内側方向に目立たない範囲で縮める ②通常のアルベドのテクスチャ処理 ③断面部分にライティングをするための処理 いろいろ厳密には違うんでしょうが、自分にはこれが限界でした。 通常だとオブジェクトのノーマルを入れるところに、スクリプトから渡した断面用プレーンのtransformを使ってライティングを行います。 使い勝手をよくするため、断面色をベースカラーにするか、別の色にするか、偽ライティングを有効にするか切り替えできるようにしています。 ライトの情報は↓の記事よりカスタムファンクションを作成しました。 ライティングの結果、オブジェクトの影が落ちています。 GIも使っているので、照り返しがあるとリアルな面になります。 (positionは裏面のものなので、なんとなくそれっぽいだけですが) ただし、他のオブジェクトからの影は落とせないです。 実際にはここに面がないためです(なにか方法があれば教えてください)。 ④通常のノーマル処理 テクスチャがある時とない時でBranchで切り替え。 (もっとスマートな方法があるかも) ⑤通常のオクルージョン処理 あっているはず… ⑥断面位置でカットする処理 スクリプトから渡した断面用プレーンのtransformを使い、プレーンのどちら側にあるかをベクトルの内積を使って求め、アルファを0or1に切り替えます。 ⑦表、裏面(断面部)で切り替える ↑で作ったノードをつなぎます これで完成です まとめ 今回初めてShaderGraphを触りましたが、使いやすかったです。 そもそものシェーダーができる範囲がとても広く、見た目だけでなくもはや機能のような 物を作れると分かり勉強になりました。 また今回Qiitaで記事を書くのが初めてなんですが、そこそこ時間がかかり、 丁寧な記事を書く先人に一層感謝しました。 以上です。 ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity/ShaderGraph】CADライクな断面表示

↓のような表示をするため紆余曲折あったので、共有します 自分のレベルは ・シェーダー言語さっぱり分からない ・Unity歴は1年 ・今回初めてShaderGraphを触った です。このくらいの人向けの記事です(なのでもっといい方法があるかも) データはこちら 目的 ・切断部に面がある(ように見える)断面表示 ・サブメッシュのあるオブジェクトに対応するため、1シェーダーで断面表示をする ・ぴったり重なった面や多少のめり込みに対応する 環境 Unity2019.4.2(おそらくこれ以降も可) 今回はライトの情報を取得したいので、URPです 前知識 ShaderGraphの概要と使い方は↓の動画を見てください 方針決め 「Unity 断面表示」などで検索すると、↓がヒットします これらは深度バッファなどを利用してクリッピングし、その後断面部分を塗っている…ようです。 切断部に面を張っていないのが参考になりました。 しかしこれらは「マスク用」「断面用」のように、複数のシェーダーで組みになっていて、 今回は1つのシェーダーにしたいので、使えませんでした。 また、切断部に色をつけないのであれば、ディソルブシェーダーというジャンルが 断面表示に近い感じです。クリッピングにはアルファを使っているみたいです。 この方法は1つのマテリアルでも使えます。 以上から、クリッピングにはアルファを使い、 切断部を単色塗りつぶしをすることにしました。 試作(Unlit) はじめPBRのライティングを無効にする方法がわからず、 Unlitにライティングを追加することにしました。 試作の過程 (参考1)https://qiita.com/o_s_t/items/c546962bbd9f064c9908 UnlitからPBRを再現している方がいました(すごい)。 これを全部見ていくと、PBRの処理がよく理解できそうです。 (参考2)https://blogs.unity3d.com/jp/2019/07/31/custom-lighting-in-shader-graph-expanding-your-graphs-in-2019/ こちらは公式で、ライトの情報を取得する方法が書いてあります。 これは後で断面のライティングをするために使います。 (参考3)https://noahbannister.blog/2019/09/23/unity-lwrp-pbr-shader-node/ こちらは通常のPBR処理を呼び出しています。 今回はこちらを使用。 このままのコードではエラーが出たため、修正 SHADERGRAPH_PREVIEWを使って、プレビュー用の値を設定してやらないといけないみたいです test.hlsl void LightweightPBR_float (float3 Albedo, float Metallic, float3 Specular, float Smoothness, float Occlusion, float3 Emission, float Alpha, float3 PositionWS, float3 NormalWS, float3 ViewDirectionWS, float FogCoord, float3 VertexLighting, float3 BakedGI, out float4 fragOut) { #if SHADERGRAPH_PREVIEW fragOut = float4 (1, 1, 1, 1); #else InputData inputData; inputData.positionWS = PositionWS; inputData.normalWS = NormalWS; inputData.viewDirectionWS = ViewDirectionWS; inputData.shadowCoord = TransformWorldToShadowCoord(PositionWS);// GetShadowCoord(GetVertexPositionInputs(PositionWS)); inputData.fogCoord = FogCoord; inputData.vertexLighting = VertexLighting; inputData.bakedGI = BakedGI; fragOut = UniversalFragmentPBR(inputData, Albedo, Metallic, Specular, Smoothness, Occlusion, Emission, Alpha); #endif } 結果がこちら おおよそ最終版と同じ見た目ですが、他のオブジェクトからの影を受けられていません。 ↑こちらの記事を参考すれば実現できそうですが、 この辺りでPBRもmetallic、normal、smoothneesを0にし、emissionを指定すればUnlitのようになることがわかり、PBRにまき直しました。 最終版(PBR) やっていることは 1.スクリプトから断面用プレーンのtransformをマテリアルに送る 2.アルファで切断、 3.断面部から見える裏面をmetallic、normal、smoothneesは0にして真っ黒に 4.断面色+偽のライティングをemissionに設定 です。 まず、スクリプトです。 [ExecuteInEditMode]と書くとplay時以外にも実行してくれます。 スクリプトからレンダラー経由でマテリアルの情報を書き換えると インスタンス化(マテリアルがオブジェクトごとに独立)されるので注意。 断面指示.cs using System.Collections; using System.Collections.Generic; using UnityEngine; [ExecuteInEditMode] public class 断面指示 : MonoBehaviour { public Renderer[] レンダラー; void Update() { for (int i=0;i< レンダラー.Length; i++) { Material[] M = レンダラー[i].materials; for(int j = 0; j < M.Length; j++) { M[j].SetVector("_DANMEN_Pos", transform.position); M[j].SetVector("_DANMEN_Vec", transform.up); } レンダラー[i].materials = M; } } } スクリプトからパラメーターを変更したい場合は、Referenceの名前を"_パラメーター"などに変更しておきます。 次にグラフの説明です ①重なったメッシュが汚くなるため、法線内側方向に目立たない範囲で縮める ②通常のアルベドのテクスチャ処理 ③断面部分にライティングをするための処理 いろいろ厳密には違うんでしょうが、自分にはこれが限界でした。 通常だとオブジェクトのノーマルを入れるところに、スクリプトから渡した断面用プレーンのtransformを使ってライティングを行います。 使い勝手をよくするため、断面色をベースカラーにするか、別の色にするか、偽ライティングを有効にするか切り替えできるようにしています。 ライトの情報は↓の記事よりカスタムファンクションを作成しました。 ライティングの結果、オブジェクトの影が落ちています。 GIも使っているので、照り返しがあるとリアルな面になります。 (positionは裏面のものなので、なんとなくそれっぽいだけですが) ただし、他のオブジェクトからの影は落とせないです。 実際にはここに面がないためです(なにか方法があれば教えてください)。 ④通常のノーマル処理 テクスチャがある時とない時でBranchで切り替え。 (もっとスマートな方法があるかも) ⑤通常のオクルージョン処理 あっているはず… ⑥断面位置でカットする処理 スクリプトから渡した断面用プレーンのtransformを使い、プレーンのどちら側にあるかをベクトルの内積を使って求め、アルファを0or1に切り替えます。 ⑦表、裏面(断面部)で切り替える ↑で作ったノードをつなぎます これで完成です まとめ 今回初めてShaderGraphを触りましたが、使いやすかったです。 そもそものシェーダーができる範囲がとても広く、見た目だけでなくもはや機能のような 物を作れると分かり勉強になりました。 また今回Qiitaで記事を書くのが初めてなんですが、そこそこ時間がかかり、 丁寧な記事を書く先人に一層感謝しました。 以上です。 ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】iOSネイティブプラグイン開発を完全に理解する - Objective-C++について

本編「【Unity】iOSネイティブプラグイン開発を完全に理解する」の付録記事です。 記事中での用語や略称についてはそのまま本編に倣う形で記載していきます。 「Objective-Cは何となく聞いたことあるけど、Objective-C++は聞いたこと無い」「これらの違いは何?」と思う方も居るかもしれないので簡単に補足します。1 ざっくりと言ってしまうと「Objective-CとC++を混在させることが出来る言語」になります。 参考リンク Objective-C++ 取扱説明書 〜始め方から使い方、注意点まで〜【完全保存版】 Objective-C++ - Wikipedia Objective-Cとの違い ObjCはあくまで「C言語をベースに拡張した言語 (言語仕様的にはC言語の完全上位互換な言語)」であり、ObjCからは「C++の構文や機能、C++で書かれた既存のライブラリ」などを利用することが出来ません。 一方でObjC++の方は「C++の構文や機能、C++で書かれた既存のライブラリ」を利用することが可能となり、これによって「C++で書かれた既存のライブラリ」と言った資産を使い回すことができるようになります。2 導入方法 Xcode上から「ObjCのソースファイルの拡張子(.m)」を「ObjC++のソースファイルの拡張子(.mm)」に変換するだけで自動的にObjC++として認識されるようになります。 (Xcode上の○○Settingsをいじって〜的な操作は必要無く、本当にこれだけ) Unity上でも同様に、拡張子が.mmのソースファイルをAssets以下にインポートすると「ObjC++で書かれたネイティブプラグイン」として扱われるようになります。 P/InvokeでObjC++(.mm)のコードを呼び出すならextern "C"を指定して外部宣言する必要がある C#からP/Invokeで「拡張子が.mmのソースに実装されているObjC++のコード」を呼び出すなら、P/Invokeで呼び出される関数を外部宣言する際にextern "C"を指定する必要があります。 (言い方を変えるとC言語のリンケージを指定する必要がある) サンプルコードを載せておくと以下のようになります。 (この際には__cplusplusマクロも用いてC++コンパイラから読まれたときだけ有効にしている) Example.mm // MARK:- extern "C" (Cリンケージで宣言) #ifdef __cplusplus extern "C" { #endif // NOTE: この関数が実際にUnity(C#)から呼び出される int printHelloWorld() { return [Example printHelloWorld]; } #ifdef __cplusplus } #endif 逆にObjC(.m)ならextern "C"は不要 逆に「拡張子が.mのソースに実装されているObjCのコード」を呼び出すならこれを行う必要はなく、外部宣言時に以下のように実装することでC#からP/Invokeで呼び出せるようになります。 Example.m // NOTE: この関数が実際にUnity(C#)から呼び出される // 拡張子が`.m`のソースなら、これだけでC#から呼び出し可能 extern int printHelloWorld() { return [Example printHelloWorld]; } なぜObjC(.m)だと不要なのか? これらの理由について話すと、C++のコンパイラで「名前マングリング」と言う処理が行われているのが原因であり、C言語ベースのObjCだと名前マングリングが行われない為にextern "C"を付けなくても呼び出せるようになってます。 もっと具体的に「何が起こっているのか?」の詳細については以下の記事が参考になります。 C++のマングルとextern "C" { CとC++が混在したプログラムでの注意点 ちなみに自分はネイティブプラグイン開発時に初めてObjective-C++の名前を知ってこの疑問を抱きました。 ↩ 現にObjC++の用途の一つとして、「C++のライブラリをObjCからアクセスする為のラッパー記述」などが挙げられます ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity / Anti-Cheat Toolkit】セーブデータを暗号化しよう!

今回はプラグインを使ってセーブデータを暗号化しましたので利用したアセットについてお伝えしたいと思います。 セーブデータの改ざんなどがご心配な方の参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】iOSネイティブプラグイン開発を完全に理解する - Xcodeの設定を自動化する

本編「【Unity】iOSネイティブプラグイン開発を完全に理解する」の付録記事です。 記事中での用語や略称についてはそのまま本編に倣う形で記載していきます。 Unityのビルド結果にあるプロジェクトファイル(.xcodeproj)を開き、Xcode上でターゲット1を選択してからBuild Settingsを開けば名前の通りにアプリケーションのビルドに関する設定を行えます。 例えば以下のスクリーンショットでは例として「Swiftのバージョン指定」を選択してます。 こちらの設定はXcode上から手動で変更する以外にも、Unity上からビルド毎に自動で設定を変更することも可能であり、今回はそのやり方について解説していきます。 ※ 前提としてUnityの場合には手動で変える運用はオススメできない 設定項目の変更状態はXcodeのプロジェクトファイル(.xcodeproj)に紐付かれることになりますが、Unityと言ったプロジェクトファイルを自動生成する様な環境だと、例えばReplace相応のビルドを行った際にはビルド設定が適用された.xcodeprojも消えてしまうため、都度手動で再設定を行う必要が出てきます。 手動設定は運用を踏まえると、ビルドの自動化との相性問題もあれば、そもそもヒューマンエラーに繋がる懸念も出てきます。 ※逆にビルドエラー解決などの為に「一瞬手動で変えてみて試す」と言ったのは全然有りだと思います。ただ、最終的にはUnity上から自動で設定を適用するようにした方が良いかもです。 [PostProcessBuild]を利用して設定を自動化 Unityからプロジェクトファイルにある設定を書き換えるには、PostProcessBuildAttributeと言う属性を利用したEditor拡張を実装します。 こちらを利用するとビルドが完了したタイミングで属性を付けたメソッドを呼び出すことが出来るようになるため、ビルド完了後にXcodeプロジェクトファイルにアクセスして内容を書き換えると言ったアプローチを取ることが出来るようになります。 XcodePostProcess.cs #if UNITY_IOS using System.IO; using UnityEditor; using UnityEditor.Callbacks; using UnityEditor.iOS.Xcode; namespace MinimumExample.Editor { static class XcodePostProcess { /// <summary> /// ビルド完了後に呼び出される /// </summary> /// <param name="target">ビルドターゲット</param> /// <param name="path">ビルド結果のパス</param> [PostProcessBuild] static void OnPostProcessBuild(BuildTarget target, string path) { // ビルドターゲットがiOSなら処理 if (target != BuildTarget.iOS) return; // TODO: ここでビルド結果に対して手を加えることが出来る } } } #endif PBXProjectを利用してXcodeのプロジェクトファイルを書き換える Unityからプロジェクトファイルの内容を書き換えるにはPBXProjectと言うiOS(と言うよりはXcode)向けのAPIを利用します。 簡単な例として「Swiftバージョンを5.0に設定する」例を取り上げつつ、以下の要点について順に解説していきます。 A. ビルド結果のパスからPBXProjectを読み込んで、終わったら変更結果で上書き B. 設定を適用するターゲットのGUIDを取得 C. 設定変更の適用 XcodePostProcess.cs /// <summary> /// Swiftを実装するにあたって必要な設定を自動で適用する /// </summary> /// <param name="target">ビルドターゲット</param> /// <param name="path">ビルド結果のパス</param> [PostProcessBuild] static void OnPostProcessBuild(BuildTarget target, string path) { if (target != BuildTarget.iOS) return; // A. ビルド結果のパスからPBXProjectを読み込み var projectPath = PBXProject.GetPBXProjectPath(path); var project = new PBXProject(); project.ReadFromString(File.ReadAllText(projectPath));        // B. 設定を適用するターゲットのGUIDを取得 // 2019.3からは`UnityFramework`に分離しているので、targetGuidはこちらを指定する必要がある。 var targetGuid = project.GetUnityFrameworkTargetGuid(); // C. 設定変更の適用 (Swiftバージョンの設定) project.SetBuildProperty(targetGuid, "SWIFT_VERSION", "5.0"); // A. 変更結果で上書き File.WriteAllText(projectPath, project.WriteToString()); } 【補足】 そもそもPBXProjectとは? PBXProjectはXcodeプロジェクトファイルの中に含まれる設定ファイル的なものであり、主にプロジェクト内にあるファイルの定義/フォルダ階層構造/ビルド設定と言った要素を持ちます。 ビルド結果にある.xcodeprojを選択したら「右クリック -> パッケージの内容を表示」を選択することで、Finder上からアクセスすることが出来ます。(以下にあるproject.pbxprojが該当) [XXX.xcodeproj] L project.pbxproj L project.wxworkspace L [xcshareddata] ちなみにproject.pbxproj自体はテキスト形式なのでテキストエディタで開いて編集することも可能です。 ■ A. ビルド結果のパスからPBXProjectを読み込んで、終わったら変更結果で上書き PostProcessBuildから呼び出されるメソッドはパラメータとして「ビルド結果のパス」を受け取ることが出来るので、こちらをPBXProject.GetPBXProjectPathに渡すことで実際のPBXProjectのパスを取得します。 あとは取得したパスからPBXProjectを文字列として読み込み、それをReadFromStringに渡すことで初期化を完了させます。 XcodePostProcess.cs /// <param name="target">ビルドターゲット</param> /// <param name="path">ビルド結果のパス</param> [PostProcessBuild] static void OnPostProcessBuild(BuildTarget target, string path) { if (target != BuildTarget.iOS) return; // A. ビルド結果のパスからPBXProjectを読み込み var projectPath = PBXProject.GetPBXProjectPath(path); var project = new PBXProject(); project.ReadFromString(File.ReadAllText(projectPath)); そして後述する諸々の設定の適用が完了したら、上記で取得したPBXProjectのパスに対して結果を書き込みます。 (先行して書いてますが、これ自体は一番最後のプロセスになります) XcodePostProcess.cs // A. 変更結果で上書き File.WriteAllText(projectPath, project.WriteToString()); ※この例ではReadFromStringとWriteToStringで読み書きしましたが、他にも関連するAPIとしては以下のようなものが存在します。 PBXProject.ReadFromStream PBXProject.WriteToStream PBXProject.ReadFromFile PBXProject.WriteToFile ■ B. 設定を適用するターゲットのGUIDを取得 Unity2019.3以降からはPBXProject.GetUnityFrameworkTargetGuidを実行してUnityFrameworkと言うターゲットのGUIDを持っておきます。 GUIDの用途については後述します。 XcodePostProcess.cs // B. 設定を適用するターゲットのGUIDを取得 // 2019.3からは`UnityFramework`に分離しているので、targetGuidはこちらを指定する必要がある。 var targetGuid = project.GetUnityFrameworkTargetGuid(); 【補足】 そもそも"ターゲット"とは? ターゲットとはXcodeプロジェクトにある「ビルド設定のまとまり」みたいなものであり、それぞれが独自の設定項目(アプリ名/ビルド設定/Bundle Identifier)などを持ちます。 Unityの場合には予め必要なものが設定されており、Xcodeの画面で言うと以下の①を選択したら表示される②の項目がターゲットになります。 2019.3からはアプリ本体側の機能がUnityFrameworkに集約されているので、PBXProject.GetUnityFrameworkTargetGuidからこのUnityFrameworkのGUIDを取得する必要があります。 もっと具体的に「それぞれのターゲットに何がどう含まれているのか?」については以下の公式ドキュメントのProject Targetsを御覧ください。 Structure of a Unity Xcode Project 【補足】 Unity2019.3前後ではプラグインに関する設定が変わってくるので注意 Unity 2019.3から新機能として「Unity as a Library」が入った影響で、iOS向けビルド後のXcodeプロジェクトファイル(.xcodeproj)の構成に大きく変更が掛かりました。 この記事中では2019.4をターゲットとしているので、基本的には2019.3以降の書き方で解説をしてますが、2018系統などを利用する場合にはこれらの設定が変わってくることについては念頭に置いておく必要があります。 詳細は以下の記事にて纏めているので、こちらを御覧ください。 【Unity】iOSネイティブプラグインをSwiftで実装する際には、2019.3前後で設定方法が変わる ■ C. 設定変更の適用 残るは設定変更の適用です。 今回の題材である「Swiftバージョンを5.0に設定する」の場合には、PBXProject.SetBuildPropertyを利用します。 第一引数にはターゲットのGUIDを指定 → B.の項目で取得したUnityFrameworkのGUIDを指定します 第二引数にはビルドプロパティの名前を指定 第三引数にはプロパティに設定したい値を指定 → 次で詳細に触れます XcodePostProcess.cs // C. 設定変更の適用 (Swiftバージョンの設定) project.SetBuildProperty(targetGuid, "SWIFT_VERSION", "5.0"); ビルドプロパティの名前について ここで指定するプロパティ名はXcode上から確認することが出来ます。 先ずはTARGETSからUnityFrameworkを選択し、[Build Settings]を開いたら設定したい項目を選択します。 (今回で言うとSwift Language Version) 選択したら以下のスクリーンショットの右上辺りにある①を選択してQuick Helpを表示し、②に記載されている文字列をUnity側で指定します。 → この例で言うとSWUFT_VERSIONが該当する文字列 他にもXcode Build Settingsと言うサイトには一通りのビルドプロパティや初期値、中には簡単な解説が記載されているので覚えておくと便利です。 Xcode Build Settings プロパティに設定したい値について 引数に渡す値は文字列である必要があるために"5.0"を渡してます。 少し余談にはなりますが、もしValueに何を渡せばよいのか分からない場合には、プロジェクトファイルに含まれるproject.pbxprojをテキストエディタで開き、該当するビルドプロパティで検索してやることで設定値を割り出せたりします。 例えば"SWIFT_VERSION"で検索した場合には↓の様に5.0と言った値が設定されているので、Unity側で渡すパラメータもこちらに準拠させればOKです。 project.pbxproj buildSettings = { (省略) SWIFT_VERSION = 5.0; (省略) }; その他設定項目 Swiftのバージョン指定を例にビルド設定の変更方法について解説してきましたが、他にも以下のような項目も設定可能です。 Frameworkの追加 Info.plistの設定 これらの設定例については以下のサイトが参考になります。 UnityでXcodeの設定を自動化する方法まとめ 設定の競合に注意 [PostProcessBuild]を利用した設定の自動化ですが、こちらの処理自体はプロジェクト中に複数実装することが出来ます。 もし複数箇所にてPBXProjectの書き換えを行った場合には、随時呼び出し順に従って設定が書き換えられていく事になりますが...注意しないと同一設定の書き換えが発生して競合するなんてことが起こり得ます。 同一設定は後から書き換えられたほうが優先される 例えば以下のXcodePostProcess1.csとXcodePostProcess2.csと言うソースがあったとして、先に呼び出される想定のXcodePostProcess1.csでは「SWIFT_VERSIONを5.0」に書き換えるとします。 そして次に呼び出される想定のXcodePostProcess2.csにて「SWIFT_VERSIONを4.0」に書き換えたとしたら、最終的なビルド結果は「SWIFT_VERSION : 4.0」が適用されることになります。 XcodePostProcess1.cs (クリックで展開) XcodePostProcess1.cs // NOTE: 属性の引数に呼び出し順を指定可能 [PostProcessBuild(1)] static void OnPostProcessBuild(BuildTarget target, string path) { if (target != BuildTarget.iOS) return; var projectPath = PBXProject.GetPBXProjectPath(path); var project = new PBXProject(); project.ReadFromString(File.ReadAllText(projectPath)); var targetGuid = project.GetUnityFrameworkTargetGuid(); // SWIFT_VERSIONを`5.0`に書き換え project.SetBuildProperty(targetGuid, "SWIFT_VERSION", "5.0"); File.WriteAllText(projectPath, project.WriteToString()); } XcodePostProcess2.cs (クリックで展開) XcodePostProcess2.cs [PostProcessBuild(2)] static void OnPostProcessBuild(BuildTarget target, string path) { if (target != BuildTarget.iOS) return; var projectPath = PBXProject.GetPBXProjectPath(path); var project = new PBXProject(); project.ReadFromString(File.ReadAllText(projectPath)); var targetGuid = project.GetUnityFrameworkTargetGuid(); // SWIFT_VERSIONを`4.0`に書き換え project.SetBuildProperty(targetGuid, "SWIFT_VERSION", "4.0"); File.WriteAllText(projectPath, project.WriteToString()); } 汎用的なプラグインを実装する際には、書き換えに注意すること もし色んなプロジェクトで使える想定の汎用的なプラグインを実装するとしたら、上記のような性質があるのでPBXProjectの書き換えには注意する必要があります。 (「導入したプラグイン側で意図せずにPBXProjectを書き換えてしまった」、若しくは「導入先のプロジェクトで期待する設定が書き換えられてしまった」なんてことが起こり得るので) もしプラグインの都合でPBXProjectを書き換える必要性が出てきた場合には、README辺りに「PBXProjectのこの設定を書き換えます」 or 「導入するならPBXProjectのこの設定を書き換えてください」と明記するなりした方が良いかもしれません。 ターゲットについては後述します ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】iOSネイティブプラグイン開発を完全に理解する - ネイティブプラグインを実装するには

本編「【Unity】iOSネイティブプラグイン開発を完全に理解する」の付録記事です。 記事中での用語や略称についてはそのまま本編に倣う形で記載していきます。 ここでは私がネイティブプラグインを実装する際によくやる手法/補足について解説します。 あくまで一例として参考にして頂けると幸いです。 この記事の中では「Xcode上から普通にプロジェクトを作成して開発するiOS向けのアプリ」のことを便宜的にネイティブアプリとして表記していきます。 TL;DR iOS向けのネイティブプラグインを作るなら、新規でネイティブアプリ用のプロジェクトを立ち上げて、そこで開発/動作確認すると効率が良い 新規でネイティブアプリ用のプロジェクトを作る際にはUIKitベースがオススメ プラグイン向けに実装したネイティブコードの手直しはUnityのiOSビルド結果であるXcodeプロジェクト上からも可能 Symlink Unity librariesの設定の有無に注意 プラグインとして導入するネイティブコードは別ソースに分けた上で独立性を高く実装 Unityとネイティブアプリのどちらからでも呼び出しやすいように プラグインとして使うネイティブコードはネイティブアプリのプロジェクト上で実装したほうが効率が良い 最終的にUnityに組み込む物と言えども、プラグインとして使うネイティブコードはネイティブアプリのプロジェクト上で実装したほうが圧倒的に効率が良いです。 → Unityが一切関与しないので、「ビルドが通るかどうかのチェック」や「実機動作確認」を高速に行うことが可能。 具体的な流れとしては以下のようになります。 Xcode上から新規でネイティブアプリ用のプロジェクトを立ち上げる 1で立ち上げたプロジェクト上からプラグインとして使うネイティブコードを実装 + 実機動作確認 2が完成したらソースをUnity上にインポート 実装/動作確認用のネイティブアプリは簡単なもので良い ネイティブアプリのプロジェクト上で実装/動作確認を行うには、ある程度のネイティブアプリ開発に関する知見が必要となってきますが、先ずは「ボタンを1つ配置してそこからプラグインとして利用するネイティブコードの処理を呼び出す」と言った簡易的なものでも十分だと思います。 この記事中ではネイティブアプリ開発に関する初歩的なところまでは触れませんが、先ずは公式資料なり初心者向けの書籍/サイトなりを参照して「ボタンを一つ配置してそこから処理を呼び出す」ところまでをキャッチアップしてみることをオススメします。 【補足】 Storyboard or SwiftUI? Xcodeから新規プロジェクト作成時に出てくる以下の画面にて、Interfaceと言うドロップダウンを選択するとStoryboardかSwiftUIかのどちらかを選択する事になりますが、ここではStoryboardを選択することをオススメします。 このStoryboardと言うのはSwiftUIが登場する以前からある従来の形式であり、更にそれらを包括する従来のUIフレームワーク全般のことをUIKitと言うのですが、ザックリと言ってしまえば現時点のUnity iOSビルドで出力されるXcodeプロジェクトはその従来の形式(UIKit)が使われているので、UI周りに関するプラグインを書く際には都合が良くなります。 この点についてはサンプルプロジェクトの解説にて再度補足します。 Unityにインポートしたネイティブコードの手直しはビルド結果のXcodeプロジェクト上からも可能 Unityにインポートしたネイティブコードはビルド結果のXcodeプロジェクトにそのままの形で含まれます。 → 場所としてはLibraries以下にAssets以下のフォルダ構成そのままの形で入る。 (以下は本編中にあるMinimumExampleの例) なので、もしネイティブコードの手直しが必要になったり、決め打ちの値を調整したくなったときにはUnityのビルド結果にあるネイティブコードを直接書き換えることで効率よくビルド/実機での動作確認を行えると言ったテクニックがあります。 【補足】 Symlink Unity librariesについての補足 Build SettingsにあるSymlink Unity librariesを有効にしてビルドすると、ビルド結果のXcodeプロジェクトに含まれるネイティブコードはシンボリックリンクとなるために、Xcodeで編集した際にはAssets以下にあるコードにもそのまま変更が適用されることになります。 逆にこちらのチェックをオフにした際にはネイティブコードはコピーされたものが含まれるので、Xcodeで編集した際にはAssets以下にあるコードにもそのまま適用されないので注意する必要があります。 その他、Symlink Unity librariesの詳細については以下をご覧ください。 参考リンク iOS build settings → Symlink Unity librariesを参照 iOSのビルド設定の「Symlink Unity libraries」の意味 サンプルプロジェクト ここまでの流れに加え、幾つかの要点を補足するためのサンプルを用意しました。 内容としては「UnityのInputFieldから入力した文字列をネイティブのシェアUIを開いてシェアする」アプリとなります。 サンプルは「plugin-development-objc++」ブランチにて管理しており、以下2点のプロジェクトを含んでます。 Unityプロジェクト プラグイン開発用のネイティブアプリプロジェクト → XcodeProjects/PluginDevelopment-Example/PluginDevelopment-Example.xcodeproj サンプルアプリのイメージはこちら (クリックで展開) ネイティブアプリのサンプル ※ネイティブアプリ側はあくまで動作確認用なのでシェアUIに渡す文字列は固定です。 Unityのサンプル このサンプルのトピック このサンプルでは主に以下のトピックについて話していきます。 プラグインとして導入するネイティブコードは独立性を高くして実装 → Unity/ネイティブアプリの両方から呼び出しやすい設計に UIKitに関連するプラグインを実装する際の注意点 求める予備知識 あとはiOSネイティブアプリ開発周りの予備知識としては、以下の範囲まではキャッチアップ済みであるという事を前提に解説していきます。 Storyboard & Objective-Cベース1の新規プロジェクト作成 新規ソースの追加 Storyboard上にボタンを一つ配置して、押されたときの処理の呼び出しまで実装 ■ プラグインとして導入するネイティブコードは独立性を高くして実装 プラグインとして導入するネイティブコードは別ソースに分けた上で、独立性を高く実装しておくと取り回しがよくなります。 早い話「ネイティブアプリにベッタリ依存するような書き方は避けて剥がしやすくする」と言うイメージですが、具体例について次で解説していきます。 ※解説の対象となる該当ソース全体のリンクはこちら 2 NativeShare.mm NativeShare.h ■ Unity/ネイティブアプリの両方から呼び出しやすい設計に もう少し具体的な内容に踏み込むと、シェアUIの表示を行うNativeShareクラスは以下のような実装となっており、なるべくネイティブアプリの状態や呼び出し元のViewに依存しないような作りにしてあります。 今回主に依存性が発生しそうなポイントとしてはshareTextで呼び出されているUIActivityViewControllerであり、こちらを表示する際には自身のUIViewControllerを要求されますが、そちらを引数化することでUnity/ネイティブアプリの両方から呼び出しやすくしてます。 NativeShare.mm // MARK:- implementation (クラスの実装部) @implementation NativeShare + (void)shareText:(NSString*)text viewController:(UIViewController*)vc { // NOTE: UIActivityViewControllerでシェア // ref: https://developer.apple.com/documentation/uikit/uiactivityviewcontroller?language=objc NSArray* array = @[text]; UIActivityViewController* avc = [[UIActivityViewController alloc] initWithActivityItems:array applicationActivities:nil]; [vc presentViewController:avc animated:TRUE completion:nil]; } @end 具体的な呼び出し箇所の実装及びポイントとしては以下のようになります。 ネイティブアプリからの呼び出し ViewControllerからボタン押下時にネイティブプラグイン向けのクラスとして実装しているNativeShareのメソッドを直接呼び出すようにしてます。 ポイントとしては、C#から呼び出される想定のP/Invoke用の外部宣言関数はここでは呼び出しません。 ViewController上からの呼び出しとなるので、shareTextの第二引数にはそのままselfを渡してます。 XcodeProjects/PluginDevelopment-Example/PluginDevelopment-Example/ViewController.m // ボタン押下で呼び出される処理 - (void)tapButton:(UIButton*)button { // 渡す文字列 NSString* text = @"ネイティブアプリからの呼び出し"; // Unity向けのネイティブプラグインを呼び出し [NativeShare shareText:text viewController:self]; } ちなみに外部宣言関数の方は以下のように本来呼び出す想定のUnityGetGLViewController()をコメントアウトし、代わりにダミー変数を渡すことでコンパイルだけ通るような形にしてます。 意図についてはコメントに記載してますが、改めて後述します。 XcodeProjects/PluginDevelopment-Example/PluginDevelopment-Example/ViewController.m #ifdef __cplusplus extern "C" { #endif // NOTE: C#から渡される文字列はcharのポインタ型として渡される void shareText(char* textPtr) { // NSStringへの変換 NSString* text = [NSString stringWithCString:textPtr encoding:NSUTF8StringEncoding]; // NOTE: Unity上からViewControllerを取得するには`UnityGetGLViewController()`を使う // ※これ自体はUnityがiOSビルドで出力したプロジェクト上からじゃないと呼び出せないので、ここではコンパイルを通すために一時的にコメントアウト //UIViewController* vc = UnityGetGLViewController(); UIViewController* vc = nil; [NativeShare shareText:text viewController:vc]; } #ifdef __cplusplus } #endif 【補足】 ネイティブコードをUnityにインポートする際の変更点 上記のNativeShare一式をUnityに持っていく際にはそのままのインポートはせずに、事前に以下の変更を加えててからインポートしてます。 ヘッダーファイル(.h)をソースファイル(.mm)にマージ 必須ではないが、個人的にマージした方が1ソースで済むために管理しやすいからと言う理由でやっている P/Invokeで呼び出される外部宣言関数内にて、Unityが持つViewControllerを渡すようにする 詳細は次で解説 Unity側にインポートしたネイティブコード全体としては以下のようになります。 NativeShare.mm Unityからの呼び出し Unityからの呼び出しは今まで通りにP/Invoke経由となるので、外部宣言している関数から呼び出されます。 ポイントとしては、Unity上からViewControllerを取得するにはUnityGetGLViewController()を呼び出すことで取得することが出来ます。 このUnityGetGLViewController()はUnityがiOSビルドで出力するプロジェクト一式に含まれるUnityAppController.mmと言うソースコードにて外部宣言されているので、呼び出すにはUnityのiOSビルド結果であることが前提3となります。 → 上記のネイティブアプリ側でコメントアウトしているのはこの為であり、補足で記載している通り、ネイティブコードをUnityに持ってくる段階で変更を加えて呼び出せるようにします。 /Assets/PluginDevelopmentExample/Plugins/iOS/NativeShare.mm // NOTE: 実態はUnityがiOSビルドで出力するプロジェクト中に含まれる`UnityAppController.mm`と言うソースにある extern UIViewController* UnityGetGLViewController(); // MARK:- extern "C" (Cリンケージで宣言) #ifdef __cplusplus extern "C" { #endif // NOTE: C#から渡される文字列はcharのポインタ型として渡される void shareText(char* textPtr) { // NSStringへの変換 NSString* text = [NSString stringWithCString:textPtr encoding:NSUTF8StringEncoding]; // NOTE: Unity上からViewControllerを取得するには`UnityGetGLViewController()`を使う UIViewController* vc = UnityGetGLViewController(); [NativeShare shareText:text viewController:vc]; } #ifdef __cplusplus } #endif ■ UIKitに関連するプラグインを実装する際の注意点 今回例に出した「シェアUIの表示」と言った「UIに関する機能」を呼び出す際にはViewControllerを要求されることがあります。 以下の補足にて「新規でネイティブアプリのプロジェクトを立ち上げるならStoryboardを選択した方が良い」と書いた理由はこれであり、UnityがiOSビルドで出力するプロジェクトはUIKitベースとなるために、APIの親和性の観点から今はこちらを選択したほうが良いかと思います。4 【補足】 Storyboard or SwiftUI? ここまでのまとめ サンプルを例に幾つかの要点を補足してきましたが、あくまで実装の一例として参考にして頂くようお願いします。 (ここに記載している手法が絶対ではない) 例えば以下の項目に記載している「ヘッダーファイル(.h)をソースファイル(.mm)にマージ」と言う対応は必須ではありませんし、UnityGetGLViewControllerに関する変更はUIKitが関わっているからこその変更内容になります。 → 逆に言うとUIKitが関わらないシンプルなAPIの呼び出しレベルであればこういった対応はしなくても良いかも 【補足】 ネイティブコードをUnityにインポートする際の変更点 サンプルのネイティブアプリ側の言語がObjCなのは、ネイティブプラグインをObjCで実装する都合から合わせているだけです。ここらに関しては特に縛りはなく、「プラグインはObjCだけど、ネイティブアプリはSwiftベースで実装」みたいに実装しやすい方に合わせてもよいかと思います。 ↩ 外部クラスから呼び出される都合からNativeShareクラスはソースとヘッダーに分ける形で用意してます ↩ ネイティブコード側で同名/相応の関数を実装してやればコンパイルを通すことはできるかもしれないが、今回のサンプルではそこまではやっていない。 ↩ 一応はUIKitとSwiftUIを連携するためのAPIも存在するが、その分キャッチアップに必要な範囲が広がってしまうので、必要になるまでは必須科目ではないかな〜と思ってます ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Monobit Unity Networking 2.0(MUN)を使ってみた話

はじめに 仕事でMonobit Unity Networkingについて調べる機会があったので,備忘録がてら記事にしました。 チュートリアルの内容のまとめと,ちょっと使うときに便利かなと思った実装法を中心に書きます。 サーバとかの設定は公式が結構ちゃんとドキュメント出してくれてるので(参考),この記事では基本的にコードとかの実装方面をまとめていきます。 かなりふわふわしてる記事なので,間違い等あればコメントで指摘していただけると幸いです。 あと掲載コードについては申し訳ないんですが,全部について動くかの検証はしてないです。動いたコードに近いコードを載せるように努力してますが,動かなかったら文句言ってください。気力や時間があったら検証します。 Monobit Unity Networking とは Monobit Unity Networking(MUN)とは,モノビットエンジンが提供する国産Unity用通信ミドルウェアです。つまりUnityでマルチプレイとか通信とかをやるためのアセットです。 通信量に合わせて課金されるタイプなので,全機能を使うのにお金はかかりません。 基本的にはUnityで作ったクライアント間のP2P通信で,この記事では取り上げませんが,オンプレミス版も提供されています。 詳しくはここ 前提環境 Unity 2020.3.5f MUN 2.8.0 基本的な機能と実装 基礎的な用語 ルーム:プレイヤーが実際に通信している部屋 ロビー:ルームの集まり,ここではプレイヤー間の通信はできない MonoBehaviorの上書き Munを使う上で,MonoBehaviorの代替クラスがあります。主にMonobitEngine.MonoBehaviorとMonobitEngine.MunMonoBehaviorがあります。後者は接続やルームの入室のコールバックが定義されてます。 MonobitEngine.MonoBehaviorはMonobitEngine.MunMonoBehaviorを含めた大体のMonobitで定義されるコンポーネントの基底クラスになってるという特徴がありますが,多分ユーザで使う分にはMonobitEngine.MunMonoBehaviorを継承しとけば十分だと思います。だって継承されてるからMonobitEngine.MonoBehaviorの全機能使えるし。 サーバへの接続〜ルーム入室 ちなみにちょくちょく出てくるMonobitEngine.MonobitNetworkは静的クラスなのでMunを導入しておけばどこでも呼べます。 サーバへの接続・切断 公式記事(サーバへの接続) 公式記事(サーバからの切断) //サーバへの接続 //gameVersion:ゲームのバージョンを表す文字列 MonobitEngine.MonobitNetwork.ConnectServer(string gameVersion) //サーバからの切断 MonobitEngine.MonobitNetwork.DisconnectServer() この際,MonobitEngine.MonobitNetwork.autoJoinLobbyを事前にtrueにしておくとデフォルトのロビーに入ってくれます。 ロビーへの入室・退出・ほか操作 公式記事(ロビー入室) 公式記事(ロビー退出) 前述したMonobitEngine.MonobitNetwork.autoJoinLobbyのフラグを立てて入室してやるとここら辺の処理は省略できます。 //デフォルトロビーへの入室 MonobitEngine.MonobitNetwork.JoinLobby() //指定したロビーへの入室 //lobbyInfo:ロビー設定とか //ロビーがなかったら勝手に作るので注意 MonobitEngine.MonobitNetwork.JoinLobby(MonobitEngine.LobbyInfo lobbyInfo) //ロビーからの退出 MonobitEngine.MonobitNetwork.LeaveLobby() LobbyInfoにはNameとKindのプロパティがありますが,Kindはドキュメントにも詳しい仕様が書いてなかったので正直よくわかりません。誰か見つけたら教えてください(他力本願) //lobbyInfoの実装例 var info = new MonobitEngine.MonobitNetwork(); info.Kind = LobbyKind.Default; lobby.Name = "MyLobby"; MonobitEngine.MonobitNetwork.JoinLobby(info); 公式記事(ルーム一覧の取得) また,ロビーの中からは公開ルームの一覧を取得できます。 //公開ルームを全取得 //RoomData[]で帰ってきます MonobitEngine.MonobitNetwork.GetRoomData() ルーム作成・入室・退出 公式記事(ルーム作成) 公式記事(ルーム入室) 公式記事(ルーム退出) //ルーム作成 //roomName: ルーム名 //roomSettings: ルームの設定 //lobbyInfo:属するロビー MonobitEngine.MonobitNetwork.CreateRoom(string roomName, MonobitEngine.RoomSettings roomSettings, MonobitEngine.LobbyInfo lobbyInfo) //ルーム入室 //なかったらfalseが帰ってきます //roomName: 入りたいルーム名 MonobitEngine.MonobitNetwork.JoinRoom(string roomName) //ルーム入室,なかったら作成 //roomName: ルーム名 //roomSettings: ルームの設定 //lobbyInfo:属するロビー MonobitEngine.MonobitNetwork.JoinOrCreateRoom(string roomName, MonobitEngine.RoomSettings roomSettings, MonobitEngine.LobbyInfo lobbyInfo) //ランダム入室 MonobitEngine.MonobitNetwork.JoinRandomRoom() //条件に合うルームにランダム入室 //expectedCustomRoomProperties: 検索したいカスタムパラメータ,ルーム作成側はroomSettings.customParametersで設定できる(後述) //expectedMaxPlayers: 検索したいルームの入室人数を指定,0なら考慮しない MonobitEngine.MonobitNetwork.JoinRandomRoom(Hashtable expectedCustomRoomProperties, byte expectedMaxPlayers) //ルーム退出 MonobitEngine.MonobitNetwork.LeaveRoom() roomSettings周りが結構複雑です。これには次のプロパティを設定できます。 byte maxPlayers : 入室可能人数 bool isVisible : trueならロビーから検索用メソッド呼ぶと検索できる bool isOpen : 他プレイヤーの入室の可否 Hashtable customRoomParameters: カスタムパラメータ,JoinRandomRoomとかの検索条件に指定できる string[] customRoomParametersForLobby: ここに指定したカスタムパラメータはロビーから見えるようになり,検索用メソッドの条件に指定できる 特にcustomRoomParametersがよーわからないと思います。これはつまりHashtable型のいろんなタグパラメータをつけられるっていう感じで,JoinRandomRoomとかの絞り込みに影響します。 詳しくはこちら(公式記事) 例を示します。 var hashTable = new Hashtable(); hashTable["tag"] = "Unity"; var settings = new RoomSettings(); settings.roomParameters = hashTable; MonobitNetwork.CreateRoom("RoomFoo", settings, LobbyInfo.Default); というルームを作ると, var hashTable = new Hashtable(); hashTable["tag"] = "Unity"; MonobitEngine.MonobitNetwork.JoinRandomRoom(hashTable, 0); でそのルームにマッチングできるようになるはずです。 公式記事ではroomParametersに対してインデックスを指定して書き込む方法で例が示されていますが,自分の環境ではそれをやると「roomParametersの中身なんもねぇよ!」って怒られたので,こちらでは自分で作ったHashtableで上書きしています。こちらだと通ることも確認しています。 オブジェクトの位置とアニメーションの同期 参考公式記事 こちらはほぼスクリプトなしで実装できます。 同期したいオブジェクトにMonobit Viewを追加し, 位置ならばMonobit Transform View,アニメーションならMonobit Animation Viewのスクリプトを追加して, Monobit ViewのObserved Component Registration ListにMonobit Transform ViewやMonobit Animation Viewを追加します。 これでとりあえず位置の同期は完了です。 ネットワーク越しのオブジェクト生成・破棄 公式記事(オブジェクト生成) 公式記事(オブジェクト破棄) ネットワーク越しで生成や破棄を同期させたいオブジェクトに関しては,UnityEngine.Instantiateするとうまくいきません。(どううまくいかないかは検証して追記するかもしれないです) //ネットワーク越しにオブジェクト生成 //string prefabName: プレファブ名(内部的にはResources.Loadしてるので正確にはパス名?要検証) //positionとrotationはUnityEngine.Instantiateと同じなので略 //int group: 所属するグループ名(なんかわかったら追記します) MonobitEngine.MonobitNetwork.Instantiate(string prefabName, Vector3 position, Quaternion rotation, int group) //ネットワーク越しに対象viewに所属するオブジェクトを全部破棄 //MonobitView monoView: 破棄する対象のMonobitView MonobitEngine.MonobitNetwork.Destroy(MonobitView monoView) //ネットワーク越しに対象オブジェクト破棄 //引数gameObjectは技術ドキュメントだとMonobitView型ですがGameObject型です。(検証済み,あとAPIリファレンスはそうなっています) MonobitEngine.MonobitNetwork.Destroy(GameObject gameObject) MonobitEngine.MonobitNetwork.InstantiateはResources.Loadを使ってるようなので,生成するプレファブはResourcesフォルダにないといけません。あと指定するのがプレファブのGameObjectではなくプレファブ名なのも注意。 操作キャラの位置同期(オブジェクトの所有権の処理) 公式記事(オブジェクトの所有権) 参考公式記事 さて,ここまでの作業でとりあえずオブジェクトの生成と位置やアニメーションの同期はできますが,操作キャラだけはもうちょっと作業が要ります。というのも,同期された相手のオブジェクトはこちらのシーンに召喚される都合上,何も対策をしないと同期されてきた相手のオブジェクトも操作できてしまうため,位置の同期とこちらの操作で干渉してなんか変なことになります。 そのため,自分が所有権をもつオブジェクト以外は操作しないというスクリプトを書いてやらないといけなくなります。 具体的には,自分がオブジェクトの所有権をもつかは,MonobitEngine.MonoBehavior(またはMunMonoBehavior)内のmonobitView.isMineプロパティで判定できます。これを使って操作スクリプトを無効化してやればいいです。実装例は下の方にあります。 ネットワーク越しのメソッド呼び出し 公式記事 MonobitEngine.MonoBehavior(またはMunMonoBehavior)なら,ネットワーク越しでRPCという手法によってメソッドを呼び出してやれます。 まず,呼び出したいメソッドに[MunRPC]を指定します。 MonobitViewを適当なオブジェクトにアタッチしてやり,MonobitViewIDを0以外の適当な正の整数値にしてやります。 それを[SerializedField]なりGetComponentなりで取得してやって,MonobitViewのRPC()メソッドを呼べばOKです。 なんのこっちゃってなってると思うので例を示します。 [MunRPC] void targetMethod(string message, int id){ Debug.Log(id + " says " + message); } void callRpc(){ var view = GetComponent<MonobitView>(); view.RPC( nameof(targetMethod) //関数の名前(string) , MonobitTargets.All //呼び出しの対象,公式ドキュメント参照 //(ここから呼び出し先の引数) , "Hi!" //message , 0 //id ); } コールバック サーバに繋いだ時や,ルームに入った時など,いろんな時に対してコールバックが用意されています。数が多いのでここでは紹介せずこちらを参照していただきたいですが,一つだけハマりそうな部分なんですが,コールバックの利用はMunMonoBehaviorを継承する必要があります。逆に言うと,MonobitEngine.MonoBehaviorではコールバックが利用できません。 理由はコールバックはMunMonoBehaviorのメソッドをオーバーライドする形で実装されてるため,MonobitEngine.MonoBehaviorではそもそものメソッドが定義されてないんですよね。もうMunMonoBehaviorだけでいいんじゃないかな。 ちょっと一工夫 オブジェクトの所有権の処理 公式のチュートリアルでは操作スクリプトに直接所有権の判定を組み込んでいますが,操作スクリプト一個一個に組み込んでるとキリがないですし,基本的にゲーム自体の開発してる時は通信のことなんて考えたくないと思うので,個別に管理してくれるコンポーネントを作った方が管理しやすいと思います。 OwnerHandler.cs /* OwnerHandler.cs Copyright (c) 2021 Dango This software is released under the MIT License. http://opensource.org/licenses/mit-license.php */ using System.Collections.Generic; using MonobitEngine; using UnityEngine; using MonoBehaviour = UnityEngine.MonoBehaviour; namespace MunCommunication { /// <summary> /// Configure MonoBehavior.enabled by ownership of attached object. /// </summary> public class OwnerHandler : MunMonoBehaviour { [SerializeField] private List<MonoBehaviour> enableIfOwner; [SerializeField] private List<MonoBehaviour> disableIfOwner; // Start is called before the first frame update void Start() { configComponents(); } /// <summary> /// Check ownership, and configure "enabled" property. /// </summary> void configComponents() { bool isOwner = monobitView.isMine; enableIfOwner.ForEach(behaviour => { behaviour.enabled = isOwner;}); disableIfOwner.ForEach(behaviour => { behaviour.enabled = !isOwner;}); } } } これでインスペクタに所有権があるときに有効/無効にするリストができてくれるので設定してやればいいです。 パスワードの設定 MUNはルームに対してパスワードを設定できたりはしないんですが,こちらのPhotonの記事のようにカスタムパラメータをつけて,RandomJoinをやってやればそれに近いことをできます。 簡易的な実装例 RoomSelector.cs /* RoomSelector.cs Copyright (c) 2021 Dango This software is released under the MIT License. http://opensource.org/licenses/mit-license.php */ using System.Collections; using System.Collections.Generic; using MonobitEngine; using UnityEngine; using UnityEngine.Events; using MonobitNetwork = MonobitEngine.MonobitNetwork; namespace MunCommunication { public class RoomSelector : MunMonoBehaviour { Hashtable generateAuthTable(string roomName, string password) { var customParam = new Hashtable(); customParam["name"] = roomName; customParam["password"] = password; return customParam; } public void createAuthorizedRoom(string roomName, string password) { var setting = generateAuthTable(roomName, password); var roomSetting = new RoomSettings { roomParameters = setting , isVisible = false }; //ランダム文字列生成 string randomLine = Guid.NewGuid().ToString("N").Substring(0, 10); MonobitNetwork.CreateRoom(randomLine, roomSetting, LobbyInfo.Default); } public void joinAuthorizedRoom(string roomName, string password) { var setting = generateAuthTable(roomName, password); MonobitNetwork.JoinRandomRoom(setting, 0); } } } ただ,これだと同じルーム名・パスワードが同時に発生した場合に対応ができないので,外部に認証サーバを設けてルーム名を管理してやらないと実用に足るものはおそらく作れないと思います。 まとめ MUNでできること ・ネットワーク越しのオブジェクトの位置やアニメーションの同期 ・ネットワーク越しでのメソッド呼び出し ちょっとよくわからない部分がなくもないですが,使ってる感じは問題ないような感じがします。 参考 MUNチュートリアル MUNのAPIリファレンス パスワードのかけかたとか:Photonのドキュメント ランダム文字列
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む