- 投稿日:2020-09-23T22:50:30+09:00
M5StackでPCの中のキャラクター(ユニティちゃん)を走らせた
今回は、9月19日・20日のM5Stackハッカソンで作ったものの作り方を書きます。
作ったもの
作品名
バーチャル・ランニング
https://protopedia.net/prototype/07a9d3fed4c5ea6b17e80258dee231fa動画
【一週一創】先日の #m5stack ハッカソンで作ったものの作り方を公開します。 #UnityChan #protoout #ヒーローズリーグ #一週一創https://t.co/NT4jInmgqI pic.twitter.com/6oDnRVgDIZ
— たつや @9/18 cluster広島LT大会 (@tatsuya1970) September 23, 2020
M5Stackをポケットに忍ばせ、ランニングすると、M5Stackの加速度センサーがある閾値をこえると、PCの中のキャラクター(ユニティちゃん)が走ります。
ちょっとでもサボると、ユニティちゃんも止まります。システム
環境
- Windows 10
- Unity 2017 4.28f1
- Ruby on Rails
使ったもの
Unityアセット
- HQ Fighting Animation FREE
- JsonObject
構成図
M5Stackの加速度センサーがある閾値を超えると「走ってるフラグ1」、止まったら「走ってるフラグゼロ」をRuby on Railsで構築したデータベースMySQLへPOST通信し保存します。
PC上のUnityはフレーム毎にMySQLにGET通信し、今の状態を確認、走ってるフラグが1ならユニティちゃんを走らせ、フラグがゼロならユニティちゃんを止まらせます。
言い訳
おそらく、こんな非効率なやり方よりももっと良いやり方があるはずだと思います。
走ってるか走ってないかを確認するだけのために、1フレーム毎にGET通信したり、走った走らないでデータベースにPOST書き込みなんて、すぐにHerokuのリクエスト制限超過になります。しかしながら、私にはあまり通信やデータベースに知識がなく、ハッカソンという限られた時間の範囲内では、このような非効率だけど自分の知識を総動員して一応動くことができるものを作ることを優先にしました。
3年前に自分が構築したRuby on Rails + MySQL のデータベースがHeroku上に生きていたので、その生きているRuby on Railsのプログラムに、ルーティングを付け加えました。ということで、これはハッカソン用の暫定措置のシステムですので、もっと良い方法がありましたら、ヒントだけでもいただければ幸いです。
コード
M5Stack側
ポイントはHttpRequest
注意ポイントを別途こちらに書いてます。M5Stack UIFlowでHTTP(POST)通信
https://qiita.com/tatsuya1970/items/737b9af78191846d4556サーバー側
Ruby on Rails + mySQLをHeroku内に構築しています。(詳細は割愛)
ちょっと古いですが、3年前に私が書いたブログが参考になると思います。
(多分今でも大丈夫だと思います・・・)
ルーティング
routes.rbRails.application.routes.draw do post '/m5hack/' => 'uchiwas#m5hack_post' get '/m5hack/' => 'uchiwas#m5hack_get' endHTTPリクエストに対する処理
MfivesController.rbclass MfivesController < ApplicationController require 'net/http' require 'uri' require 'json' def m5hack_post # mySQlにMfiveというデータベース、idが500(500という数字に今はない)のところにrunningFlagというカラムを設定している message =params[:runningFlag] mfive = Mfive.find_by(id:500) mfive.runningFlag = message mfive.save end def m5hack_get mfive = Mfive.find_by(id:500) render :json => mfive end endUnity側
ポイントはHTTP通信です。
こちらに記事を書いてます。
UnityでHTTP(GET)通信によるJSONデータ取得
https://qiita.com/tatsuya1970/items/afa299a260d09c4672aaユニティちゃんの格闘モーションのアセット
HQ Fighting Animation FREE
についているプログラムにHTTP通信のプログラムを入れただけです。IdleChanger.csusing UnityEngine; using System.Collections; using System.Collections.Generic; // // アニメーション簡易プレビュースクリプト // 2015/4/25 quad arrow // // Require these components when using this script [RequireComponent(typeof(Animator))] public class IdleChanger : MonoBehaviour { private Animator anim; // Animatorへの参照 private AnimatorStateInfo currentState; // 現在のステート状態を保存する参照 private AnimatorStateInfo previousState; // ひとつ前のステート状態を保存する参照 // Use this for initialization void Start() { // 各参照の初期化 anim = GetComponent<Animator>(); currentState = anim.GetCurrentAnimatorStateInfo(0); previousState = currentState; } void Update() { StartCoroutine(connect("URL")); } IEnumerator connect(string url) { WWW www = new WWW(url); yield return www; JSONObject json = new JSONObject(www.text); JSONObject runningFlag = json.GetField("runningFlag"); if (runningFlag.n > 1) { anim.SetBool("Run", true); } else { anim.SetBool("Run", false); } } }
- 投稿日:2020-09-23T21:56:03+09:00
【Unity】動画で見るSlerp
Vector3のSLerpを動画にしました。
SLerpをなんとなく使っている人に見ていただけると幸いです。
動画で見るLerpも投稿していますのそちらも参考に。動画
投稿用
— TANUKEINA (@FH1b4mzzirXPoiM) September 13, 2020
Slerpサンプル1 pic.twitter.com/mHnTSWlUk7投稿用
— TANUKEINA (@FH1b4mzzirXPoiM) September 13, 2020
Slerpサンプル2 pic.twitter.com/tmpvOFXeS6オブジェクトの色も線形補間(Color.Lerp)によって変化させています。
Slerp(Vector3 a, Vector3 b, float t);
引数
- Vector3 a :開始点
- Vector3 b :終了点
- float t :二点の補間値
計算式は
Slerp(a,b;t) = \frac{sin[(1-t)Ω]}{sinΩ}a + \frac{sin[tΩ]}{sinΩ}ba,bの二点間を球面的に補間するということを表してるわけですが、
これだけ見るとなんだかよくわかりませんね。
この辺は私がわざわざ説明するより、先人達の記事の方が何倍もわかりやすいので以下にリンクを貼っておきます。○×つくろ〜ドットコム その57 クォータニオンを"使わない"球面線形補間
SlerpExample
SlerpExample.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class SlerpExample : MonoBehaviour { public Transform sunrise; public Transform sunset; //始点から終点までの時間 public float journeyTime = 1.0f; [System.NonSerialized] public float fracComplete; private float startTime; private void Start() { startTime = Time.time; } private void Update() { //弧の中心 Vector3 center = (sunrise.position + sunset.position) * 0.5f; //弧を中心にするため調整 center -= new Vector3(0, -1, 0); //中心を基準として円弧を補間する Vector3 riseRelCenter = sunrise.position - center; Vector3 setRelCenter = sunset.position - center; fracComplete = (Time.time - startTime) / journeyTime; transform.position = Vector3.Slerp(riseRelCenter, setRelCenter, fracComplete); transform.position += center; } }ColorLerp
Lerpの記事から少し改良しています。
ColorLerp.cssing System.Collections; using System.Collections.Generic; using UnityEngine; public class ColorLerp : MonoBehaviour { public enum LerpType { Lerp, Slerp } public LerpType lerpType; private LerpExample lerpExample; private SlerpExample slerpExample; private Material mat; private void Start() { mat = this.GetComponent<Renderer>().material; lerpExample = GetComponent<LerpExample>(); slerpExample = GetComponent<SlerpExample>(); } void Update() { switch (lerpType) { case LerpType.Lerp: mat.color = Color.Lerp(Color.blue, Color.red, lerpExample.fractionOfJourney); break; case LerpType.Slerp: mat.color = Color.Lerp(Color.blue, Color.red, slerpExample.fracComplete); break; } } }まとめ
- Slerpは球面線形補間をするメソッド
- 回転表現に使用できる
- Quaternionだと理解しにくいので動画を見て、直感的に理解してくれたら幸いです
- 投稿日:2020-09-23T21:34:24+09:00
Unity ML-agents でアルゴリズム、行動、観察を変えて学習時間を比較してみた
学習アルゴリズム、行動、観察を変更しながら時間測定してみました。
プログラムはRollerBallを参考につくっていて、こちらに挙げてあります。環境
- Windows10
- Python 3.7.9
- TensorFlow 2.3.0
- Unity 2019.4.10f1
- ML-Agent Release6
結果
一番上のグラフを表にしたものです。
時間、ステップ数は Mean Reward が 1.000 になるまで計測したものになります。
(1回しか測定していないので、数秒は誤差だと思ってください。)
アルゴリズム 行動 観察 時間(s) ステップ数(k) 備考 PPO Continuous Vector Observation 68 13 SAC Continuous Vector Observation 191 121 PPO Discrete Vector Observation 159 31 SAC Continuous Visual Observation 1177 138 PPO Discrete Raycast Observation 1318 236 ※1 ※1 学習に時間がかかりすぎたので、途中で止めました
- 投稿日:2020-09-23T18:08:53+09:00
スタンプ型デバイス(5) ~VuforiaとHoloLens2~
まえがき
前回,HoloLens2とデバイスの連携に成功した.
理想としてHoloLens2以外のデバイスでも利用できるようにしたいと考えていたが,
このデバイスをBletoothKeyboradとして扱うことで,Android,iOS,Vive,PCなどでも利用可能となった.今回はデバイスをトラッキングする手法を考えることにした.
・Bluetoothを利用した位置調整
→ Bluetoothでは数cm単位での同期ほぼ不可能
・画像処理,機械学習による識別(python)
→ python勉強中
・NuGetForUnityを用いたQRコードトラッキング
→ 試したがうまくいかなかった
・Vuforia
→ Androidで触ったしいけそう!ということで以前利用したことがあるVuforiaを使用することにした.
HoloLens1・2用のAssetがstoreにあったので,まずはHoloLens2でDemoを作ることにした.本題
HoloLens2でデバイスを活用したARコンテンツのデモを作成する
機器概要
・Windows10 Laptop
・Unity 2019.3.2f1
・HoloLens2
・Visual Stadio 2019
・Arduino IDE
・スタンプ型デバイス(M5StickC)準備
Vuforiaを利用するにはアカウントを作成する必要があります.
1. Vuforia Developer Portalにアクセス
2. Register」からアカウントを作成,ログイン
License Managerへ
3. Get Development KeyでLicense Keyを発行
※License Key(赤枠で塗りつぶしたところ)はUnityの画面で使用する
4.Target Managerからマーカーを登録する
Add Databaseをクリック
TypeはDeviceでCreate
Add Targetをクリック
Type:マーカーにしたい形状を選択,今回は平面
File:マーカーとする画像を選択,今回は特徴点がわかりやすいQRコードを使用
Witdth:マーカーのサイズを指定,1=1mなので今回は0.01=1cmを指定Addするとマーカーが追加される
Ratingは特徴点の多さ,認識のしやすさ
Unity内で利用するためには,
Download Database
をクリック
必要なtargetを選択して,.unitypackageファイルをDL
これをプロジェクト内にImportすることで利用可能となる手順
1. Unityでプロジェクトを作成
2. HoloLens2用の設定に変更する
・Universal Windows PlatformにSwitch Platformをクリック
・MRTK Project ConfiguratorでApplyをクリック
・TextMeshProでImport TMP Essentialsをクリック
・PlayerのXR Settingsを確認する
3. Asset Storeから
Vuforia Hololens 1+2 Sample
をimport※バージョンが違うかもしれないが構わず,Install/Upgrade
エラーも無視でOK(たぶん)4. Sampleシーンを試してみる
HoloLens2でVuforiaが動くことを確認する
Assets/SampleResources/Scenes
を開くとサンプルがあるのでこれらをビルド
出てきたファイルをVisual StadioからHoloLens2にビルド
HoloLens2でVuforia使ってみた#HoloLens2 #Vuforia #Unity pic.twitter.com/YBRgX42D37
— ゆーま (@sagirin262) September 17, 20205. 自作のシーンを作成
まず自作のマーカーを利用するためには発行したLicense Keyを入力する
Assets/Resources/VuforiaConfiguration
を開く
App License Key
にLicense Keyをコピペ
これでLicenseが認証される次にDLした自作したマーカーを入れる
パッケージをimportする
これで後述のDatabase
やImage Target
から選択できるようになるデモ用のシーンを作成する
・0.Menu
・1.Scene1
・2.Scene2
・3.Scene3必要なものをSampleから持ってくる
※MixedRealityToolKit
とMixedRaaliyPlayspace
はそれぞれのシーンで新たに作成した方が良い
作成の仕方はMixedReality/Add to Scene and Configure...
をクリックする
Menu
・SpeechInputHandler
← 音声認識に関するもの
・Lights
← 光源
・UIContent
← Menuのオブジェクト,シーン遷移ができる
Button
のFrontPlate
にそれぞれScene Loader
というscriptがアタッチされている
この変数を変更することでどのシーンに移行するか変更できる(例では3)
シーンの番号はBuild Settingsを参照
ビルド対象に選択した順に0から番号を振られる
また,ButtonのテキストはSectionTitle
のTextMeshPro-Text
,マークはQuad
のMesh Render
のMaterials
から変更可能
Scene1,2,3
・SpeechInputHandler
← 音声認識に関するもの
・Lights
← 光源
・UIContent
← 説明のパネル(Homeボタン付き),Menuへの遷移ができる
・VuforiaContent
← ARtargetと出現するobject
VuforiaContent
にはImageTargetが入っている.
ImageTarget
にはImage Target Behaviour
というscriptがアタッチされている
この内容を変更することでARマーカーとなる画像等を変更することが可能である
Type:Vuforiaに登録したDatabaseを利用するのでFrom Database
Database:自分の作成したDatabase
Image Target:登録したTarget
ImageTarget
の下にAR表示させたいobjectを配置することでマーカーを認識したときにobjectが表示される6. プログラムを作成
今回作成したいものはマーカーをトラッキングして,その位置にobjectを配置するプログラムである
次にマーカーを配置
マーカーとなるobjectはCreate/Vuforia Engine/Image Taget
で作成できる
マーカーの設定はImage Target Behaviour
から行うマーカーの下に,位置を伝える
Position_Maker
と認識したことがわかるようにPlane
を置く
初期位置の調整用のscriptを作成
Tracking.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class Tracking : MonoBehaviour { public GameObject imageTarget; //初期の位置を設定 public float x = 0.0f; public float y = 0.0f; public float z = 0.0f; private void Start() { Vector3 target_Pos = new Vector3(x, y, z); this.GetComponent<Transform>().localPosition = target_Pos; } }デバイスの入力処理,マーカーのPositionを認識してそのy=0の位置にオブジェクトを配置するscript
スイッチで生成,ButtonAで種類変更,ButtonBで出したオブジェクトを全消しDevice_Controller.csusing System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class Device_Controller : MonoBehaviour { public GameObject[] Stamp; private int num = 0; public float tall_down = 0; private void Update() { if(Input.GetKeyDown(KeyCode.LeftControl)){ //マーカーの位置を取得 Vector3 Maker = GameObject.Find("Position_Maker").transform.position; // Debug.Log("Device: " + Maker); //マーカーの位置の下(y=0)にスタンプ Maker.y = -1.0f * tall_down; // Device_Pos.transform.position = Maker; // Transform Pos = Device_Pos.transform; // Pos = Vector3(Maker.transform.position.x, 0 , Maker.transform.position.z); Vector3 Pos = Maker; Instantiate(Stamp[num], Pos, Quaternion.identity); } else if(Input.GetKeyDown(KeyCode.Tab)){ num++; if(num >= 3) num = 0; } else if(Input.GetKeyDown(KeyCode.Escape)){ // GameObject型の配列targetsに、"stamp"タグのついたオブジェクトをすべて格納 GameObject[] targets = GameObject.FindGameObjectsWithTag("stamp"); // GameObject型の変数targetに、targetsの中身を順番に取り出す。 // foreachは配列の要素の数だけループします。 foreach (GameObject target in targets) { // 消す! target.GetComponent<Destroy>().OnDestroy(); } } } }出現させるobjectに自分を消すscriptをアタッチ(多くなりすぎると処理落ちする可能性があるため)
Destroy.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class Destroy : MonoBehaviour { public void OnDestroy() { Destroy(this.gameObject); } }
stamp
というtagを作成して,出現させるobjectを設定
これで完成
ビルドして,VisualStadioからHoloLens2にビルドする.実行例
QRコードをトラッキングしてスタンプできる?#HoloLens2 #Vuforia #Unity pic.twitter.com/FlAgvUQCfK
— ゆーま (@sagirin262) September 23, 2020トラブルシューティング
主に詰まったところを載せておく
・2回目以降のVisualStadioのビルドがうまくいかない
Unityで同じフォルダにビルドした際にVisualStadioで配置完了してもうまくいかない場合がある.
おそらく他の設定ファイルとごっちゃになってしまう場合である.
そんな時はビルド先のフォルダを新たに作成すると良い.(APP,Demo等)
・HoloLens2で同じアプリとして認識され,上書きされる(設定でアプリ名を変更しても)
別のアプリとして認識させたいときは別のプロジェクトでビルドしましょう.
ちなみにUnityのアプリとVuforiaのアプリは別もの扱いされる
あとがき
QRコードで位置をトラッキングできたのでデバイスに張り付ければ使えそう!!(なおサイズ...)
VuforiaはAndroid,iOS,HoloLens2では使えそうだし,汎用性高い?(Viveはカメラ占有されて?無理とか聞いた)
とりあえずできたので良かった.
分からないことやトラブル,エラーなどがあれば気軽にどうぞ.参考
Nuget
・QR コードの追跡
・Hololens2とQRコードについて学ぶ
Vuforia
・Unity での Vuforia Engine の使用
・HoloLensでVuforiaを、一歩ずつ進めながら、確実に動かす
・HoloLensでVuforiaを使う (Unity&MRTK 2017世代)
・【Unity】Vuforiaを使ってARを表示する手順(2019年版)
・UnityとVuforiaで始めるAR開発
- 投稿日:2020-09-23T13:56:49+09:00
Unityでモバイルでobjファイルを書き出したい人の最小コード
はじめに
今回はUnityのモデルをランタイムにobj形式に変換して出力します。Editor上やPC上で動くものはUnityが提供してくれていたりするので、こちらではモバイル環境下での実装をしていきます。
objについて簡単に
objについてはこちらに詳しく解説があるのでどうぞ。
https://yttm-work.jp/model_render/model_render_0001.htmlWavefront社が開発した3Dモデルフォーマットの1つです。
3Dモデルの中でも非常にわかりやすい構造をしているため3Dモデルを一から描画する時などの登竜門的な役割をしているとかなんとか。サブ的な知識も必要なので普通に難しいと思います。●ファイル構成
1、.obj(v:頂点情報, vn:法線情報, vt:UV情報, f:面情報)
2、.mtl(テクスチャ情報, マテリアル情報等)
3、テクスチャ画像(.png, .jpgなど)極端な話、3Dモデル描画ということであれば頂点と面情報だけでも描画できます。
今回はobj形式のコードを記述しておきます。実装方法(モバイル上で動作させたい)
実装方法はファイル構成に従ってファイルを生成し、そのファイルにObjフォーマットに従って記述していく流れになります。
using System.Collections; using System.Collections.Generic; using UnityEngine; using System.IO; public class ObjExp : MonoBehaviour { [SerializeField] private MeshFilter _obj; private string _storagePath; void Start() { //フォルダ生成 _storagePath = Application.dataPath + "/" + "TestContents"; Directory.CreateDirectory(_storagePath); //objファイル生成 var _filePath = _storagePath + "/" + Application.productName + ".obj"; StringBuilder sb = new StringBuilder(); sb.AppendLine("# OBJ File:" + Application.productName); sb.AppendLine("mtllib " + Application.productName + ".mtl"); //頂点情報 Vector3[] vertices = _obj.mesh.vertices; foreach (var child in vertices) { Vector3 v = child; v.x *= -1; sb.AppendLine("v " + v.x + " " + v.y + " " + v.z); } //法線情報 Vector3[] vertnormal = _obj.mesh.normals; foreach (var child in vertnormal) { Vector3 v = child; v.x *= -1; sb.AppendLine("vn " + v.x + " " + v.y + " " + v.z); } //UV情報 Vector2[] vertuvs = _obj.mesh.uv; foreach (var child in vertuvs) { sb.AppendLine("vt " + child.x + " " + child.y); } //面情報 int faceOrder = (int)Mathf.Clamp((_obj.gameObject.transform.lossyScale.x * _obj.gameObject.transform.lossyScale.z), -1, 1); string matName = _obj.GetComponent<MeshRenderer>().sharedMaterial.name; sb.AppendLine("usemtl " + matName); int[] triangles = _obj.mesh.GetTriangles(i); for (int t = 0; t < triangles.Length; t += 3) { int p2 = triangles[t] + 1; int p1 = triangles[t + 1] + 1; int p0 = triangles[t + 2] + 1; if (faceOrder < 0) { sb.AppendLine("f " + ConstructOBJString(p2) + " " + ConstructOBJString(p1) + " " + ConstructOBJString(p0)); } else { sb.AppendLine("f " + ConstructOBJString(p0) + " " + ConstructOBJString(p1) + " " + ConstructOBJString(p2)); } } File.WriteAllText(_storagePath + "/" + Application.productName + ".obj", sb.ToString()); } private string ConstructOBJString(int index) { string idxString = index.ToString(); return idxString + "/" + idxString + "/" + idxString; } }まとめ
今回SubMeshの情報やマテリアル情報を格納する(.mtl)ファイル生成は含めていません。
生成する際の位置、回転、スケールなども頂点に変更を加えて保存することでそのまま保存することも可能です。
取り敢えずの最低限の.Objファイル生成するためのコードでした。
- 投稿日:2020-09-23T04:32:35+09:00
Mac + Unity + Visual Studio Code でデバッガを動かすまで
はじめに
久しぶりに Unity を触ってみようと思い立ったは良いものの、デバッグ実行の仕方も分からなくなっていた(以前は MonoDevelop を使っていました)ので、改めて調べながら動くところまで漕ぎ着けたので情報を整理して残します。
また、大まかな流れは以下だけです。
1. Unity 側で Visual Studio Code との紐付けをする
2. Visual Studio Code でデバッガ用の拡張機能を入れ、プロジェクトを読み込み、実行状態にする動作環境
- macOS Catalina (10.15.5)
- Unity (2019.2.12f1)
- Visual Studio Code (1.49.1)
- C# for Visual Studio Code (1.23.2)
- Unity Debugger Extension for Visual Studio Code (2.7.5)
- .NET Core 3.1 SDK
前提条件
- Unity のインストールが終わっていること
- Visual Studio Code のインストールが終わっていること
- 適当な Unity のプロジェクトがあること
私は Unity NavMesh : Basics のサンプルプロジェクトを使いました。
https://learn.unity.com/tutorial/unity-navmeshUnity 側の設定
- [Unity] -> [Preferences...] -> [External Tools] -> [External Script Editor] のプルダウンメニューから Visual Studio Code を選択する。
Visual Studio Code 側の設定
左側にある Extensions タブ(四角いブロックみたいなアイコン)を選択し、
C# for Visual Studio Code
とUnity Debugger Extension for Visual Studio Code
を検索し、インストールする。(※図はインストール済みの状態。また、私の環境では .NET 系の警告が出ていたため、指示に従って .NET Core 3.1 SDK Core 3.1 SDK も合わせてインストールしています)
左側にある Run タブ(虫&再生ボタンみたいなアイコン)を選択し、Open a file~ の部分をクリックして、Unity のプロジェクトを読み込ませる。
先の Unity NavMesh のサンプルプロジェクトを読み込むとこんな感じになりました。
引き続き Run タブから、create a launch.json file. の部分をクリックして、Unity Debugger を選び launch.json を作成する。
作成に成功すると、Run タブの UI が変化し、デバッグ実行可能な状態となるので、図にある「▷マーク」をクリックして実行します。(※Unity アプリが立ち上がる訳ではないので、Unit 側は別途立ち上げ・実行する必要があります)
デバッグ実行中はエディタ上によくあるステップ実行等のボタンが表示され、
左から「実行/一時停止」「ステップオーバー」「ステップイン」「ステップアウト」「再起動」「停止」
実行例
以上でデバッガを動作させるまでの手順は完了です。
ここからは、実際にブレークポイントを貼って動かした際の例となりますので、不要な方はここで読み終えて頂ければと思います。
- 以下は Unity NavMesh のサンプル実行画面(画面をクリックするとレイを飛ばして、筒状のキャラクターがその位置まで移動すると言うものです) 画面をクリックした時に通過する場所に貼ったブレークポイントでしっかり止まってくれました。 変数の中身もちゃんと覗けているようです。 という事で、最低限動作するところまでは確認できました。
おわりに
Visual Studio Code には Unity 用の便利な拡張機能があるようなので、少しずつ試してみたいと思います。
拡張機能の検索欄に unity と打ち込むだけでも相当数出てくるので、どれが良いものなのか判断に困るのですが..。参考文献
UnityでVisual Studio Codeを使用できるようにするまでの手順
Unity での C# コードのデバッグ(リファレンス)
https://code.visualstudio.com/docs/editor/debugging#_global-launch-configuration
- 投稿日:2020-09-23T01:53:41+09:00
MagicLeapで任意の平面にオブジェクトを置く方法
ここの記事の続き : MagicLeapで床にオブジェクトを配置する方法
基本的な開発環境やシーンの構成は同じ
開発環境
Unity : 2019.3.7f1
LuminOS : 0.98.11, APILevel 8
MagicLeap : UnitySDK 0.24.1
MagicLeap : ToolKit 特にバージョン表記等はないので現時点(2020/09/23)での最新今回開発したアプリのリポジトリはこちら
PlaneCheckシーンにサンプルが配置してあります
完成するもの
任意の平面を判定するやつ pic.twitter.com/bJ8k1Udb5k
— 松本隆介 (@matsumotokaka11) September 22, 2020
下準備
この辺りは前回の記事と同様なので飛ばしても大丈夫です
ProjectSettings > MagicLeap > ManifestSettings
にて以下の項目にチェックを入れました
- ControllerPose
- LowLatencyLightwear
- WorldReconstruction
シーンの構成
基本的には前回とはあまり変わってませんがどの平面を判定しているかを確認するためRuntimeConsoleを新たに追加しました
RuntimeConsoleは
MagicLeap-Tools > Prefabs > Debugging
にあるプレハブをシーン上に配置していますPlaneCheckerオブジェクトにアタッチしているスクリプトで参照するので紐づけます
スクリプト
前回作成したFloorCheckerをさらに改造したPlaneCheckerを作成します
using System.Collections; using System.Collections.Generic; using UnityEngine; #if PLATFORM_LUMIN using UnityEngine.XR.MagicLeap; #endif namespace PlaneCheck { /// <summary> /// MagicLeapToolsのFloorOnPlaceを改造したクラス. /// 任意の平面を判定する. /// </summary> public class PlaneChecker : MonoBehaviour { readonly float HeadLocationIdleThreshold = 0.003f; readonly float HeadRotationIdleThreshold = .3f; readonly int HistoryCount = 5; readonly float HeadIdleRequiredDuration = .2f; public Vector3 Location { get; private set; } List<Vector3> headLocationHistory; List<Quaternion> headRotationHistory; float headLocationVelocity; float headRotationVelocity; Transform mainCamera; bool headLocationIdle; bool headRotationIdle; bool headTemporarilyIdle; bool headIdle; bool placementValid; void Awake() { mainCamera = Camera.main.transform; if (FindObjectOfType<MLSpatialMapper>() == null) { Debug.LogError("PlaceOnFloor requires and instance of the MLSpatialMapper in your scene."); } } void OnEnable() { headLocationHistory = new List<Vector3>(); headRotationHistory = new List<Quaternion>(); } void Update() { if (Time.frameCount < 3) { return; } HeadActivityDetermination(); } IEnumerator HeadIdleTimeout() { yield return new WaitForSeconds(HeadIdleRequiredDuration); headIdle = true; } void HeadActivityDetermination() { //history: headLocationHistory.Add(mainCamera.position); if (HistoryCount < headLocationHistory.Count) headLocationHistory.RemoveAt(0); headRotationHistory.Add(mainCamera.rotation); if (HistoryCount < headRotationHistory.Count) headRotationHistory.RemoveAt(0); //location velocity: if (headLocationHistory.Count == HistoryCount) { headLocationVelocity = 0; for (int i = 1; i < headLocationHistory.Count; i++) { headLocationVelocity += Vector3.Distance(headLocationHistory[i], headLocationHistory[i - 1]); } headLocationVelocity /= headLocationHistory.Count; //idle detection: if (headLocationVelocity <= HeadLocationIdleThreshold) { if (!headLocationIdle) { headLocationIdle = true; } } else { if (headLocationIdle) { headLocationIdle = false; } } } //rotation velocity: if (headRotationHistory.Count == HistoryCount) { headRotationVelocity = 0; for (int i = 1; i < headRotationHistory.Count; i++) { headRotationVelocity += Quaternion.Angle(headRotationHistory[i], headRotationHistory[i - 1]); } headRotationVelocity /= headRotationHistory.Count; //idle detection: if (headRotationVelocity <= HeadRotationIdleThreshold) { if (!headRotationIdle) { headRotationIdle = true; } } else { if (headRotationIdle) { headRotationIdle = false; } } } //absolute idle head determination: if (headLocationIdle && headRotationIdle) { if (!headTemporarilyIdle) { headTemporarilyIdle = true; StartCoroutine(HeadIdleTimeout()); } } else { if (headTemporarilyIdle) { headIdle = false; headTemporarilyIdle = false; StopCoroutine(HeadIdleTimeout()); } } } /// <summary> /// 指定したRayの位置に任意の面があるか否か、ある場合はその座標も返す. /// </summary> /// <param name="ray"></param> /// <param name="surfaceType"></param> /// <returns></returns> public (bool, Vector3) LookingAtFloorDetermination( Ray ray, MagicLeapTools.SurfaceType surfaceType) { RaycastHit hit; if (Physics.Raycast(ray, out hit)) { MagicLeapTools.SurfaceType surface = MagicLeapTools.SurfaceDetails.Analyze(hit); if (surface == surfaceType) { Location = hit.point; placementValid = true; return (true, Location); } else { placementValid = false; return (false, Vector3.zero); } } else { placementValid = false; return (false, Vector3.zero); } } } }
この部分で任意の平面かの判定をとっています
/// <summary> /// 指定したRayの位置に任意の面があるか否か、ある場合はその座標も返す. /// </summary> /// <param name="ray"></param> /// <param name="surfaceType"></param> /// <returns></returns> public (bool, Vector3) LookingAtFloorDetermination( Ray ray, MagicLeapTools.SurfaceType surfaceType) { RaycastHit hit; if (Physics.Raycast(ray, out hit)) { MagicLeapTools.SurfaceType surface = MagicLeapTools.SurfaceDetails.Analyze(hit); if (surface == surfaceType) { Location = hit.point; placementValid = true; return (true, Location); } else { placementValid = false; return (false, Vector3.zero); } } else { placementValid = false; return (false, Vector3.zero); } }判定することが出来る平面はMagicLeapToolsのSurfaceDetils.csに定義されています
今回のサンプルでは床、壁、天井の三種類を判定することにしました
PlaneCheckerを利用するPlaneCheckOnPlaceContent.cs
基本的な構成は前回のFloorCheckOnPlaceContent.csとあまり変わっていません
Bumperボタンを押下したら判定を切り替えるようにしています
ControlPointerオブジェクトのControlInputにOnBumperButtonDown()を登録
using MagicLeapTools; using UnityEngine; namespace PlaneCheck { /// <summary> /// トリガを入力したときに任意の平面を判定し、床の場合はオブジェクトを配置するサンプル. /// </summary> [RequireComponent(typeof(PlaneChecker),typeof(AudioSource))] public class PlaneCheckOnPlaceContent : MonoBehaviour { [SerializeField] AudioClip pressClip; [SerializeField] AudioClip successClip; [SerializeField] AudioClip failedClip; [SerializeField] GameObject content; [SerializeField] Pointer pointer; [SerializeField] RuntimeConsole runtimeConsole; PlaneChecker planeChecker; AudioSource audio; SurfaceType[] surfaceTypes; int index = 0; void Start() { planeChecker = GetComponent<PlaneChecker>(); audio = GetComponent<AudioSource>(); // RuntimeConsoleに自分が指定した文字列だけ表示したい. runtimeConsole.errors = false; runtimeConsole.logs = false; runtimeConsole.warnings = false; // 今回はこの三種類のみをチェック. surfaceTypes = new[] { SurfaceType.Floor, // 床. SurfaceType.Wall, // 壁. SurfaceType.Ceiling, // 天井. }; } public void OnBumperButtonDown() { index = (int)Mathf.Repeat(index + 1, surfaceTypes.Length); runtimeConsole.logText.text = surfaceTypes[index].ToString(); } public void OnTriggerDown() { audio.PlayOneShot(pressClip); (bool onSurfaceType, Vector3 pos ) result = planeChecker.LookingAtFloorDetermination(new Ray(pointer.Origin, pointer.Direction), surfaceTypes[index]); if (result.onSurfaceType) { audio.PlayOneShot(successClip); content.transform.position = result.pos; } else { audio.PlayOneShot(failedClip); } } } }
完成
これでデプロイ or ZeorIterationで確認すれば↓の様な動作になります
ただしRuntimeConsoleは実機にデプロイ時にしか動作しないので実機での確認のほうがどの平面を判定しているかがわかりやすいです任意の平面を判定するやつ pic.twitter.com/bJ8k1Udb5k
— 松本隆介 (@matsumotokaka11) September 22, 2020
感想
これで任意の平面にオブジェクトを配置したりできるようになりました
なんか家具の配置確認アプリ的なもので天井にランプをつるすとか、壁に絵を飾るとかの確認に使うのがメイン所の使い方かな?