- 投稿日:2021-08-09T21:45:38+09:00
【Unity + Photon + WebGL】ウェブ上でマルチプレイヤー対応
Unityで作ったものをウェブ上でマルチプレイヤー対応に挑戦してみた。 UnityとPhotonとWebGLを使えばできた。 動作確認バージョン Unity 2019.4.22f1 Photon Photonにアクセス https://www.photonengine.com/ja/ アカウント登録して、アプリ新規登録する。 Unity Unityを起動する。 このとき作ったプログラム、アニメーションを使う (カメラ追随以外) Asset STOREで、 PUN2 -FREE をダウンロードし、インポートする。 ポップアップが出るので、さきほどのPhotonウェブサイトで作成したアプリのアプリケーションIDを入力 Photon ServerSetting というのができる。 Resources フォルダを作成 プレイヤー自身のアバターとして表示するゲームオブジェクトのプレハブを作成し、「Resources」フォルダーの中に入れる。 アバターなど人型の場合 Avatarのinspectorには ADD componentから [Photon view]を追加 [Photon Transform View]を追加 [Photon Animator View]を追加 以下設定 ※これでベストかどうか分からないが、正常に動いた なお、デフォルトで存在する Main Camera は絶対に消さないこと。 ※各参加者を追随するカメラを設定するため コード 空のオブジェクトを作り、そこに以下のコードをアタッチ ※アバターは3つ用意し、乱数でアバターを選ぶようにした PhotonController.cs using Photon.Pun; using Photon.Realtime; using UnityEngine; public class PhotonController : MonoBehaviourPunCallbacks { private void Start() { // PhotonServerSettingsの設定内容を使ってマスターサーバーへ接続する PhotonNetwork.ConnectUsingSettings(); } // マスターサーバーへの接続が成功した時に呼ばれるコールバック public override void OnConnectedToMaster() { // "Room"という名前のルームに参加する(ルームが存在しなければ作成して参加する) PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions(), TypedLobby.Default); } // ゲームサーバーへの接続が成功した時に呼ばれるコールバック public override void OnJoinedRoom() { //何番目の入室者か int num = PhotonNetwork.CurrentRoom.PlayerCount; Debug.Log("num: "+num); string avatarName=""; int _ranNum = Random.Range(1, 4); avatarName = "Avatar" + _ranNum; // ランダムな座標に自身のアバター(ネットワークオブジェクト)を生成する var position = new Vector3(Random.Range(-10f, 10f), Random.Range(10f, 0f), Random.Range(-10f, 10f)); GameObject avatar = PhotonNetwork.Instantiate(avatarName, position, Quaternion.identity); var camera = GameObject.Find("MainCamera"); camera.transform.parent = avatar.transform; //camera.transform.position = avatar.transform.position; var pos = avatar.transform.position; camera.transform.position = new Vector3(pos.x , pos.y+1.0f, pos.z); } } アバターのスクリプト Walk.cs の public class Walk : MonoBehaviourを public class Walk : MonoBehaviourPunCallbacks に変更 Update()関数に if (photonView.IsMine) を追加。 これで、自分のオブジョクトとその他オブジョクトの切り分けができる。 Walk.cs using UnityEngine; using System.Collections; using Photon.Pun; using Photon.Realtime; public class Walk : MonoBehaviourPunCallbacks { private Animator animator; // Use this for initialization void Start() { animator = GetComponent<Animator>(); } // Update is called once per frame void Update() { if (photonView.IsMine) { if (Input.GetKey("up")) { transform.position += transform.forward * 0.02f; animator.SetBool("is_walking", true); } else if (Input.GetKey("down")) { transform.position -= transform.forward * 0.02f; animator.SetBool("is_walking", true); } else { animator.SetBool("is_walking", false); } if (Input.GetKey("right")) { transform.Rotate(0, 1.0f, 0); } if (Input.GetKey("left")) { transform.Rotate(0, -1.0f, 0); } } } } ビルド WebGLでやってみる インストールする必要があるので、クリック モジュールを追加 再度ビルド! できた。 なんとか2日かかって、自分のレンタルサーバー上にバーチャル空間を構築しました(Unity + Photon +WebGL)こちらのリンク先 → https://t.co/uL4L1tNH4s・マルチプレイヤー対応(20人まで)・アバター3種類(選択できない)・チャット機能無し、視点選べないと、まったく何も無いですが・・ pic.twitter.com/B4ZyuSsYn2— たつや (@tatsuya1970) August 9, 2021 参考サイト
- 投稿日:2021-08-09T21:45:38+09:00
【Unity + Photon + WebGL】マルチプレイヤー対応のバーチャル空間を自作してみた
Unityで作ったものをマルチプレイヤー対応のバーチャル空間をウェブで公開してみた。 UnityとPhotonとWebGLを使えばできた。 動作確認バージョン Unity 2019.4.22f1 Photon Photonにアクセス https://www.photonengine.com/ja/ アカウント登録して、アプリ新規登録する。 Unity Unityを起動する。 このとき作ったプログラム、アニメーションを使う (カメラ追随以外) https://qiita.com/tatsuya1970/items/5dea86208951574fcc9c PUN2 Asset STOREで、 PUN2 -FREE をダウンロードし、インポートする。 ポップアップが出るので、さきほどのPhotonのウェブサイトで作成したアプリのアプリケーションIDを入力 Photon ServerSetting というのができる。 アバターの設定 [Resources] フォルダを作成する。 プレイヤー自身のアバターとして表示するゲームオブジェクトのプレハブを作成し、「Resources」フォルダーの中に入れる。 アバターは3つ用意し、後述するスクリプトでは乱数でアバターを選ぶようにした。 アバターなど人型の場合 Avatarのinspectorには Add Componentから [Photon view]を追加 [Photon Transform View]を追加 [Photon Animator View]を追加 以下設定 disable(同期しない) discrete(非連続的 = 一定間隔で同期) continuous(連続的 = 常に同期) もちろん、RigidBodyとCollidar は忘れずに。 ヒト型は不安定なのでFreeze Rotation のXYZにチェック 今まで述べてきたInspectorをまとめると、 なお、inspector と関係ないが、Hierarchy にデフォルトで存在する Main Camera は絶対に消さないこと。 ※スクリプト上で各参加者を追随するカメラとして利用するため スクリプト 空のオブジェクトを作り、そこに以下のコードをアタッチ PhotonController.cs using Photon.Pun; using Photon.Realtime; using UnityEngine; public class PhotonController : MonoBehaviourPunCallbacks { private void Start() { // PhotonServerSettingsの設定内容を使ってマスターサーバーへ接続する PhotonNetwork.ConnectUsingSettings(); } // マスターサーバーへの接続が成功した時に呼ばれるコールバック public override void OnConnectedToMaster() { // "Room"という名前のルームに参加する(ルームが存在しなければ作成して参加する) PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions(), TypedLobby.Default); } // ゲームサーバーへの接続が成功した時に呼ばれるコールバック public override void OnJoinedRoom() { //何番目の入室者か? 1から順番通り採番される。退室者がいた場合の法則は未確認 //今回使わないけど、今後何かで必要そうなので備忘録としてここに残してます。 int num = PhotonNetwork.CurrentRoom.PlayerCount; //自身のアバターはランダムで決定 //今回のアバター3体で、名をAvatar1,Avatar2,Awatar3とし、Resources フォルダに入れておく string avatarName=""; int _ranNum = Random.Range(1, 4); avatarName = "Avatar" + _ranNum; // ランダムな座標に自身のアバター(ネットワークオブジェクト)を生成する var position = new Vector3(Random.Range(-10f, 10f), Random.Range(10f, 0f), Random.Range(-10f, 10f)); GameObject avatar = PhotonNetwork.Instantiate(avatarName, position, Quaternion.identity); var camera = GameObject.Find("MainCamera"); camera.transform.parent = avatar.transform; //camera.transform.position = avatar.transform.position; var pos = avatar.transform.position; camera.transform.position = new Vector3(pos.x , pos.y+1.0f, pos.z); } } アバターのスクリプト Walk.cs の public class Walk : MonoBehaviourを public class Walk : MonoBehaviourPunCallbacks に変更 Update()関数に if (photonView.IsMine) を追加。 これで、自分のオブジョクトとその他オブジョクトの切り分けができる。 Walk.cs using UnityEngine; using System.Collections; using Photon.Pun; using Photon.Realtime; public class Walk : MonoBehaviourPunCallbacks { private Animator animator; // Use this for initialization void Start() { animator = GetComponent<Animator>(); } // Update is called once per frame void Update() { if (photonView.IsMine) { if (Input.GetKey("up")) { transform.position += transform.forward * 0.02f; animator.SetBool("is_walking", true); } else if (Input.GetKey("down")) { transform.position -= transform.forward * 0.02f; animator.SetBool("is_walking", true); } else { animator.SetBool("is_walking", false); } if (Input.GetKey("right")) { transform.Rotate(0, 1.0f, 0); } if (Input.GetKey("left")) { transform.Rotate(0, -1.0f, 0); } } } } ビルド WebGLでやってみる インストールする必要があるので、クリック モジュールを追加 再度ビルド! HTMLファイルなどウェブサイト公開に必要なファイルができる。 いろいろなやり方があるが、 私は、できたものをそのまま、契約しているレンタルサーバーにFTPでファイル転送。(使用ソフトはCyberduck) 完了!! なんとか2日かかって、自分のレンタルサーバー上にバーチャル空間を構築しました(Unity + Photon +WebGL)こちらのリンク先 → https://t.co/uL4L1tNH4s・マルチプレイヤー対応(20人まで)・アバター3種類(選択できない)・チャット機能無し、視点選べないと、まったく何も無いですが・・ pic.twitter.com/B4ZyuSsYn2— たつや (@tatsuya1970) August 9, 2021 参考サイト
- 投稿日:2021-08-09T21:21:50+09:00
[Unity]Twitter認証の導入で躓いた話
Twitterのアカウント連携を実装しようと色々躓いたメモ。 ■Firebaseを導入 とりあえずFirebaseを利用してみた。 導入手順に沿ってアプリケーション作って、Unity用のSDK(firebase_unity_sdk_8.1.0.zip)を落とす。 問題1 「dotnet3」「dotnet4」の2フォルダに分かれてる…。 当然ドキュメントにはそこらへんに触れた記述はない。 ド素人に合わせたドキュメントなんてあるわけないかと、と調べたところ どうやら.NET のバージョンっぽい。 →.Netは4系を利用しているのでdotnet4のフォルダの方を選択 問題2 .unitypackageがたくさんある。 どれを入れればいいのか公式ドキュメントを読み進めたところ、 認証系だけであれば「FirebaseAuth.unitypackage」だけでいいらしい。 手順に沿って進めていき、FirebaseのコンソールからAuthenticationを選択。 「Sign-in metod」にTwitterがあった。 押してみたところの入力を求められる。 ・APIキー ・APIシークレット なんぞこれ、と調べてみたらどうやらTwitterAPIを利用するために まずTwitterのDeveloper登録?が必要みたい。 ■TwitterのDeveloper登録 問題3 アカウント登録が面倒。 なんか目的とか英語で書いてね、って言われる。しかも承認までに時間がかかりそうな感じ。 ビビッて別の方法がないか調べたけど見つからずしぶしぶ英語(Google翻訳さんに感謝)で書いて、登録。 そしたら即承認されたのかコンソールが使えるようになっていた。 問題4 ここからが本当に困ったところ。 https://firebase.google.com/docs/auth/unity/twitter-login?hl=ja "Twitter でログインするための手順に沿って、OAuth アクセストークンと OAuthシークレットを取得します" OAuth アクセストークンと OAuthシークレットとか急に言われても…。 とにかくTwitterへのログインは別の方法でやらないといけないみたい。 というわけで下記のアセットを利用することに。 Twitter Kit for Unity TwitterDemo.sceneを起動して、実機で確認してみると "fail to get request token" といったエラーが出てログインできない。 調べてみると https://github.com/twitter-archive/twitter-kit-android/issues/134 Twitterのコンソールから callback URLsに twittersdk:// を追加しないといけない模様。 ※2018年以降は必要みたい。 callback URLsに上記を追加したところ、 なぜかSaveが押せない…。 調べてみても解決方法がわからず。 試しに元々入れていたcallback urlを修正してみたところそれでもSaveが押せない。 もうお手上げ、ということでTwitterのコンソールからappを再作成し、 最初にcallback URLsを設定するタイミングでtwittersdk://を入力したら無事Saveできて Twitterのアカウント認証画面が立ち上がった、という話。 まだ何も進んでない…
- 投稿日:2021-08-09T17:59:16+09:00
VisualEffectGraphのメモ
Triggerイベントが検索で引っかからない デフォルト設定では以下の様にTrigger系のイベントを検索しても表示されません。 Preferences > VisualEffect > ExprementalOperators/Bloks のチェックをONにすると表示されるようになります。 パーティクルが消えた時に別のパーティクルを表示させたい TriggerイベントをUpdateParticleに設定する evtをParticleEventに繋ぐ そこから次に表示に表示するパーティクルのInitialに繋ぐと実現できる。
- 投稿日:2021-08-09T17:57:52+09:00
Unity FadeCamera2のUIトランジションの不具合修正
概要 テラシュールブログ様 FadeCamera2はUnityのトランジションを行う非常に有用なパッケージですが、gitの最終コミットから5年が経ち2021年現在のUnityでUIトランジションを行う際の不具合に遭遇したので備忘録的に書き残しておきます。 動作確認した環境 Editor and Build結果確認: Windows10 Unity 2019.4.7f1 遭遇した不具合 FadeMaskを使用したUIのみのトランジションの際にTextureの変更が適応されない 解決策 シェーダの_MaskTexにSetTextureで直接テクスチャを置く テクスチャをレンダーテクスチャにコピーするGraphics.Blit(src, dest, mat)が上手くMaskテクスチャを送れていないため、画面の表示をレンダーテクスチャにBlitするようにする FadeUI.cs全文 using UnityEngine; using System.Collections; using UnityEngine.UI; [RequireComponent (typeof(RawImage))] [RequireComponent (typeof(Mask))] public class FadeUI : MonoBehaviour, IFade { [SerializeField, Range (0, 1)] private float cutoutRange; public float Range { get { return cutoutRange; } set { cutoutRange = value; UpdateMaskCutout (cutoutRange); } } [SerializeField] Material mat = null; [SerializeField] RenderTexture rt = null; [SerializeField] Texture texture = null; protected void Start() { // 追加 UpdateMaskTexture(texture); } // 追加 public void UpdateMaskTexture(Texture texture) { mat.SetTexture("_MaskTex", texture); } private void UpdateMaskCutout (float range) { mat.SetFloat ("_Range", range); // 画面の表示をレンダーテクスチャにBlitする場合source = null Graphics.Blit (null, rt, mat); // コメントアウト // Graphics.Blit(texture, rt, mat); var mask = GetComponent<Mask> (); mask.enabled = false; mask.enabled = true; } }
- 投稿日:2021-08-09T17:43:08+09:00
【Unity(C#)】ARFoundationのImageTrackingで複数の画像マーカーそれぞれに対応したARオブジェクトを出現させる
はじめに ARFoundationのImageTrackingを使えば登録した画像マーカーを認識することにより、 任意のARオブジェクトを出現させることができます。 【参考リンク】:ARFoundationを触ってみた 実は画像マーカーを複数登録することも可能なようです。 今回はそれについて調べたのでメモします。 内容としては"認識した画像マーカーそれぞれの上にARオブジェクトを出す"だけに留めます。 下記のような難しい実装の手法をこの記事で書くわけでないということ予め明示しておきます。 ×複数の画像マーカーの位置関係を固定した、広域対応のARの手法。 【参考リンク】:【GyroEye Holo】マーカー位置 デモ 画像マーカー二枚のそれぞれに対応したARオブジェクトを表示しています。 あまり用途は無いかもですが、ロスト時に非表示にする実装も試してみました。 バージョン情報 諸々名前 バージョン Unity 2020.3.4f1 ARFoundation 4.0.12 ARCore XR Plugin 4.0.12 ARCore Kit Plugin 4.0.12 XR Plugin Management 4.0.1 ※Andoroidでしか試していませんが、ARFoundationなのでiosでも動くはず、、、です。 下準備 バージョン情報に従って一通りビルドできるまでの環境構築が終わったら、下記画像を参考にReferenceImageLibraryを作成します。 次にReferenceImageLibraryを設定します。 あとはAR Session OriginにAR Tracked Image Managerを設定し、 Serialized Libraryに先ほど作成したReferenceImageLibraryを設定すれば準備完了です。 コード 適当なオブジェクトにアタッチ using System.Collections.Generic; using UnityEngine; using UnityEngine.XR.ARFoundation; using UnityEngine.XR.ARSubsystems; /// <summary> /// 画像マーカー複数対応のサンプル /// </summary> public class MultiMarker : MonoBehaviour { /// <summary> /// マーカー用オブジェクトのプレハブ /// </summary> [SerializeField] private GameObject[] _arPrefabs; /// <summary> /// ARTrackedImageManager /// </summary> [SerializeField] private ARTrackedImageManager _imageManager; /// <summary> /// マーカー用オブジェクトのプレハブと文字列を紐づけた辞書 /// </summary> private readonly Dictionary<string, GameObject> _markerNameAndPrefabDictionary = new Dictionary<string, GameObject>(); private void Start() { _imageManager.trackedImagesChanged += OnTrackedImagesChanged; //辞書を作る 画像の名前とARオブジェクトのPrefabを紐づける for (var i = 0; i < _arPrefabs.Length; i++) { var arPrefab = Instantiate(_arPrefabs[i]); _markerNameAndPrefabDictionary.Add(_imageManager.referenceLibrary[i].name, arPrefab); arPrefab.SetActive(false); } } private void OnDisable() { _imageManager.trackedImagesChanged -= OnTrackedImagesChanged; } /// <summary> /// 認識した画像マーカーに応じて紐づいたARオブジェクトを表示 /// </summary> /// <param name="trackedImage">認識した画像マーカー</param> private void ActivateARObject(ARTrackedImage trackedImage) { //認識した画像マーカーの名前を使って辞書から任意のオブジェクトを引っ張り出す var arObject = _markerNameAndPrefabDictionary[trackedImage.referenceImage.name]; var imageMarkerTransform = trackedImage.transform; //位置合わせ var markerFrontRotation = imageMarkerTransform.rotation * Quaternion.Euler(90f, 0f, 0f); arObject.transform.SetPositionAndRotation(imageMarkerTransform.transform.position, markerFrontRotation); arObject.transform.SetParent(imageMarkerTransform); //トラッキングの状態に応じてARオブジェクトの表示を切り替え arObject.SetActive(trackedImage.trackingState == TrackingState.Tracking); } /// <summary> /// TrackedImagesChanged時の処理 /// </summary> /// <param name="eventArgs">検出イベントに関する引数</param> private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs) { foreach (var trackedImage in eventArgs.added) { ActivateARObject(trackedImage); } foreach (var trackedImage in eventArgs.updated) { ActivateARObject(trackedImage); } } } やっていることは下記です。 ①"ReferenceImageLibraryに登録した画像の名前"と"表示したいARオブジェクトのPrefab"を辞書で紐づける。 ②画像マーカー認識時に画像の名前に紐づけられたARオブジェクトを出現させる。 ARTrackedImageManager.trackedImagesChanged trackedImagesChangedに画像マーカー認識時のイベントハンドラーを設定できます。 イベントハンドラーの引数であるARTrackedImagesChangedEventArgsからは 下記3種類の情報をそれぞれのタイミングで得ることができます。 Type Name Description List added The list of ARTrackedImages added since the last event. List updated The list of ARTrackedImages updated since the last event. List removed The list of ARTrackedImages removed since the last event. 【引用元】:Struct ARTrackedImagesChangedEventArgs addedとupdatedの使い道は理解できたのですが、removedがいまいち使い道がわかりませんでした。 The list of ARTrackedImages removed since the last event.という説明から、画像をロストした時?かと思い、 その前提で実装を組んでみましたが、何をやっても呼ばれずです。 使い道等知ってる方いたら教えてください。 TrackingState 先ほど画像をロストした時について実装を試みたと書きましたが、 それについてはTrackingStateが有効でした。 ARTrackedImage(認識した画像)から三種類取得できます。 TrackingState Description Limited Some tracking information is available, but it is limited or of poor quality. None Not tracking. Tracking Tracking is working normally. 【引用元】: Enum TrackingState 簡単に言うとLimitedは認識精度が低い時、Noneは非認識時、Trackingは認識した時 といった感じです。 おわりに 画像マーカーを動かすと、再認識とロストを繰り返してARオブジェクトがカクカクし始めるので、 平面に固定することを前提として使った方がいいかも と色々と実験しながら思いました。 参考リンク AR Foundation Improved Image Tracking - Multiple Objects/Images - Unity Augmented Reality/AR
- 投稿日:2021-08-09T17:03:42+09:00
【ARFoundation】深度画像からポイントクラウドをリアルタイムに表示する【Unity】
概要 UnityのARFoundationのDepth画像とRGB画像を使用し、パーティクルでPointCloudの表示を実装する。 リポジトリ : https://github.com/AzetaTakuya/DepthToPointCloudForARFoundation ※本記事は実装の説明的な立ち位置。詳しくはリポジトリ見てもらった方が早そう。 環境 MacBook Air (M1, 2020) iPad Pro 11インチ(第2世代) Unity 2020.3.1f1 ARFoundation 4.1.0 実装 RGB画像を取得する ARCameraManagerのコールバックから、カメラ画像を取得する 参考:https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@4.1/manual/cpu-camera-image.html [SerializeField] private ARCameraManager CameraManager; public Texture2D RGB_Texture { get; private set; } private void OnEnable() { CameraManager.frameReceived += OnARCameraFrameReceived; } private void OnDisable() { CameraManager.frameReceived -= OnARCameraFrameReceived; } unsafe void OnARCameraFrameReceived(ARCameraFrameEventArgs eventArgs) { if (!CameraManager.TryAcquireLatestCpuImage(out XRCpuImage image)) return; var conversionParams = new XRCpuImage.ConversionParams { inputRect = new RectInt(0, 0, image.width, image.height), outputDimensions = new Vector2Int(image.width / 2, image.height / 2), outputFormat = TextureFormat.RGBA32, transformation = XRCpuImage.Transformation.MirrorY }; int size = image.GetConvertedDataSize(conversionParams); var buffer = new NativeArray<byte>(size, Allocator.Temp); image.Convert(conversionParams, new IntPtr(buffer.GetUnsafePtr()), buffer.Length); image.Dispose(); if (RGB_Texture == null) { RGB_Texture = new Texture2D(conversionParams.outputDimensions.x, conversionParams.outputDimensions.y, conversionParams.outputFormat, false); } RGB_Texture.LoadRawTextureData(buffer); RGB_Texture.Apply(); buffer.Dispose(); } Depth画像を取得する AROcculusionManagerからenvironmentDepthTextureを取得する。 [SerializeField] private AROcclusionManager OcclusionManager; private void XXX() { var env_Texture = OcclusionManager.environmentDepthTexture; } RGB画像のサイズをDepth画像と合わせる RGB画像とDepth画像のサイズが一致していた方が扱い易い為Graphics.BlitをかけてRenderTextureに出力する。 private RenderTexture RGB_RT; private void XXX() { RGB_RT = RenderTexture.GetTemporary(Width, Height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); RGB_RT.Create(); Graphics.Blit(RGB_Texture, RGB_RT); } ShaderでDepth画像を加工する ARFoundationSample内にあるUnlit/DepthGradientシェーダーを使用してDepth画像を加工する。 [SerializeField] private Material EnvDepth_Material; private readonly int MaxDistanceId = Shader.PropertyToID("_MaxDistance"); private readonly int MinDistanceId = Shader.PropertyToID("_MinDistance"); public float EnvDepth_MinDistance { get; private set; } public float EnvDepth_MaxDistance { get; private set; } private RenderTexture EnvDepth_RT; private void OnEnable() { EnvDepth_MinDistance = 0; EnvDepth_MaxDistance = 5; EnvDepth_Material.SetFloat(MaxDistanceId, EnvDepth_MaxDistance); EnvDepth_Material.SetFloat(MinDistanceId, EnvDepth_MinDistance); } private void Update() { var env_Texture = OcclusionManager.environmentDepthTexture; if (EnvDepth_RT == null) { Width = env_Texture.width; Height = env_Texture.height; EnvDepth_RT = RenderTexture.GetTemporary(Width, Height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); EnvDepth_RT.Create(); } Graphics.Blit(env_Texture, EnvDepth_RT, EnvDepth_Material); } RenderTextureからピクセル情報を取得する RenderTextureから、Texture2Dにピクセルを反映しGetPixel32()によりColor32配列を取得。 その後、Color32配列よりbyte配列の方が保存等で扱い易い為変換する。 private Texture2D HubTexture; public byte[] RGB_Buffer { get; private set; } public byte[] EnvDepth_Buffer { get; private set; } private void Update() { if (HubTexture == null) HubTexture = new Texture2D(Width, Height); var currentRT = RenderTexture.active; RenderTexture.active = RGB_RT; HubTexture.ReadPixels(new Rect(0, 0, Width, Height), 0, 0); HubTexture.Apply(); var colors = HubTexture.GetPixels32(); RGB_Buffer = Color32ArrayToByteArray(colors); RenderTexture.active = EnvDepth_RT; HubTexture.ReadPixels(new Rect(0, 0, Width, Height), 0, 0); HubTexture.Apply(); colors = HubTexture.GetPixels32(); EnvDepth_Buffer = Color32ArrayToByteArray(colors); RenderTexture.active = currentRT; byte[] Color32ArrayToByteArray(Color32[] colors) { if (colors == null || colors.Length == 0) return null; int lengthOfColor32 = Marshal.SizeOf(typeof(Color32)); int length = lengthOfColor32 * colors.Length; byte[] bytes = new byte[length]; GCHandle handle = default(GCHandle); try { handle = GCHandle.Alloc(colors, GCHandleType.Pinned); IntPtr ptr = handle.AddrOfPinnedObject(); Marshal.Copy(ptr, bytes, 0, length); } finally { if (handle != default(GCHandle)) handle.Free(); } return bytes; } } Depth画像から距離を取得する Depth画像のbyte配列から距離情報のfloat配列を生成する。 Depth画像のbyte配列をColor32配列に割り当て、Color32のRGBをHSVに変換し、Hの値によって距離を割り出す。 void XXX() { var env_Colors = new Color32[env_Buffer.Length / 4]; var env_Distances = new float[env_Buffer.Length / 4]; for (int i = 0; i < EnvDepth_Buffer.Length / 4; i++) { int index = i * 4; env_Colors[i].r = EnvDepth_Buffer[index + 0]; env_Colors[i].g = EnvDepth_Buffer[index + 1]; env_Colors[i].b = EnvDepth_Buffer[index + 2]; env_Colors[i].a = EnvDepth_Buffer[index + 3]; Color.RGBToHSV(env_Colors[i], out float H, out float S, out float V); float distance = 0; if (H > 0.7f && H < 0.85f) { distance = 0; } else if (H >= 0 && H <= 0.7f) { distance = (0.7f - H) * (maxDistance - minDistance) / (0.7f + 0.15f); } else if (H >= 0.85f && H <= 1) { distance = (1.7f - H) * (maxDistance - minDistance) / (0.7f + 0.15f); } env_Distances[i] = distance; } } パーティクルを表示する RGBピクセル情報と距離情報を使用してパーティクルを生成する。 パーティクルのGameObjectは常にカメラの正面にある様にする。 ※RGB画像とDepth画像の向きが違うことに注意が必要 [SerializeField] private Transform ARCamera; [SerializeField] private ParticleSystem ParticleSystem; private ParticleSystem.Particle[] Particles; private readonly float ParticleSize = 0.025f; private void Awake() { GameObject particleObj = ParticleSystem.gameObject; particleObj.transform.SetParent(ARCamera); particleObj.transform.localPosition = Vector3.forward; } void XXX() { Array.Resize(ref Particles, pixelNum); int index = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float distance = env_Distances[index]; var _x = width - x - 1; int colorIndex = y * width + _x; //x軸反転 var r = RGB_Buffer[colorIndex * 4 + 0]; var g = RGB_Buffer[colorIndex * 4 + 1]; var b = RGB_Buffer[colorIndex * 4 + 2]; var a = RGB_Buffer[colorIndex * 4 + 3]; var particle = Particles[index]; particle.position = new Vector3(x * 0.01f, y * -0.01f, distance); var color = new Color32(r, g, b, a); particle.startColor = color; particle.startSize = ParticleSize; Particles[index] = particle; index++; } } float posX = -width * 0.01f / 2; float posY = height * 0.01f / 2; float posZ = ParticleSystem.gameObject.transform.localPosition.z; ParticleSystem.gameObject.transform.localPosition = new Vector3(posX, posY, posZ); ParticleSystem.SetParticles(Particles, Particles.Length); } まとめ こんな感じで動く。 プロジェクトのアプリは画面をタップすればRGBとPointCloudが切り替わるように実装した。
- 投稿日:2021-08-09T15:14:57+09:00
SOLID原則を勉強する その4~インターフェース分離の原則(ISP)~
目次 SOLID原則を勉強する その1~単一責任の原則 (SRP)~ SOLID原則を勉強する その2~オープン・クローズド原則(OCP)~ SOLID原則を勉強する その3~リスコフの置換原則(LSP)~ SOLID原則を勉強する その4~インターフェース分離の原則(ISP)~ ←いまここ SOLID原則を勉強する その5~オ 依存性逆転の原則(DIP)~ 前置き 書籍を読んだり、ググったりして、自分に分かりやすいようにまとめた記事です。 より詳しく知りたい方は、下記の参考文献を読んでみてください。 参考文献 Clean Architecture 達人に学ぶソフトウェアの構造と設計 | Amazon Adaptive Code ~ C#実践開発手法 | Amazon C#の設計の基本【SOLID原則】まとめ Unity開発で使える設計の話+Zenjectの紹介 C# で SOLID の原則に違反する危険性 インターフェース分離の原則(ISP) クライアントが利用しないメソッドの実装を強制してはいけない 「不必要なメソッドなのに実装しなければならない」を防ぐ インターフェースには最小限のものだけ定義するべき コード例 それぞれの敵の行動パターンを管理する Shadow クラス、 Gigas クラス、 DarkInferno クラスを作成します。 (敵の元ネタはキングダムハーツです。分からない人はすみません…) それぞれのクラスは、以下の行動を行います。 Shadow Gigas DarkInferno 攻撃 ○ ○ ○ 歩行 ○ ○ × 飛行 × ○ ○ before IEnemy インターフェースを用意 敵クラスは全てこのインターフェースを実装する IEnemy.cs public interface IEnemy { void Attack(); void Walk(); void Fly(); } Shadow.cs using UnityEngine; public class Shadow : IEnemy { public void Attack() { Debug.Log("シャドウの攻撃"); } public void Fly() { // 空を飛ばないので、このメソッドでは何もしない } public void Walk() { Debug.Log("シャドウが歩いて移動"); } } Gigas.cs using UnityEngine; public class Gigas : IEnemy { public void Attack() { Debug.Log("ギガースの攻撃"); } public void Fly() { Debug.Log("ギガースが空を飛ぶ"); } public void Walk() { Debug.Log("ギガースが歩く"); } } DarkInferno.cs using UnityEngine; public class DarkInferno : IEnemy { public void Attack() { Debug.Log("ダークインフェルノの攻撃"); } public void Fly() { Debug.Log("ダークインフェルノが空を飛んで移動"); } public void Walk() { // 歩かずに常に空を飛んで移動するので、このメソッドでは何もしない } } ダメなところ Gigas クラスは全てのメソッドを必要としているが… Shadow クラスは Fly メソッドは不要(空を飛ばないから) DarkInferno クラスは Walk メソッドは不要(歩かないから) 不要なメソッドの実装を強要している 新たな敵クラスを実装するときや、メソッドを呼び出すクライアント側も混乱してしまう After IEnemy インターフェースを IAttack, IWalk, IFly の各インターフェースに分離 敵クラスは必要なインターフェースを選んで実装する IAttack.cs public interface IAttack { void Attack(); } IWalk.cs public interface IWalk { void Walk(); } IFly.cs public interface IFly { void Fly(); } Shadow.cs using UnityEngine; public class Shadow : IAttack, IWalk { public void Attack() { Debug.Log("シャドウの攻撃"); } public void Walk() { Debug.Log("シャドウが歩いて移動"); } } Gigas.cs using UnityEngine; public class Gigas : IAttack, IFly, IWalk { public void Attack() { Debug.Log("ギガースの攻撃"); } public void Fly() { Debug.Log("ギガースが空を飛ぶ"); } public void Walk() { Debug.Log("ギガースが歩く"); } } DarkInferno.cs using UnityEngine; public class DarkInferno : IAttack, IFly { public void Attack() { Debug.Log("ダークインフェルノの攻撃"); } public void Fly() { Debug.Log("ダークインフェルノが空を飛んで移動"); } } 良いところ 不要なインターフェースの実装がなくなった 終わりに もし変なところがあったら教えて下さい。 (特にクラス図とか…)
- 投稿日:2021-08-09T13:13:32+09:00
【Unity】細胞シミュレーション(準備)
背景 生きているとはどういうことなのか。その疑問の答えに近づくために、細胞をシミュレートしたいです。まず、この細胞がどのように生きているかを確認したいです。本記事では、シミュレーションの準備として、ランダムウォークを取り上げました。 環境 win 10 pro(core i7) Unity ver 202.3.13f1 Microsoft Visual Studio Community 2019 Version 16.8.4 ①ランダムウォーク1(ブラウン運動) pic.twitter.com/10wn3ulsQ9— Asayan (@L4R2h) August 9, 2021 Randomwork.cs void Update() { Vector3 pos = this.transform.position; if (Random.Range(0, 2) == 1) pos.x += 0.01f; else pos.x += -0.01f; if (Random.Range(0, 2) == 1) pos.y += 0.01f; else pos.y += -0.01f; if (Random.Range(0, 2) == 1) pos.z += 0.01f; else pos.z += -0.01f; this.transform.position = pos; } ②ランダムウォーク2(ブラウン運動) 衝突時に結合する処理を追加した。 pic.twitter.com/wJaZWjA4xA— Asayan (@L4R2h) August 9, 2021 private FixedJoint fixedJoint; void OnCollisionEnter(Collision collision) { fixedJoint = gameObject.AddComponent<FixedJoint>(); } ③ミセルもどき pic.twitter.com/gHrdioqWio— Asayan (@L4R2h) August 9, 2021 HydrophobicPart.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class HydrophobicPart : MonoBehaviour { private FixedJoint fixedJoint; void OnCollisionEnter(Collision collision) { //疎水部分同士が衝突した時に衝突した疎水部分を結合 if((gameObject.name == "Capsule")&&(collision.gameObject.name == "Capsule")){ fixedJoint = gameObject.AddComponent<FixedJoint>(); } } } CreateObj.cs //EmptyObjにアタッチ using System.Collections; using System.Collections.Generic; using UnityEngine; public class CreateObj : MonoBehaviour { public GameObject prefabObj; void Start() { CreateObject(); } void CreateObject() { int i; float x, y, z; for (i= 0;i< 100; i++) { float phase = Time.time * 2 * Mathf.PI; x = 10*Mathf.Cos(phase); y = 1; z = 10*Mathf.Sin(phase); GameObject obj = Instantiate(prefabObj, new Vector3(x, y, z),new Quaternion(5.0f*i,0.0f,0.0f,1.0f)); } } } おわりに ミセルもどきを何とかして、ミセルに近づけたいです。 ミセルがうまくいけばベシクルにも挑戦したいです。 参考文献 ランダムウォーク 水と油が仲良く同居