20190915のUnityに関する記事は8件です。

Unity+ARKit3のbody segmentation結果を配列として取得する

既知の問題(2019/9/16更新)

以下の方法は、セグメンテーションのテクスチャに限り、端末の向きを変更するとそれ以降値が変化しなくなる問題が起きています。(デプスのテクスチャは問題なし。Orientation設定は関係なし)
そのため、あまり上手い方法ではないかもしれませんので、そのつもりで利用してください。

やりたいこと

ARKit3を利用することで人物領域をセグメンテーションすることができる。
しかし、結果がTexture2Dとして取得されるので、そのままでは結果を加工しにくい。
なので、byteなどの配列として結果を取得したい。

ハマったポイント

Texure2Dなので、GetPixels()などを使えば一瞬だと思ったが、関数がうまく機能しないらしい。

原因(推定)

UnityのARKit3プラグインを確認していくと、このTexture2Dはネイティブで作られている模様。(CreateExternalTexture/UpdateExternalTextureでの作成・更新処理を発見)
Texure2Dはネイティブで作られたものをラップしているだけなので、GetPixels()などの関数が正しく機能しなかったと思われる。
参考(テラシュールブログさん):
http://tsubakit1.hateblo.jp/entry/20140104/1388830289
http://tsubakit1.hateblo.jp/category/トラブルシューティング?page=1469026740#RenderTextureをTexture2Dに流し込む

解決策

凹みTipsさんの
Low-Level Native Plugin Interface を利用してネイティブから Unity のテクスチャを高速に更新する方法を調べてみた - 凹みTips
によると、iOSにおいてはid<MTLTexture>がテクスチャの実体らしいので、以下のようなプラグインを書いて、配列として取得することで解決。
idから配列としてデータを取得する関数は以下。
https://developer.apple.com/documentation/metal/mtltexture/1516318-getbytes

/Assets/Plugins/iOS/TextureExtention.mm
#import <Metal/Metal.h>
#import <MetalKit/MetalKit.h>

extern "C" {
    void GetNativePixels(id<MTLTexture> texture, int width, int height, int sizePerRow, char *buffer) ;
}

void GetNativePixels(id<MTLTexture> texture, int width, int height, int sizePerRow, char *buffer) {
    [texture getBytes:buffer bytesPerRow:sizePerRow fromRegion:MTLRegionMake2D(0, 0, width, height) mipmapLevel:0];
}
/Assets/Plugins/TextureExtention.cs
using System;
using UnityEngine;
using System.Runtime.InteropServices;

public class TextureExtention { 
    [DllImport("__Internal")]
    public static extern void GetNativePixels(IntPtr texturePtr, int width, int height, int sizePerRow, [Out] byte[] buffer);

    public static void GetNativePixels(Texture2D humanTexture, ref byte[] buffer) {
        // humanTexture は ARKit3で取得されたセグメンテーション結果が格納されたテクスチャ
        IntPtr humanTexturePtr = humanTexture.GetNativeTexturePtr();
        int width = humanTexture.width;
        int height = humanTexture.height;
        int sizeParRow = humanTexture.width * 1; // ボディセグメンテーションではテクスチャフォーマットはR8なので 1ピクセル=1Byte
        GetNativePixels(humanTexturePtr, width, height, sizeParRow, buffer);
    }
}

補足

今回ネイティブプラグインを作って対処しましたが、Unity内でRenderTexture->Texuture2Dへの変換のようにテクスチャを本当のTexutre2Dコピーして、GetPixels()を使う方法でもできるかもしれません。
また、セグメンテーション結果ではなくデプス推定結果でも同じようにすれば大丈夫です。その際はテクスチャフォーマットがRFloatなので、その調整をする必要があります。

動作環境

Unity 2019.2.5f
Xcode 11 GM Seed

感想

GetPixels()が使えないという判断までも時間がかかったし、初めてのiOSネイティブプラグイン作成だったので色々苦労してしまいました…。

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

[Unity] 別名でシリアライズされた値のシリアライズ名を強制的に書き換える

概要

SerializeFieldのフィールド名を変えると、Unity内部ではフィールド名をキーとして値をシリアライズしているため、参照が解決できなくなり値が失われてしまう。
そこでFormerlySerializedAsAttributeを用いることで、リネーム前のシリアライズ値を利用することが可能になる。

public class MyClass : MonoBehaviour
{
    [FormerlySerializedAs("myValue")]
    string m_MyValue;
    public string myValue
    {
        get { return m_MyValue; }
        set { m_MyValue = value; }
    }
}

しかし、FormerlySerializedAsAttributeの機能はあくまで別名でシリアライズされた値を読むことだけであり、自動的にシリアライズ名のリネームを行ってくれるという気の利いた機能までは持っていない。
シリアライズ名の書き替えが行われるには、何らかの理由で対象のオブジェクトが再保存される必要がある。オブジェクトが再保存される際には新しいシリアライズ名を用いてシリアライズが行われる。

従って、全てのオブジェクトのシリアライズ名を書き換えなければFormerlySerializedAsAttributeをコード上から削除することはできない
これはあまり好ましくないため、全てのオブジェクトのシリアライズ名を強制的に書き換える方法を考える。

解決法

以下の手順によって解決ができる。

  1. 変数名を変え、FormerlySerializedAsAttributeを設定する
  2. 対象のオブジェクトを含む全てのアセットを取得する
  3. EditorUtility.SetDirtyにより再保存フラグを立てる
  4. AssetDatabase.SaveAssetsで全てのアセットを再保存する
  5. FormerlySerializedAsAttributeを削除する

SerializeFieldを持てるのはMonobehaviourとScriptableObjectの2種類あるが、それぞれで2の対処法が異なる。

ScriptableObjectの場合

ScriptableObjectの場合、必ず単体のアセットとして存在しているため、AssetDatabase.FindAssetsを使うことで簡単に全てのアセットを取得することができる。

[MenuItem("Tools/UpdateMyScriptableObject")]
public static void UpdateMyScriptableObject()
{
    // 全ての対象アセットを取得する
    var assetGUIDs = AssetDatabase.FindAssets("t:MyScriptableObject");
    foreach (var assetGUID in assetGUIDs)
    {
        var assetPath = AssetDatabase.GUIDToAssetPath(assetGUID);
        var asset = AssetDatabase.LoadAssetAtPath<MyScriptableObject>(assetPath);
        // 再保存フラグを立てる
        EditorUtility.SetDirty(asset);
    }
    // 再保存する
    AssetDatabase.SaveAssets();
}

MonoBehaviourの場合

MonoBehaviourの場合は少し厄介になる。
対象のコンポーネントを含む全てのSceneまたはPrefabを取得する必要があるが、そのためには全てのSceneとPrefabを走査する必要がある。

[MenuItem("Tools/UpdateMyMonoBehaviour")]
public static void UpdateMyMonoBehaviour()
{
    // Prefabを走査
    var prefabGUIDs = AssetDatabase.FindAssets("t:Prefab");
    foreach (var prefabGUID in prefabGUIDs)
    {
        var assetPath = AssetDatabase.GUIDToAssetPath(prefabGUID);
        var asset = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
        var component = asset.GetComponentInChildren<MyMonoBehaviour>();
        if (component != null)
        {
            // MyMonoBehaviourを持つ場合、再保存フラグを立てる
            EditorUtility.SetDirty(asset);
        }
    }
    AssetDatabase.SaveAssets();

    // Sceneを走査
    var sceneGUIDs = AssetDatabase.FindAssets("t:Scene");
    foreach (var sceneGUID in sceneGUIDs)
    {
        var scenePath = AssetDatabase.GUIDToAssetPath(sceneGUID);
        var scene = EditorSceneManager.OpenScene(scenePath);
        var rootGameObjects = scene.GetRootGameObjects();

        foreach (var go in rootGameObjects)
        {
            var component = go.GetComponentInChildren<MyMonoBehaviour>();
            if (component != null)
            {
                // MyMonoBehaviourを持つ場合、再保存する
                EditorSceneManager.MarkSceneDirty(scene);
                EditorSceneManager.SaveScene(scene);
                break;
            }
        }
    }
}

予め影響範囲がある程度分かっている場合は、FindAssetsの第2引数にアセットのディレクトリを指定するなどで走査範囲を狭めることで処理時間を短縮することができる。

参考

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

【Unity】Oculus向けのEventSystemをEditor上から操作できる形に置き換える

初めに

Oculus Integrationを用いたVRアプリケーションの開発でuGUIを使う時、通常のEventSystemなどをOculus向けのものに置き換えて使用します。

参考: Oculus QuestでuGUIを操作する - Qiita

大きな変更なくVRでuGUI操作ができるのは大変便利なのですが、その代わりにEditor上で再生した時にuGUIの操作が効かなくなってしまう問題があります。そこで今回は、Editorで再生した時のみuGUIを通常のEventSystemに置き換える方法を紹介したいと思います。

環境

  • Unity 2019.1.9f1
  • Oculus Integration for Unity 1.39.0
  • Mac OSX 10.14.6(18G87)
  • Oculus Go (実機で影響がないことを確認)

アプローチ

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]を使います。これはゲーム開始時にメソッドが呼び出されるようにするアトリビュートで、シーン上に配置しなくても実行される機能を持ちます。これを付けたスクリプトをEditor以下に配置することで、Editor上でのみ呼び出されるデバッグ機能を実現できます。注意としては、プロジェクトにインポートされているだけでどのシーンからでも必ず呼び出されてしまうのでお気をつけください。

参考: 【Unity】ゲームの起動後 Awakeより前にメソッドを実行する - テラシュールブログ

では実際にEditor上で操作できる形に置き換えていくわけですが、具体的には以下の差分があります。

アタッチ場所 通常 Oculus Integration
EventSystem StandaloneInputModule OVRInputModule
Canvas GraphicRaycaster OVRRaycaster

これをランタイム上で置き換えていきます。

まず判定条件として、VR用のシーンであるかどうかを判断するためOVRCameraRigの有無を確認します。

// VR用シーンでなさそうならreturn
if (GameObject.FindObjectOfType<OVRCameraRig>() == null) { return; }

次にEventSytemを置き換えます。

  1. EventSystemを検索
  2. 同じGameObjectについているOVRInputModuleを無効化
  3. 同じGameObjectに対してStandaloneInputModuleをアタッチ
// OVRInputModuleを無効 かつ StandaloneInputModuleを有効に
var eventSystem = GameObject.FindObjectOfType<EventSystem>();
eventSystem.GetComponent<OVRInputModule>().enabled = false;
eventSystem.gameObject.AddComponent<StandaloneInputModule>();

最後に各CanvasにGraphicRaycasterを付けていきます。Find~系メソッドでは非アクティブなコンポーネントなどを習得できない場合があるため、Resources.FindObjectsOfTypeAll()を使って検索をします。Linqを使ってシーン上のものだけに絞り込みます。

// Project中の全Canvasコンポーネントの中からAssets以下でないもの(=Hierarchy上のもの)を全て習得
var canvases = Resources.FindObjectsOfTypeAll<Canvas>()
.Where(c => AssetDatabase.GetAssetOrScenePath(c).Contains(".unity")).ToArray();

検索したCanvasにGraphicRaycasterが付いていれば有効に、なければ追加します。ポイントはraycasterの型比較で、GetComponent<GraphicRaycaster>()ではOVRRaycasterも習得できてしまうため、継承された型でないことを確認する必要がありました。raycaster.GetType() == typeof(GraphicRaycaster)とすれば大丈夫です。

// GraphicRaycasterがあれば有効に、なければ追加
// OVRRaycasterは競合しないので特にdisableにしていない
foreach (var canvas in canvases)
{
    var raycaster = canvas.gameObject.GetComponent<GraphicRaycaster>();
    if (raycaster != null && raycaster.GetType() == typeof(GraphicRaycaster))
    {
        raycaster.enabled = true;
    }
    else
    {
        canvas.gameObject.AddComponent<GraphicRaycaster>();
    }
}

コメントにもありますが、OVRRaycasterGraphicRaycasterと競合しないことを確認したので特に無効にする操作はしていません。

2019.09.17追記

[RuntimeInitializeOnLoadMethod]だけだと実行一回につき1度しか再生されないため、SceneManager.sceneLoaded += OnSceneLoadedを追加しました。これにより新たなシーンが読み込まれる度に実行されるようになりました。初期シーンのみは今までと同じように直接実行する形です。

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void Init()
{
    Replace();
    SceneManager.sceneLoaded += OnSceneLoaded;
}
static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
    Replace();
}

導入方法

以下のスクリプトをEditor以下に入れてください。

Gist: ReplaceEventSystems.cs

ReplaceEventSystems.cs
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

/// <summary>
/// エディタ上でのアプリ起動時に自動実行されるデバッグ用スクリプト
/// VRアプリ向けになっているEventSystemをEditor上からも操作できる形に組み替える
/// </summary>
public class ReplaceEventSystems
{
    const float InitialHeight = 1.6f;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
    static void Init()
    {
        Replace();
        SceneManager.sceneLoaded += OnSceneLoaded;
    }
    static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        Replace();
    }
    static void Replace()
    {
        // VR用シーンでなさそうならreturn
        if (GameObject.FindObjectOfType<OVRCameraRig>() == null) { return; }

        // カメラ位置の調整
        var ovrManager = GameObject.FindObjectOfType<OVRManager>();
        if (ovrManager != null && ovrManager.trackingOriginType == OVRManager.TrackingOrigin.FloorLevel)
        {
            Camera.main.transform.position = Camera.main.transform.position + Vector3.up * InitialHeight;
        }
        // カメラにキーボード操作用コンポーネントを追加(必要なければ消してください)
        Camera.main.gameObject.AddComponent<EditorCamera>();

        // OVRInputModuleを無効 かつ StandaloneInputModuleを有効に
        var eventSystem = GameObject.FindObjectOfType<EventSystem>();
        eventSystem.GetComponent<OVRInputModule>().enabled = false;
        eventSystem.gameObject.AddComponent<StandaloneInputModule>();

        // Project中の全Canvasコンポーネントの中からAssets以下でないもの(=Hierarchy上のもの)を全て習得
        var canvases = Resources.FindObjectsOfTypeAll<Canvas>()
        .Where(c => AssetDatabase.GetAssetOrScenePath(c).Contains(".unity")).ToArray();
        // GraphicRaycasterがあれば有効に、なければ追加
        // OVRRaycasterは競合しないので特にdisableにしていない
        foreach (var canvas in canvases)
        {
            var raycaster = canvas.gameObject.GetComponent<GraphicRaycaster>();
            if (raycaster != null && raycaster.GetType() == typeof(GraphicRaycaster))
            {
                raycaster.enabled = true;
            }
            else
            {
                canvas.gameObject.AddComponent<GraphicRaycaster>();
            }
        }
    }
}

EditorCamera.csはGameビュー上で視点をキーボード操作するためのスクリプトです。必要なら入れてください。MonoBehabiourを継承しているため、Editor以下に入れると動作しません。

Gist: EditorCamera.cs

EditorCamera.cs
using UnityEngine;

/// <summary>
/// Editor上でCameraをキーボード操作するための
/// </summary>
public class EditorCamera : MonoBehaviour
{
    const float Angle = 30f;
    const float Speed = 0.25f;

    bool IsShift { get { return Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); } }
    bool shouldRotateLeft { get { return !IsShift && (Input.GetKeyDown(KeyCode.LeftArrow) || Input.GetKeyDown(KeyCode.A)); } }
    bool shouldRotateRight { get { return !IsShift && (Input.GetKeyDown(KeyCode.RightArrow) || Input.GetKeyDown(KeyCode.D)); } }
    bool shouldMoveforwad { get { return !IsShift && (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.W)); } }
    bool shouldMoveBack { get { return !IsShift && (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.S)); } }
    bool shouldMoveLeft { get { return IsShift && (Input.GetKeyDown(KeyCode.LeftArrow) || Input.GetKeyDown(KeyCode.A)); } }
    bool shouldMoveRight { get { return IsShift && (Input.GetKeyDown(KeyCode.RightArrow) || Input.GetKeyDown(KeyCode.D)); } }
    bool shouldMoveUp { get { return IsShift && (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.W)); } }
    bool shouldMoveDown { get { return IsShift && (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.S)); } }

    void Update()
    {
        // [←|A]キーで左回転
        if (shouldRotateLeft) { transform.Rotate(0, -Angle, 0); }
        // [→|D]キーで右回転
        if (shouldRotateRight) { transform.Rotate(0, Angle, 0); }
        // [↑|W]キーで前方移動
        if (shouldMoveforwad) { transform.position += transform.forward.normalized * Speed; }
        // [↓|S]キーで後方移動
        if (shouldMoveBack) { transform.position -= transform.forward.normalized * Speed; }
        // [Shift+(←|A)]キーで左方移動
        if (shouldMoveLeft) { transform.position -= transform.right.normalized * Speed; }
        // [Shift+(→|D)]キーで右方移動
        if (shouldMoveRight) { transform.position += transform.right.normalized * Speed; }
        // [Shift+(↑|W)]キーで上方移動
        if (shouldMoveUp) { transform.position += transform.up.normalized * Speed; }
        // [Shift+(↓|S)]キーで下方移動
        if (shouldMoveDown) { transform.position -= transform.up.normalized * Speed; }
    }
}

最後に

これで実機ビルドに影響を出さず、Editor上でも快適にuGUIのデバッグができるようになりました。

逆にOculus Go開発されている方ってどうしてるんでしょうか? もし他の良いやり方があれば是非教えてほしいです?

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

UnityでスクリプトをダブルクリックしてもVSCodeでファイルが開かない時の解決策

先日 Unity2019.1 から Unity2019.2 に変更したら、スクリプトをダブルクリックしても VSCode でファイルが開かなくなりました。
かなり悩んだので、その解決策を記載します。

解決策↓
PackageManager で VisualStudioCodeEditor を Version1.1.2 にアップデートする。以上。

同じ悩みを抱えた方の参考になることを願って。

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

Unityの基本操作

はじめに

この記事は筆者がUnityの記事を書く際、基本操作までいちいち説明していたら長くなってしまうということで、そういった基本操作等をまとめるために作りました。(項目は記事を書く際に必要だと思ったら随時追加していきます。)

私が書いた記事を読む前にこの記事を一通り読んでおけ、という趣旨のものではないです。
私が書いた記事を読んでいて操作がわからない場合にこの記事の該当項目を読んでください。

そのため、各セクションはその項目だけ読めばわかるように書いています。

「プロジェクトにファイルを追加する」

追加したいファイルをProject内にドラッグアンドドロップしてプロジェクトにファイルを追加できます。

またこのとき、Assetsフォルダ直下に置いてしまうと数が増えた際に散らかってしまうので、
わかりやすい名前のフォルダを作ってその中に入れるとよいでしょう。

間違って追加してしまった場合は、右クリックしてdeleteを選択、またはCtrl/Cmd+deleteで消すことが出来ますが、この操作はUndoができないので気をつけてください。

「新規オブジェクトを追加する」

Hierarchyタブ内で右クリックしてCreate Emptyを選択する、
または、Ctrl/Cmd+Shift+Nで新規オブジェクトが追加できます。

またこのとき、「GameObject」のような名前で生成されるため、
わかりやすい名前に変更しておくとよいでしょう。

間違って追加してしまった場合は、右クリックしてdeleteを選択、またはCtrl/Cmd+deleteで消すことが出来ます。

「オブジェクトにコンポーネントを追加する」

コンポーネントを追加したいオブジェクトをHierarchyタブやSceneタブで選択し、
InspectorタブのAdd Componentボタンを押してコンポーネントを選んで追加します。

もし間違えて追加してしまった場合、これを消すためには、
消したいコンポーネントの右上の歯車マークを左クリック(右クリックでもよい)をしてRemove Componentを選ぶことで消すことが出来ます。

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

VRChatでワールドを作った

はじめに

前の記事では、新しい習慣が構築される手順を説明しました。
次にVRChatを使って、この仮説を検証するためのワールドを作成することにしました。

結論

VR感覚をトレーニングするためのワールド

Seek Invisible Key

ワールドは、鍵を探してドアを開けていくという単純なルールです。
しかし鍵の一部が不可視になっているため、手探りで鍵の形状を確かめないと本物の鍵かどうかが分からない仕組みになっています。

解説

何故このようなワールドを作ったかというと、女性アバターで見ず知らずの人に触られるのを怖いと感じたからです。

あとは「昔からMYSTや脱出ゲーなどの、謎解きゲームが好きだったから」というのも大きな理由です。

これから

このワールドによって、最初に書いた目的を達成できたかどうかは正直怪しいところです。
VR感覚はまだ謎が多く、個人差も大きいのでトライ&エラーで調整していくことが重要だと思っています。

皆さんにも是非、このワールドを体験していただいて、感想やアドバイスを頂けるとありがたいです。

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

習慣の力

はじめに

今回は習慣の力という本の説明と、それに自分なりにの解釈を加えた内容になっています。

習慣の力〔新版〕 ((ハヤカワ・ノンフィクション文庫)) チャールズ デュヒッグ
https://www.amazon.co.jp/dp/415050542X/ref=cm_sw_r_tw_dp_U_x_h0EFDb0A9WTDT‬

習慣の力では以下のような、“きっかけ”からスタートする“ルーチン“の繰り返しが、習慣に変わっていくと説明されています。
(詳しくは本を読んでください)

  1. きっかけ
  2. ルーチン
  3. 報酬
  4. 以下繰り返し

結論

私は、習慣のループを続けると以下のような状態になると考えています。

  1. 欲求が満たされいない状態
  2. きっかけ
  3. ルーチンを実行したいという欲求
  4. ルーチンの実行
  5. 報酬
  6. 以下繰り返し

このループは身体感覚を伴っていて、VR上でも十分機能します。

説明

私は参考文献にあった習慣のループに“欲求”という概念を追加しました。
理由は、この「欲求」がVRにとって重要なのではないかと感じたからです。

分かりやすく、動物の例を挙げます。

先日、上野公園で鴨を見つけました。
足を止めて鴨を見つめると、鴨が私に向かって歩いてきました。
ある程度近づきましたが、私が何もしないでいると鴨は離れていきました。

この鴨の行動を前述の手順に当てはめてみます。

  1. きっかけ(人間に見つめられる)
  2. ルーチン(人間に近づく)
  3. 報酬(餌をもらえる)

鴨はこのようなループを何度か繰り返していたはずです。
しかし、実際にはもっと複雑な手順(人間がバッグを開ける、しゃがむ等)があるためか、鴨はそれ以上接近してきませんでした。

では何故、鴨はルーチンを繰り返すのでしょうか?
そこには欲求があるからです。

  1. 欲求が満たされいない状態(空腹、低血糖など)
  2. きっかけ(人間に見つめられる)
  3. ルーチンを実行したいという欲求(「人間に近づくことで餌をもらえる」というイメージが喚起される)
  4. ルーチン(人間に近づく)
  5. 報酬(餌をもらえる)
  6. 以下繰り返し

詳しく見てみましょう。

1.欲求が満たされない状態

最初に欲求が満たされない状態があります。
もし満腹の場合や、食欲より緊急度が高い欲求(「喉が渇いている」、「襲われる危険性がある」等)があった場合は、手順2以降に行かないこともあります。

3.ルーチンを実行したいという欲求

次にルーチンを行う直前、鴨は餌の事をイメージします。
習慣ができる前は「人間に見つめられる」と「餌が手に入る」は何の関係もないので、その想像は発生しませんでした。
しかし、ループが発生すると途端にそれらが結びつき、さらに何度も繰り返すことでどんどん強くなります。

6.以下繰り返し

さらにループを繰り返すと「人間に見つめられる」だけで「餌を食べたい」という欲求が生まれるようになります。
それは最初のブログにも書いた「ヨダレが出る」等の情動反応を伴うものです。

これから

VRに話を戻しましょう。

習慣がVRにとって重要なのは、以下の3つです。
・”きっかけ“は割と何でもいい(他に優先度が高い欲求がなければ)
・「見つめられる」という視覚情報だけでも発生条件になりえる
・情動、つまり身体的な反応を伴う感情が発生する

これらを条件を満たすVRコンテンツを作る事で、現実には存在しない新しい習慣を作ることも可能です。

2.1. VRChatでワールドを作った

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

Inputを分岐させる機構を作った

はじめに

以前キー入力、マウス入力をIObservableに変換して使うという記事を書きました。
このライブラリはInputを流すストリームを作るものでしたが、いまいち使い道が思い浮かばなかったのですが、
Inputを分岐してくれる機構があれば便利かなと思いました。
と思っていたらUnityでInputの分岐器を作った話という記事を見つけました。そうです、やりたいことはこれです。
ということで前回のInputAsObservableを使って楽に作れるんじゃないかということで作ってみました。

用意するもの

UniRx
InputAsObservable
InputBrancher

使い方

①Interfaceの実装

Inputの流す先のクラスにIInputReceiverインターフェースを実装します(空のインターフェースです)

Hoge.cs
using InputBranch;
using UnityEngine;
using UniRx;

public class Hoge : MonoBehaviour,IInputReceiver{
    void Start(){
        InputBrancher.GetKey(this, KeyCode.C)
               .Subscribe(_ => {Debug.Log("Hoge");}); //Cをおすとほげええええ
    }
}
Foo.cs
using InputBranch;
using UniRx;
using UnityEngine;

public class Foo : MonoBehaviour,IInputReceiver{
    void Start(){
        InputBrancher.GetKey(this, KeyCode.C)
               .Subscribe(_ => {Debug.Log("Foo");}); //Cを押すとふううううう
    }
}

②分岐方法を決定

InputBrancher.Switch(IInputReceiver)メソッドでどのインスタンスのインプットを受け取るかを決めます。
例ではHを押したときとFを押したときで分岐させています。

SampleInputController.cs
using InputBranch;
using UniRx.Triggers;
using UnityEngine;
using UniRx;

public class SampleInputController : MonoBehaviour{
    [SerializeField] private Hoge hoge;
    [SerializeField] private Foo foo;
    private void Start(){
        this.OnKeyDownAsObservable(KeyCode.H)
            .Subscribe(_ => {
                Debug.Log("switch hoge");
                InputBrancher.Switch(hoge); //Hを押したらhogeのインプットしか受け取らない
            });

        this.OnKeyDownAsObservable(KeyCode.F)
            .Subscribe(_ => {
                Debug.Log("switch foo");
                InputBrancher.Switch(foo); //Fを押したらfooのインプットしか受け取らない
            });
    }
}

結果

インプットを分岐することができ、同じCキーを押したときでもインスタンスによって結果が変わりました。

利点

InputBrancherを介することでインプットを分岐することができ、結果をイベントで受け取るのでUpdateにif文で判定を書くことを撲滅できます。

欠点

単にboolやflootで値が欲しいときに対応できない。(それ用のメソッドをまた作らないと…)

余談


見逃してましたがRiderに怒られてました。Brancherって単語はないようですね!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む