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

M5StackでPCの中のキャラクター(ユニティちゃん)を走らせた

今回は、9月19日・20日のM5Stackハッカソンで作ったものの作り方を書きます。

0437082cb1c2c55124054d3bb3c3b90e.png

作ったもの

作品名

バーチャル・ランニング
https://protopedia.net/prototype/07a9d3fed4c5ea6b17e80258dee231fa

動画



M5Stackをポケットに忍ばせ、ランニングすると、M5Stackの加速度センサーがある閾値をこえると、PCの中のキャラクター(ユニティちゃん)が走ります。
ちょっとでもサボると、ユニティちゃんも止まります。

システム

環境

  • Windows 10
  • Unity 2017 4.28f1
  • Ruby on Rails

使ったもの

Unityアセット

  • HQ Fighting Animation FREE
  • JsonObject

構成図

2020-09-22 22.48.38.png

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

Image from Gyazo

サーバー側

Ruby on Rails + mySQLをHeroku内に構築しています。(詳細は割愛)

ちょっと古いですが、3年前に私が書いたブログが参考になると思います。
(多分今でも大丈夫だと思います・・・)


ルーティング

routes.rb
Rails.application.routes.draw do

  post '/m5hack/' => 'uchiwas#m5hack_post'
  get '/m5hack/' => 'uchiwas#m5hack_get'

end

HTTPリクエストに対する処理

MfivesController.rb
class 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

end

Unity側

ポイントはHTTP通信です。
こちらに記事を書いてます。
UnityでHTTP(GET)通信によるJSONデータ取得
https://qiita.com/tatsuya1970/items/afa299a260d09c4672aa

ユニティちゃんの格闘モーションのアセット
HQ Fighting Animation FREE
についているプログラムにHTTP通信のプログラムを入れただけです。

IdleChanger.cs
using 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);
        }
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】動画で見るSlerp

Vector3のSLerpを動画にしました。
SLerpをなんとなく使っている人に見ていただけると幸いです。
動画で見るLerpも投稿していますのそちらも参考に。

動画

オブジェクトの色も線形補間(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Ω}b

a,bの二点間を球面的に補間するということを表してるわけですが、
これだけ見るとなんだかよくわかりませんね。
この辺は私がわざわざ説明するより、先人達の記事の方が何倍もわかりやすいので以下にリンクを貼っておきます。

○×つくろ〜ドットコム その57 クォータニオンを"使わない"球面線形補間

SlerpExample

SlerpExample.cs
using 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.cs
sing 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だと理解しにくいので動画を見て、直感的に理解してくれたら幸いです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity ML-agents でアルゴリズム、行動、観察を変えて学習時間を比較してみた

bandicam 2020-09-23 20-54-03-158.jpg

学習アルゴリズム、行動、観察を変更しながら時間測定してみました。
プログラムは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 学習に時間がかかりすぎたので、途中で止めました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スタンプ型デバイス(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へ
image.png
3. Get Development KeyでLicense Keyを発行
image.png
image.png
※License Key(赤枠で塗りつぶしたところ)はUnityの画面で使用する
4.Target Managerからマーカーを登録する
image.png
Add Databaseをクリック
image.png
TypeはDeviceでCreate
image.png
Add Targetをクリック
image.png
Type:マーカーにしたい形状を選択,今回は平面
File:マーカーとする画像を選択,今回は特徴点がわかりやすいQRコードを使用
Witdth:マーカーのサイズを指定,1=1mなので今回は0.01=1cmを指定

Addするとマーカーが追加される
Ratingは特徴点の多さ,認識のしやすさ
image.png

Unity内で利用するためには,Download Databaseをクリック
必要なtargetを選択して,.unitypackageファイルをDL
これをプロジェクト内にImportすることで利用可能となる

手順

1. Unityでプロジェクトを作成
2. HoloLens2用の設定に変更する

・Universal Windows PlatformにSwitch Platformをクリック
image.png
・MRTK Project ConfiguratorでApplyをクリック
image.png
・TextMeshProでImport TMP Essentialsをクリック
image.png
・PlayerのXR Settingsを確認する
image.png

3. Asset StoreからVuforia Hololens 1+2 Sampleをimport

※バージョンが違うかもしれないが構わず,Install/Upgrade
image.png
image.png
image.png
エラーも無視でOK(たぶん)

4. Sampleシーンを試してみる

HoloLens2でVuforiaが動くことを確認する
Assets/SampleResources/Scenesを開くとサンプルがあるのでこれらをビルド
出てきたファイルをVisual StadioからHoloLens2にビルド

5. 自作のシーンを作成

まず自作のマーカーを利用するためには発行したLicense Keyを入力する
Assets/Resources/VuforiaConfigurationを開く
image.png
App License KeyにLicense Keyをコピペ
これでLicenseが認証される

次にDLした自作したマーカーを入れる
パッケージをimportする
image.png
これで後述のDatabaseImage Targetから選択できるようになる

デモ用のシーンを作成する
・0.Menu
・1.Scene1
・2.Scene2
・3.Scene3

必要なものをSampleから持ってくる
MixedRealityToolKitMixedRaaliyPlayspaceはそれぞれのシーンで新たに作成した方が良い
 作成の仕方はMixedReality/Add to Scene and Configure...をクリックする
image.png

Menu
image.png
SpeechInputHandler ← 音声認識に関するもの
Lights ← 光源
UIContent ← Menuのオブジェクト,シーン遷移ができる
image.png
ButtonFrontPlateにそれぞれScene Loaderというscriptがアタッチされている
この変数を変更することでどのシーンに移行するか変更できる(例では3)
image.png
シーンの番号はBuild Settingsを参照
ビルド対象に選択した順に0から番号を振られる
image.png
また,ButtonのテキストはSectionTitleTextMeshPro-Text,マークはQuadMesh RenderMaterialsから変更可能
image.png
image.png

Scene1,2,3
image.png
SpeechInputHandler ← 音声認識に関するもの
Lights ← 光源
UIContent ← 説明のパネル(Homeボタン付き),Menuへの遷移ができる
VuforiaContent ← ARtargetと出現するobject
image.png
VuforiaContentにはImageTargetが入っている.
image.png
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.png
マーカーの設定はImage Target Behaviourから行う

マーカーの下に,位置を伝えるPosition_Makerと認識したことがわかるようにPlaneを置く
image.png
image.png

初期位置の調整用のscriptを作成

Tracking.cs
using 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;
    }
}

PlanePosition_Makerにアタッチ
image.png
image.png

デバイスの入力処理,マーカーのPositionを認識してそのy=0の位置にオブジェクトを配置するscript
スイッチで生成,ButtonAで種類変更,ButtonBで出したオブジェクトを全消し

Device_Controller.cs
using 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();
            }
        }
    }
}

Eventを作成してアタッチ
image.png

出現させるobjectに自分を消すscriptをアタッチ(多くなりすぎると処理落ちする可能性があるため)

Destroy.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Destroy : MonoBehaviour
{
    public void OnDestroy()
    {
        Destroy(this.gameObject);
    }
}

stampというtagを作成して,出現させるobjectを設定
image.png

これで完成
ビルドして,VisualStadioからHoloLens2にビルドする.

実行例

トラブルシューティング

主に詰まったところを載せておく
2回目以降のVisualStadioのビルドがうまくいかない
Unityで同じフォルダにビルドした際にVisualStadioで配置完了してもうまくいかない場合がある.
おそらく他の設定ファイルとごっちゃになってしまう場合である.
そんな時はビルド先のフォルダを新たに作成すると良い.(APP,Demo等)
image.png

HoloLens2で同じアプリとして認識され,上書きされる(設定でアプリ名を変更しても)
別のアプリとして認識させたいときは別のプロジェクトでビルドしましょう.
ちなみにUnityのアプリとVuforiaのアプリは別もの扱いされる
image.png

あとがき

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開発

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityでモバイルでobjファイルを書き出したい人の最小コード

はじめに

今回はUnityのモデルをランタイムにobj形式に変換して出力します。Editor上やPC上で動くものはUnityが提供してくれていたりするので、こちらではモバイル環境下での実装をしていきます。

objについて簡単に

objについてはこちらに詳しく解説があるのでどうぞ。
https://yttm-work.jp/model_render/model_render_0001.html

Wavefront社が開発した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ファイル生成するためのコードでした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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-navmesh

Unity 側の設定

  • [Unity] -> [Preferences...] -> [External Tools] -> [External Script Editor] のプルダウンメニューから Visual Studio Code を選択する。 unity_preference.png

Visual Studio Code 側の設定

  • 左側にある Extensions タブ(四角いブロックみたいなアイコン)を選択し、C# for Visual Studio CodeUnity Debugger Extension for Visual Studio Code を検索し、インストールする。(※図はインストール済みの状態。また、私の環境では .NET 系の警告が出ていたため、指示に従って .NET Core 3.1 SDK Core 3.1 SDK も合わせてインストールしています)
    extention_tab.png

  • 左側にある Run タブ(虫&再生ボタンみたいなアイコン)を選択し、Open a file~ の部分をクリックして、Unity のプロジェクトを読み込ませる。
    add_project_before.png
    先の Unity NavMesh のサンプルプロジェクトを読み込むとこんな感じになりました。
    add_project_after.png

  • 引き続き Run タブから、create a launch.json file. の部分をクリックして、Unity Debugger を選び launch.json を作成する。
    add_launch_json_before.png
    作成に成功すると、Run タブの UI が変化し、デバッグ実行可能な状態となるので、図にある「▷マーク」をクリックして実行します。(※Unity アプリが立ち上がる訳ではないので、Unit 側は別途立ち上げ・実行する必要があります)
    add_launch_json_after.png
    デバッグ実行中はエディタ上によくあるステップ実行等のボタンが表示され、
    左から「実行/一時停止」「ステップオーバー」「ステップイン」「ステップアウト」「再起動」「停止」
    debugging_04.png

実行例

以上でデバッガを動作させるまでの手順は完了です。
ここからは、実際にブレークポイントを貼って動かした際の例となりますので、不要な方はここで読み終えて頂ければと思います。

  • 以下は Unity NavMesh のサンプル実行画面(画面をクリックするとレイを飛ばして、筒状のキャラクターがその位置まで移動すると言うものです) unity_demo.png 画面をクリックした時に通過する場所に貼ったブレークポイントでしっかり止まってくれました。 debugging_01.png 変数の中身もちゃんと覗けているようです。 debugging_02.png という事で、最低限動作するところまでは確認できました。

おわりに

Visual Studio Code には Unity 用の便利な拡張機能があるようなので、少しずつ試してみたいと思います。
拡張機能の検索欄に unity と打ち込むだけでも相当数出てくるので、どれが良いものなのか判断に困るのですが..。

参考文献

UnityでVisual Studio Codeを使用できるようにするまでの手順
Unity での C# コードのデバッグ(リファレンス)
https://code.visualstudio.com/docs/editor/debugging#_global-launch-configuration

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MagicLeapで任意の平面にオブジェクトを置く方法

ここの記事の続き : MagicLeapで床にオブジェクトを配置する方法

基本的な開発環境やシーンの構成は同じ

開発環境

Unity : 2019.3.7f1

LuminOS : 0.98.11, APILevel 8

MagicLeap : UnitySDK 0.24.1

MagicLeap : ToolKit 特にバージョン表記等はないので現時点(2020/09/23)での最新

MagicLeapToolKitのDLはこちらから

今回開発したアプリのリポジトリはこちら

PlaneCheckシーンにサンプルが配置してあります


完成するもの


下準備

この辺りは前回の記事と同様なので飛ばしても大丈夫です

ProjectSettings > MagicLeap > ManifestSettingsにて以下の項目にチェックを入れました

  • ControllerPose
  • LowLatencyLightwear
  • WorldReconstruction

Manifest.png


シーンの構成

Scene.png

基本的には前回とはあまり変わってませんがどの平面を判定しているかを確認するためRuntimeConsoleを新たに追加しました

RuntimeConsoleは MagicLeap-Tools > Prefabs > Debugging にあるプレハブをシーン上に配置しています

PlaneCheckerオブジェクトにアタッチしているスクリプトで参照するので紐づけます
PlaneChecker.png


スクリプト

前回作成した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に定義されています

今回のサンプルでは床、壁、天井の三種類を判定することにしました
SurfaceTypes.png


PlaneCheckerを利用するPlaneCheckOnPlaceContent.cs

基本的な構成は前回のFloorCheckOnPlaceContent.csとあまり変わっていません

Bumperボタンを押下したら判定を切り替えるようにしています

ControlPointerオブジェクトのControlInputにOnBumperButtonDown()を登録
BumperButton.png

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は実機にデプロイ時にしか動作しないので実機での確認のほうがどの平面を判定しているかがわかりやすいです


感想

これで任意の平面にオブジェクトを配置したりできるようになりました

なんか家具の配置確認アプリ的なもので天井にランプをつるすとか、壁に絵を飾るとかの確認に使うのがメイン所の使い方かな?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む