- 投稿日:2022-02-01T23:25:02+09:00
HoudiniのパーティクルシミュレーションをUnityのVFX Graphで再生する
HoudiniのパーティクルシミュレーションをUnityのVFX Graphで再生したいと思います。 使用しているバージョンは次の通りです。 Houdini: 19.0.455 SideFXLabs: 19.0.455 Unity: 2021.2.9f1 Visual Effect Graph: 12.14 流れとしては次のように、Houdiniで作成したパーティクルシミュレーションの位置や色といった情報をテクスチャに書き出し、そのテクスチャをUnityで読み込んでVFX Graphで使用します。 Houdiniでパーティクルシミュレーションの作成 Houdiniでパーティクルシミュレーションのテクスチャ書き出し Unityでテクスチャの読み込み UnityでVFX Graphの作成 先に結果を載せると、上がHoudiniでのパーティクルシミュレーションで、下がそれをUnityのVFX Graphで再生したものになります。 1. Houdiniでパーティクルシミュレーションの作成 まず、HoudiniでPOPを使ってパーティクルシミュレーションを作成します。OUT_Particlesがテクスチャ書き出しの対象になりますが、今回はパーティクル位置にメッシュを配置したいのでcopytopoints1でボックスを配置しています。 dotnet1の中は次のようになっています。popwind1でノイズを追加して、popwrangle1で@nageをもとに@pscaleを設定しています。 2. Houdiniでパーティクルシミュレーションのテクスチャ書き出し 作成したパーティクルシミュレーションをLabs Vertex Animation TextureROPを利用してテクスチャに書き出します。 Labs Vertex Animation TextureROPのパラメータを次のように設定します。 Mode / Target Engine: Particle Sprites(Sprite) / Unity Input Geometry: (対象のジオメトリ) Settings: Export Custom Attributes: In Color: v Export: Include: Decide Individually Geometry: オフ Unity Material: オフ 今回は、Houdini側で用意しているVertex Animation Texture用のジオメトリやマテリアルは使用しないのでエクスポートをオフにします。また、パーティクルの色は不要なので、その代わりにカラー用のテクスチャにはvアトリビュートを書き込んでいます。これはVFX Graph側でパーティクルの方向を表すために使用します。 設定が完了したら、Render Allを押してテクスチャを書き出します。 書き出したテクスチャには、それぞれ次の情報が格納されます。 /tex/{OS}_pos.exr: RGBにP、Alphaにpscale /tex/{OS}_col.exr: RGBにv 以上でHoudini側の作業は終わりです。 3. Unityでテクスチャの読み込み Houdiniで作成したテクスチャをUnityに読み込み、Import設定を次のように変更してApplyします。 sRGB: オフ Advanced: Non-Power of 2: None Generate Mip Maps: オフ Filter Mode: Point(no filter) Default: Compression: None 4. UnityでVFX Graphの作成 VFX Graphで次のようにネットワークを組みます。Experimentalなノードを使用しているのでPreferences > Visual Effects > Experimental Operators/Blocksをオンにする必要があります。 プロパティにはそれぞれ以下の値を設定します。 Position Texture: Houdiniから書き出した/tex/{OS}_pos.exr Velocity Texture: Houdiniから書き出した/tex/{OS}_col.exr Frame Count: シミュレーションのフレーム数 FPS: シミュレーションのフレームレート このVFX Graphをシーンに配置することでHoudiniのパーティクルシミュレーションをUnityで再生することができます。
- 投稿日:2022-02-01T18:36:06+09:00
Unity Edtitorでコンポーネントを一括操作
初めに ヒエラルキーのコンポーネントを一括で操作したい いつも通り、まずはgoogle先生にそれっぽい事の検索から ありました。 とても良いプログラムです。 ですが、今回やりたかったことは、削除だけでなく 各コンポーネントのチェックボックスを外したり、付けたりしたいので ちょっと改造が必要がありました。 ソースを元に色々アレンジ 元のソースはゲームオブジェクトにスクリプトつけて、実行するタイプだったのですが、 メニューから起動できるようにして、削除するだけでなく各コンポーネントのチェックボックスを付けたり 外したりできるようにしました。 using UnityEngine; using UnityEditor; using System.Collections.Generic; public class OperationComponentInChild : EditorWindow { public enum Operation { Enabled, Disabled, Delete, }; private GameObject _objGameObj = null; private Operation _operation = Operation.Enabled; //private string componentName = ""; private List<string> _componentName = new List<string>(); private int _componentIndex = 0; [MenuItem("tool/OperationComponentInChild")] static private void Show() { GetWindow<OperationComponentInChild>(); } /// <summary> /// コンポーネントを探す /// </summary> private void SearchComponent() { _componentIndex = 0; _componentName.Clear(); _componentName.Add("ALL"); var components = _objGameObj.GetComponentsInChildren<UnityEngine.Component>(); foreach (var component in components) { if (component == null) continue; if (component.GetType() != typeof(Transform)) { _componentName.Add(component.GetType().Name); } } } private void OnGUI() { // 自身のSerializedObjectを取得 var so = new SerializedObject(this); EditorGUI.BeginChangeCheck(); _objGameObj = EditorGUILayout.ObjectField("RootGameObj", _objGameObj, typeof(GameObject), true) as GameObject; if (EditorGUI.EndChangeCheck() && _objGameObj) { SearchComponent(); } if (_objGameObj) { EditorGUILayout.Space(); _operation = (Operation)EditorGUILayout.EnumPopup("Operation", _operation); EditorGUILayout.Space(); _componentIndex = EditorGUILayout.Popup("操作したいコンポーネント", _componentIndex, _componentName.ToArray()); //コンポーネントを名前入力するのでなくてあらかじめ調べて、セレクタで出すようにした //EditorGUILayout.LabelField("操作したいコンポーネントを文字列で指定 *[指定なしで全操作]", EditorStyles.boldLabel); //componentName = EditorGUILayout.TextField("ComponentName", componentName); EditorGUILayout.Space(); if (GUILayout.Button("Operation Set")) { var executionFlag = true; if (_operation == Operation.Delete) { if (!EditorUtility.DisplayDialog("確認", "コンポーネントを削除していいですか?", "Yes", "No")) { executionFlag = false; } } if (executionFlag) { GetComAndDes(); } } } so.Update(); so.ApplyModifiedProperties(); } /// <summary> /// コンポーネントを取得して該当コンポーネントを操作 /// </summary> private void GetComAndDes() { var components = _objGameObj.GetComponentsInChildren<UnityEngine.Component>(); foreach (var component in components) { if (component == null) continue; // if (component.GetType().Name == _componentName[_componentIndex] || _componentIndex == 0) { if (component.GetType() != typeof(Transform)) { Debug.Log($"{_operation.ToString()} Operation name {component.gameObject.name} component {component.GetType().Name} "); switch (_operation) { // true case Operation.Enabled: SetComponentObj(component, true); break; // false case Operation.Disabled: SetComponentObj(component, false); break; //削除 case Operation.Delete: DestroyImmediate(component); break; } } } } } /// <summary> /// オブジェクトの操作 /// </summary> /// <param name="comp"></param> /// <param name="flag"></param> private void SetComponentObj(Component comp, bool flag ) { var be = (comp is Behaviour ? (Behaviour)comp : null); var re = (comp is Renderer ? (Renderer)comp : null); var coll = (comp is Collider ? (Collider)comp : null); if (be != null) { be.enabled = flag; } else if (re != null) { re.enabled = flag; } else if (coll != null) { coll.enabled = flag; } else { Debug.Log($"none : {comp.GetType()}"); } } } こんな画面に Selectで選んだり 一応削除する時は確認も出ます。 色々苦労したところ チェックボックスだけを外すのにがてこずった。。 var components = _objGameObj.GetComponentsInChildren<UnityEngine.Component>(); でComponentには、チェックボックスを外すenabledフラグを外す変数がない。。 各コンポーネントはComponentクラスを継承したクラスがそれぞれ用意している var be = (comp is Behaviour ? (Behaviour)comp : null); var re = (comp is Renderer ? (Renderer)comp : null); var coll = (comp is Collider ? (Collider)comp : null); コンポーネントが含まれているかチェックして含まれているならキャストして それぞれのコンポーネントクラスのenabled変数を操作するようにしました。 最後に もっといい方法ないのかな。。 キャストして操作するのちょっと手間と言うか一気に出来たらいいのですが。。。 どなたか教えてください。。。 修正 名前を指定してコンポーネントを操作していたけど、オブジェクトした際に 調べて、セレクタで指定するようにしました。(2022/02/03)
- 投稿日:2022-02-01T18:36:06+09:00
Unity Edtitorコンポーネントを一括操作
初めに ヒエラルキーのコンポーネントを一括で操作したい いつも通り、まずはgoogle先生にそれっぽい事の検索から ありました。 とても良いプログラムです。 ですが、今回やりたかったことは、削除だけでなく 各コンポーネントのチェックボックスを外したり、付けたりしたいので ちょっと改造が必要がありました。 ソースを元に色々アレンジ 元のソースはゲームオブジェクトにスクリプトつけて、実行するタイプだったのですが、 メニューから起動できるようにして、削除するだけでなく各コンポーネントのチェックボックスを付けたり 外したりできるようにしました。 using UnityEngine; using UnityEditor; public class OperationComponentInChild : EditorWindow { public enum Operation { Enabled, Disabled, Delete, }; private GameObject _objGameObj = null; private Operation _operation = Operation.Enabled; private string componentName = ""; [MenuItem("Tools/ShowLog")] static private void Show() { GetWindow<OperationComponentInChild>(); } private void OnGUI() { // 自身のSerializedObjectを取得 var so = new SerializedObject(this); _objGameObj = EditorGUILayout.ObjectField("RootGameObj", _objGameObj, typeof(GameObject), true) as GameObject; if (_objGameObj) { EditorGUILayout.Space(); _operation = (Operation)EditorGUILayout.EnumPopup("Operation", _operation); EditorGUILayout.Space(); EditorGUILayout.LabelField("操作したいコンポーネントを文字列で指定 *[指定なしで全操作]", EditorStyles.boldLabel); componentName = EditorGUILayout.TextField("ComponentName", componentName); EditorGUILayout.Space(); if (GUILayout.Button("Operation Set")) { var executionFlag = true; if (_operation == Operation.Delete) { if (!EditorUtility.DisplayDialog("確認", "コンポーネントを削除していいですか?", "Yes", "No")) { executionFlag = false; } } if (executionFlag) { GetComAndDes(); } } } so.Update(); so.ApplyModifiedProperties(); } /// <summary> /// コンポーネントを取得して該当コンポーネントを操作 /// </summary> private void GetComAndDes() { var components = _objGameObj.GetComponentsInChildren<UnityEngine.Component>(); foreach (var component in components) { if (component == null) continue; // if (component.GetType().Name == componentName || componentName == "") { if (component.GetType() != typeof(Transform)) { Debug.Log($"{_operation.ToString()} Operation name {component.gameObject.name} component {component.GetType().Name} "); switch (_operation) { // true case Operation.Enabled: SetComponentObj(component, true); break; // false case Operation.Disabled: SetComponentObj(component, false); break; //削除 case Operation.Delete: DestroyImmediate(component); break; } } } } } /// <summary> /// オブジェクトの操作 /// </summary> /// <param name="comp"></param> /// <param name="flag"></param> private void SetComponentObj(Component comp, bool flag ) { var be = (comp is Behaviour ? (Behaviour)comp : null); var re = (comp is Renderer ? (Renderer)comp : null); var coll = (comp is Collider ? (Collider)comp : null); if (be != null) { be.enabled = flag; } else if (re != null) { re.enabled = flag; } else if (coll != null) { coll.enabled = flag; } else { Debug.Log($"none : {comp.GetType()}"); } } } こんな画面に Selectで選んだり 一応削除する時は確認も出ます。 色々苦労したところ チェックボックスだけを外すのにがてこずった。。 var components = _objGameObj.GetComponentsInChildren<UnityEngine.Component>(); でComponentには、チェックボックスを外すenabledフラグを外す変数がない。。 各コンポーネントはComponentクラスを継承したクラスがそれぞれ用意している var be = (comp is Behaviour ? (Behaviour)comp : null); var re = (comp is Renderer ? (Renderer)comp : null); var coll = (comp is Collider ? (Collider)comp : null); コンポーネントが含まれているかチェックして含まれているならキャストして それぞれのコンポーネントクラスのenabled変数を操作するようにしました。 最後に もっといい方法ないのかな。。 キャストして操作するのちょっと手間と言うか一気に出来たらいいのですが。。。 どなたか教えてください。。。
- 投稿日:2022-02-01T17:17:10+09:00
【コピペで実装】Unityで作ったゲームにログイン機能を追加してみた
概要 Unityで作ったアプリにニフクラ mobile backendを使って無料でログイン機能を追加する方法を紹介します? コピペで実装できるので初心者の方でも簡単に実装できます? 使うサービス ニフクラ mobile backend(Unity SDK) 無料で利用可能できるサービスで、簡単にバックエンドの開発ができるニフクラ mobile backendを利用してランキング機能を実装していきます。 以下より会員登録を行うと、環境構築する必要なしにすぐ利用できます???? 会員登録 Unity SDKのドキュメントはこちらです? ニフクラ mobile backendのサービスの概要はこちらをご参照ください? Unityとの連携方法 ニフクラ mobile backendのクイックスタートで画像を用いながらわかりやすく説明しているので 是非以下を参考に連携させてみてください?✨ ▼イメージ クイックスタート 実装方法 今回は1つのファイルに新規会員登録・ログイン・ログアウト機能を実装していきます?????? 使用するスクリプトテンプレートなど実装前のコード 新規会員登録・ログイン・ログアウト処理を書く前の状態は以下のような感じです。 userNameとpasswordを入力するInputFieldを作成しています。 新規会員登録をする際は↓の画像と同じようなシーンを作成します。 既に会員登録をしている場合は、↓のInputFieldにユーザー名とパスワードを入力してログインします。 現在は以下のコードだけ書いています。 using UnityEngine; using UnityEngine.UI; using System.Collections; using NCMB; using UnityEngine.SceneManagement; public class ***スクリプト名*** : MonoBehaviour { public InputField UserName; public InputField PassWord; private string currentPlayerName; //ここの中に新規会員登録・ログイン・ログアウト処理を書いていきます } ※1画面で新規会員登録とログインをするとデータが保存できないエラーが起きることがあります? エラー内容と解決方法は以下のブログをご参照ください。 https://blog.mbaas.nifcloud.com/entry/2020/10/01/145149 ログイン機能 (★の部分が今回ニフクラ mobile backendのコードです。他の箇所はゲームの処理です。) public void Login() { print (UserName.text); print (PassWord.text); //★NCMBUserのインスタンス作成 NCMBUser user = new NCMBUser (); // ★ユーザー名とパスワードでログイン NCMBUser.LogInAsync (UserName.text, PassWord.text, (NCMBException e) => { if (e != null) { UnityEngine.Debug.Log ("ログインに失敗: " + e.ErrorMessage); } else { UnityEngine.Debug.Log ("ログインに成功!"); //画面遷移 SceneManager.LoadScene (*);//遷移させたいシーンを指定 } }); } 新規会員登録 public void SignUp() { print (UserName.text); print (PassWord.text); //★NCMBUserのインスタンス作成 NCMBUser user = new NCMBUser (); //ユーザ名とパスワードの設定 user.UserName = UserName.text; user.Password = PassWord.text; //★会員登録 user.SignUpAsync ((NCMBException e) => { if (e != null) { UnityEngine.Debug.Log ("新規登録に失敗: " + e.ErrorMessage); SceneManager.LoadScene (*); //遷移させたいシーンを指定 } else { UnityEngine.Debug.Log ("新規登録に成功"); //★カレントユーザーを確認 NCMBUser currentUser = NCMBUser.CurrentUser; if (currentUser != null) { UnityEngine.Debug.Log ("ログイン中のユーザー: " + currentUser.UserName); Login(); } else { UnityEngine.Debug.Log ("未ログインまたは取得に失敗"); } //画面遷移 SceneManager.LoadScene (*);//遷移させたいシーンを指定 } }); } ログアウト public void Userlogout() { NCMBUser.LogOutAsync ( (NCMBException e) => { if(e == null ){ UnityEngine.Debug.Log ("ログアウト成功"); }else{ UnityEngine.Debug.Log ("ログアウトに失敗: " + e.ErrorMessage); } }); } 結果 ニフクラ mobile backendの会員管理を見てみると、ちゃんと登録されています?❣ 最後に 以上で、ログイン機能が追加できました! さらに、別の記事でランキング機能を追加する記事も公開しているので、是非ご覧ください??? 不明点があればユーザーコミュニティへ質問してみてください!(無料プランから利用可能) 有償プランを契約している方はテクニカルサポートで個別にサポートしていただけます!
- 投稿日:2022-02-01T17:12:38+09:00
【コピペで実装】Unityで作ったゲームにランキング機能を追加してみた
概要 Unityで作ったアプリにランキング機能を追加する方法を紹介します? コピペで実装できるので初心者の方でも簡単に実装できます? 使うサービス ニフクラ mobile backend(Unity SDK) 無料で利用可能できるサービスで、簡単にバックエンドの開発ができるニフクラ mobile backendを利用してランキング機能を実装していきます。 以下より会員登録を行うと、環境構築する必要なしにすぐ利用できます???? 会員登録 Unity SDKのドキュメントはこちらです? ニフクラ mobile backendのサービスの概要はこちらをご参照ください? Unityとの連携方法 ニフクラ mobile backendのクイックスタートで画像を用いながらわかりやすく説明しているので 是非以下を参考に連携させてみてください?✨ ▼イメージ クイックスタート ランキング機能の実装手順 ランキング機能を実装するためには データストア(DB)にユーザー名とスコアを保存 データストアに保存されているデータをスコア降順で5件取得 ユーザーネームとスコアを画面表示 という順番で実装していきます。 このアプリは、ログイン中のユーザー(カレントユーザー)を認識してゲームで遊んでいるため、ユーザーはログインしている必要があります。 Unityで作ったアプリにログイン機能を追加する方法は別途ブログで公開していますので まだ実装されていない方はそちらからご覧ください???? データストア(DB)にユーザー名とスコアを保存 私は今回ブロック崩しゲームにランキング機能を付けました。 定義は以下です。 ブロックが1個ボールに当たるごとに点数が+100点 成功しても失敗しても「data」クラスにスコアとユーザーネームが保存される データストアにデータ(ユーザーネームとスコア)を保存する場合のコードは以下です。 (★の部分が今回ニフクラ mobile backendのコードです。他の箇所はゲームの処理です。) using System; using UnityEngine; using NCMB; public class Ball : MonoBehaviour { Rigidbody rigid; GameManager manager; public string currentUserName; [SerializeField] int speed = default; //スコアは0からスタート int score = 0; // ★クラスのNCMBObject(data)を作成 NCMBObject data = new NCMBObject("data"); void Start() { //ゲームの中身 } void OnCollisionEnter(Collision collision) { // ボールの移動速度を一定にする処理など // ボールがブロックに衝突した場合 if (collision.gameObject.name.StartsWith("Block", StringComparison.Ordinal)) { // ブロックを削除 Destroy(collision.gameObject); GameManager.Instance.BlockCount--; //点数を追加 score += 100; } // クリアした場合 if (manager.IsClear()){ manager.EndGame("Game Clear!"); // ★カレントユーザーの確認 NCMBUser currentUser = NCMBUser.CurrentUser; if (currentUser != null) { UnityEngine.Debug.Log ("ログイン中のユーザー: " + currentUser.UserName); } else { UnityEngine.Debug.Log ("未ログインまたは取得に失敗"); } // ★UserNameとscoreを「data」クラスに保存する data["score"] = score; data["UserName"] = currentUser.UserName; // ★データストア(「data」クラス)への登録 data.SaveAsync(); return; } // ゲームオーバーの場合も同じ if (manager.IsGameOver()){ //失敗した際の処理を書く //★ニフクラ mobile backendの処理は上と同じ } } } ニフクラ mobile backendのデータストア(「data」クラス)にも保存されていることを確認できました? データストアに保存されているデータをスコア降順で5件取得して表示させる データストアに保存されているデータをスコア降順で5件取得 ユーザーネームとスコアを画面表示 は1ファイルにまとめて書いています。 using System.Collections; using System.Collections.Generic; using UnityEngine; using NCMB; using System.Linq; using UnityEngine.UI; public class RankingHyoji : MonoBehaviour { public Text targetText; void Start () { //順位のカウント int count = 0; string tempScore = ""; //★ データストアの「data」クラスから検索 NCMBQuery<NCMBObject> query = new NCMBQuery<NCMBObject>("data"); //★Scoreフィールドの降順でデータを取得 query.OrderByDescending ("score"); //★検索件数を5件に設定 query.Limit = 5; query.FindAsync ((List<NCMBObject> objList ,NCMBException e) => { if (e != null) { UnityEngine.Debug.Log ("ランキング取得失敗"); } else { //検索成功時の処理 UnityEngine.Debug.Log ("ランキング取得成功"); // 値とインデックスのペアをループ処理 foreach (NCMBObject obj in objList) { count++; //★ユーザーネームとスコアを画面表示 tempScore += count.ToString() + "位:" + ":ユーザーネーム:" + obj ["UserName"] + ":スコア:" + obj ["score"] + "\r\n"; } targetText.GetComponent<Text>().text = tempScore; } }); } } 結果 以上で、ランキング機能が追加できました! 不明点があればユーザーコミュニティへ質問してみてください!(無料プランから利用可能) 有償プランを契約している方はテクニカルサポートで個別にサポートしていただけます!
- 投稿日:2022-02-01T13:31:38+09:00
【Unity】PlayFab で Curl error 61 が出ていたときの話
はじめに 初投稿です。 タイトルの件について、既に解決している方も多いと思いますが、こういうケースがあったということで備忘録的に書き記します。 当時もあまり話題にされなかったので、原因がわからなくて詰まっている人の助けになれば幸いです。 原因 Unity バージョン 2020.3.9 ~ 2020.3.15 または 2021.1.3 ~ 2021.1.16 で UnityWebRequest などネットワーク機能を使っていると起こりうる問題で、 バージョンアップされた libcurl がサポートされていないデータ圧縮エンコーディングを拒否するようになり、gzip 圧縮をリクエストすると失敗を返すようになったことが原因でした。 他にSDK内で gzip 圧縮 を使っていた VRoid , Mapboxでも起きていたみたいですね。 IssueTracker: UNITYWEBREQUEST FAILS ON WINDOWS IF GZIP COMPRESSION IS USED 解決 自分の環境では当時、リクエストヘッダーで "Accept-Encoding" に "gzip" を指定している箇所を "identity" に変更することで対処しました。 PlayFabでは、 PlayFabUnityHttp.cs で MakeApiCall 内の reqContainer.RequestHeaders["X-Accept-Encoding"] = "GZIP"; と書かれているところを、 reqContainer.RequestHeaders["X-Accept-Encoding"] = "identity"; ですね。 現在は 2019.4.30, 2020.3.16, 2021.1.17 それぞれで UnityWebRequest が gzip 圧縮をサポートするように修正されているので、まだエラーが出ている方は Unity のバージョンを確認してみてください。 しれっと 2019.4 で修正をしたと書きましたが、リリースノートに書いてあるのを見ただけで 2019.4 でいつから同様の問題が出ていたかは、発生報告を見つけられなかったのでわかりません。 参考程度に Unity リリースノートをリンクしておきます。 修正項目は Networking の分類です。 結論として、よほどの理由がない限り Unity のバージョンを上げたほうが良いでしょう。
- 投稿日:2022-02-01T04:59:02+09:00
【自分用の下書き】記事のテンプレート
UnityのTips記事で使う、自分用のテンプレートです。 実際に書く際はマークダウン記法のチートシートなどを参考にするといいでしょう。 テンプレート(自由に使ってください) Template.md <!-- まえがきを書く--> # ?この記事で出来ること - できること1 - できること2 - できること3 ## ?環境 ### ?Unityのバージョン Unity 20xx.x #### ?使用したアセット&ライブラリ - [アセット1](url) ``csharp:ClassName.cs // Write Source Code! `` # ?コードの解説 <!-- コードの解説を書く --> # ?まとめ <!-- まとめを書く --> # ?参考にしたサイト [title](https://qiita.com/) プレビュー 実際の見た目はこんなかんじ ?この記事で出来ること できること1 できること2 できること3 ?環境 ?Unityのバージョン Unity 20xx.x ?使用したアセット&ライブラリ アセット1 csharp:ClassName.cs // Write Source Code! ?コードの解説 ?まとめ ?参考にしたサイト title
- 投稿日:2022-02-01T03:36:28+09:00
Unityの接触判定管理を隣接行列を用いて表現してみる試み
前置き 大量の接触判定管理を同時にいい感じに管理して参照する方法はないかなと この質問 を見てて思ったところ高専時代に離散数学の授業をしてた某教員の顔が思い浮かびグラフ理論で見られる無向グラフで接触状態を表現できるのではないかと思いこの記事を書きました サンプルコード 以下の解説で使用される実装は上記のリポジトリにおいて公開されています。 Unity2Dで実装されていますがわかっている人なら3Dでも容易に書き換えられると思います。 前提条件 以下のようなボールがいくつもあるなかでボールをClickすると隣接してるボールが消えてClickしたボールも消えるような実装を書きたい。 説明っぽいやつ ※参考文献の最初の6ページ位を事前に読んでもらえると理解が進みます。素晴らしい資料ですね・・・ 頂点の集合Vと枝の集合Eとして表現できるデーター構造は以下のようにあらわすことができます。 G=(V,E) 今回やりたいのはボール同士の当たり判定を管理したいので頂点をボール、枝を「当たってる状態」として表現します 枝に方向が定義されてない場合は無向グラフ、ある場合は有向グラフとして扱われますが今回は只の当たり判定なので無向グラフとして考えます (イメージ図: 雑で申し訳ない・・・) グラフの数値的表現 ※参考文献の6pに丁寧な説明があるためスキップしてそちらを読んでも差し支えない 隣接している頂点の関係はリスト、行列を用いて表現可能でこれらを隣接行列、隣接リストと言います。 頂点$(v,u)$ に隣接関係があるとき行列成分$a(v,u)=1$、ないときは$a(v,u)=0$になるデータ表現。 今回は表現と実装が容易な隣接行列を用いて頂点同士の隣接関係(ボールの接触関係)を表現します。 以下のようなグラフがあるときは 次のような行列で表すことができます a= \begin{pmatrix} 0 & 1 & 1 & 0 \\ 1 & 0 & 1 & 0 \\ 1 & 1 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ \end{pmatrix} n行目m列が1なら頂点nと頂点mが隣接関係にある、0ならないといった感じで表現されます。 そのような表現方法を使うため対称行列になります。 行列と行ってもなんかガリガリ数値計算をするわけでもないので只の隣接一覧表みたいな感じですね 実装 基本的な考え方 隣接行列はあくまでも当たり判定がどうなっているかを記録する為の表現方法に過ぎないのでBallクラスを作ってballのPrefabにアタッチしそこでOnCollisionEnter2D OnCollisionExit2Dをイベントを呼べるようにして当たり判定がとられる&外れる度にBallPlayManagerクラスの隣接行列を更新するような実装にします。 BallManager 当たり判定の状態管理とボールの生成からDestroy()まではこのクラスにやってもらいます using System.Collections.Generic; using UnityEngine; /// <summary> /// ボールを生成したり管理したりするクラス /// </summary> public class BallPlayManager : MonoBehaviour { [SerializeField] private int _maxBall = 900; [SerializeField] private Transform _ballGenratePos; //ballの生成位置をtransformで定義 //ballの数×ballの数のサイズになる隣接行列を定義 private bool[,] _adjacencyMatrix; //隣接行列でBallの接続関係を表現する無向グラフを表現する private List<Ball> _balls;//各ballPrefabにアタッチされてるballクラスを格納するリスト private string _ballPrefabPath = "TestBall"; // 動的生成するPrefab名を指定(Resourcesにある) private void Start() { _balls = new List<Ball>(_maxBall); _adjacencyMatrix = new bool[_maxBall, _maxBall];// BallGenerate(); } /// <summary> /// ボール生成クラス、Eventの追加 /// </summary> private void BallGenerate() { GameObject obj = (GameObject)Resources.Load(_ballPrefabPath); for (int i = 0; i < _maxBall; i++) { _balls.Add(Instantiate(obj, _ballGenratePos).GetComponent<Ball>()); _balls[i].BallNumber = i;//ballの要素番号をballのIDとして使うよ _balls[i].ColliderEnter.AddListener(AdjacencyMatrixEnterUpdateEvent);//当たった時のイベントを購読 _balls[i].ColliderExit.AddListener(AdjacencyMatrixExitUpdateEvent);//当たり判定が外れたときのイベントを購読 _balls[i].OnClick.AddListener(BallClickd);//Clickした時のイベントを購読 } } /// <summary> /// 接触状態になったときに反映されるメソッド /// </summary> /// <param name="mynumber"></param> /// <param name="ballnumber"></param> private void AdjacencyMatrixEnterUpdateEvent(int mynumber, int ballnumber) { _adjacencyMatrix[mynumber, ballnumber] = true;//頂点(v,u)を1にする処理 } /// <summary> /// 接触状態が解除になったときに反映されるメソッド /// </summary> /// <param name="mynumber"></param> /// <param name="ballnumber"></param> private void AdjacencyMatrixExitUpdateEvent(int mynumber, int ballnumber) { _adjacencyMatrix[mynumber, ballnumber] = false;//頂点(v,u)を0にする処理 } /// <summary> /// 生成したballがClickされときに呼ばれるメソッド /// </summary> private void BallClickd(int ballnumber) { for (int i = 0; i < _maxBall; i++) { if (_adjacencyMatrix[ballnumber, i] && i != ballnumber) { Destroy(_balls[i].gameObject); //隣接行列で接触状態にあるボールを破壊 } } Destroy(_balls[ballnumber].gameObject); //最後にClickされたボールを破壊 } } Ballクラス 当たり判定系のイベント関数やClickEventはこっちに持ってもらいボールごとに発火するように実装しています。 using UnityEngine; using UnityEngine.Events; /// <summary> /// ボール本体にアタッチされるクラス /// </summary> public class Ball : MonoBehaviour { public int BallNumber = -1; //ボールの当たり判定が発火した時に呼ばれるEvent型を定義 [System.Serializable] public class BallColliderEventType : UnityEvent<int, int> { } // ボールがClickされたときに発火するEvent型を定義 [System.Serializable] public class BallClickEvent : UnityEvent<int> { } public BallColliderEventType ColliderEnter = new BallColliderEventType(); public BallColliderEventType ColliderExit = new BallColliderEventType(); public BallClickEvent OnClick = new BallClickEvent(); /// <summary> /// 当たったら隣接行列の要素を1(bool型なのでtrue) /// </summary> /// <param name="other"></param> private void OnCollisionEnter2D(Collision2D other) { //壁とか関係ないObjectに接触した時の為にTrygetComponentでBallかどうか判定 if (other.gameObject.TryGetComponent(out Ball ball)) { ColliderEnter.Invoke(BallNumber, ball.BallNumber); } } /// <summary> /// 接触状態が外れたら隣接行列の要素0(bool型なのでfalse) /// </summary> /// <param name="other"></param> private void OnCollisionExit2D(Collision2D other) { if (other.gameObject.TryGetComponent(out Ball ball)) { ColliderExit.Invoke(BallNumber, ball.BallNumber); } } /// <summary> /// Clickされたときに呼ばれるEvent(serializefieldでアタッチ) /// </summary> public void ClickEvent() { OnClick.Invoke(BallNumber); //Debug.Log(BallNumber); } } 出来上がった実装 特に実用する気はなく思いつきが実装になったらどんな感じかなみたいな気持ちで書きました。 参考文献 東北大学塩浦准教授 講義資料 アルゴリズムとデータ構造 http://www.dais.is.tohoku.ac.jp/~shioura/teaching/ad09/ad09-09.pdf 余談 グラフ(離散数学的)をサクッと書くときはGraphvizOnlineが便利だということがわかった。 graph { 1 -- 2 1 -- 3 2 -- 3 3 -- 4 } 簡単な無向グラフぐらいならこれで書けます。 あと更に余談なんですが ffmpeg -i {任意のmp4} {任意の名前}.gifで .gifが出力されるの便利ですね・・・世界は便利にあふれている・・・