athome-developer’s blog

不動産情報サービスのアットホームの開発者が発信するブログ

HoloLensでオブジェクトを回転してみる

こんにちは、情報システム部の高野です。
ちょっと間が空いてしまいましたが
拡大縮小に引き続き、今度は回転です。
今回は、Y軸に対する回転だけやります。
(いまのところそれしか使わなそうなので)

準備

前回の拡大縮小で作成したプロジェクトにそのまま追加していきます。

回転用のハンドルCubeとスクリプトの作成

回転ハンドルを作成する

HierarchyウィンドウのCube内のWireを右クリックし
「3D Obejct」→「Sphere」を選択します。
名称をRotateHandle1に変更します。
サイズと位置を変更します。
Position X=0.5 Y=0 Z=-0.5
Size X/Y/Z=0.05
前回作成したHandle用のマテリアルをアタッチします。

スクリプトを作成する

Projectウィンドウで「C# Script」を作成し
名称をRotateControllerに変更します。
これをRotateHandle1オブジェクトにアタッチします。

RotateController.csをVisualStudioで開き以下の様に修正します。

using HoloToolkit.Unity.InputModule;
using UnityEngine;

public class RotateController : MonoBehaviour, IInputHandler, ISourceStateHandler
{
    private bool isHold;
    private IInputSource currentInputSource;
    private uint currentInputSourceId;
    private Vector3 prevPos;
    private GameObject targetObj;

    // Use this for initialization
    void Start()
    {
        // rootから取れば良かった
        targetObj = transform.root.gameObject;
    }

    // Update is called once per frame
    void Update()
    {
        if (!isHold) return;

        Vector3 handPos;
        currentInputSource.TryGetPosition(currentInputSourceId, out handPos);
        // ワールド空間からローカル空間に変換
        handPos = Camera.main.transform.InverseTransformDirection(handPos);

        var diff = prevPos - handPos;
        prevPos = handPos;

        targetObj.transform.Rotate(0f, diff.x * 360, 0f, Space.World);
    }

    public void OnInputUp(InputEventData eventData)
    {
        if (!isHold) return;

        isHold = false;
        InputManager.Instance.PopModalInputHandler();
    }

    public void OnInputDown(InputEventData eventData)
    {
        if (!eventData.InputSource.SupportsInputInfo(eventData.SourceId, SupportedInputInfo.Position))
            return;

        if (isHold) return;

        isHold = true;
        InputManager.Instance.PushModalInputHandler(gameObject);

        currentInputSource = eventData.InputSource;
        currentInputSourceId = eventData.SourceId;

        currentInputSource.TryGetPosition(currentInputSourceId, out prevPos);
        prevPos = Camera.main.transform.InverseTransformDirection(prevPos);
    }

    public void OnSourceDetected(SourceStateEventData eventData)
    {
    }

    public void OnSourceLost(SourceStateEventData eventData)
    {
        if (!isHold) return;

        isHold = false;
        InputManager.Instance.PopModalInputHandler();
    }
}

TransformクラスのInverseTransformDirectionメソッドで
ワールド空間からローカル空間に変換してますが
オブジェクトの正面からだけ操作するのであればこれは必要ありません。
オブジェクトの横や後ろに回って操作すると
これが無いと手の動きと回転が合わなくなります。
下図のような位置関係の場合は、手を横に動かすとZ方向に動かすことになります。
このままだとXの移動距離では無くZの移動距離が必要になります。
f:id:taktak1974:20170620160225p:plain
自分とオブジェクトの位置関係に応じて
Xの移動距離だったりZの移動距離だったりに変更するのは大変なので
カメラのローカル空間に変換して手の横移動は常にXの移動にしました。
これがよくわからずに嵌りました(Unity初心者なので色々、嵌ります)


後は、各辺にハンドルをコピーします。
完成したものがこちらになります。

hololens rotate object


これを作っている間にMRDesignLabs_Unityが公開されて
作る必要なかったなって感じにはなったのですが
勉強にはなったので良かった。
MRDesignLabs_Unityは、HoloToolkitのイベントを使ってないので
やり方は結構違いますけどね。*1


弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
Unityできる人もいるとうれしいな
athome-inc.jp

*1:MRDesignLabs_UnityだとHoloLens以外でも動かせそう

HoloLensでオブジェクトを変倍してみる

こんにちは、情報システム部の高野です。
今回は、HoloLensでオブジェクトの拡大縮小をします。
しかも等倍ではなくXYZ軸自由に拡大縮小できるように作ります。

HoloLensで拡大縮小する方法は、いくつかあります。
・声で指示する方法(以前テレビでやってました)
・HoloLensにプリインストールされているActiongramの様にスライダーのようなもので操作する方法
・下記ブログのようにワイヤーフレームにハンドルを付けて操作する方法
こちらのブログを大分参考にさせていただきました)
blog.d-yama7.com

今回は、変倍ということもあり3番目の方法でやってみます。


準備

まずプロジェクトの準備は、以前と同様です。
HoloToolkitをimportしプロジェクトの設定をします。
HololensCamera・InputManager・DefaultCursorをHierarchyウィンドウに配置します。
f:id:taktak1974:20170606101603p:plain

拡大縮小するオブジェクトとワイヤーフレームの配置

Cubeの配置

まず拡大縮小するオブジェクトを配置します。
これは何でも良いのですが、今回はCubeを配置します。
Hierarchyウィンドウで右クリックし「3D Object」→「Cube」を選択して
Cubeオブジェクトを配置します。
配置されたCubeを選択しInspectorウィンドウで位置と大きさを変更します。
Position Z=1
Scale X/Y/Z=0.2

適当にMaterialを用意しCubeに色を付けておきます。
ワイヤーフレームを緑にするのでそれ以外の色がいいです)

ワイヤーフレームの配置

ワイヤーフレーム用のShaderを作成します。
ProjectウィンドウのAssetsフォルダ上で右クリックし
「Create」→「Shader」→「Standard Surface Shader」を選択します。
作成されたShaderの名前をWireに変更しておきます。
Wireシェーダーを右クリックし「Open C# Project」を選択します。
VisualStudioでWire.shaderを開きこちらを参考に編集します。

次にMaterialを作成します。
ProjectウィンドウのAssetsフォルダ上で右クリックし
「Create」→「Material」を選択します。
Materialの名称をWireに変更します。
Wireマテリアルを選択しInspectorウィンドウの上部にある
Shaderプルダウンから「Custom」→「Wire」を選択します。
LineColorを緑に変更します。
f:id:taktak1974:20170606105725p:plain

HierarchyウィンドウのCubeを右クリックし子オブジェクトとしてCubeを作成します。
名称をWireに変更しておきます。

WireマテリアルをWireオブジェクトに関連づけます。
ここまでで下図のようになります。
f:id:taktak1974:20170606110049p:plain

拡大縮小用のハンドルCubeとスクリプトの作成

リサイズハンドルを作成する

HierarchyウィンドウのWireオブジェクトを右クリックし
「3D Object」→「Cube」を選択します。
名称をResizeHandle1に変更します。
Position X=0.5 Y=0.5 Z=-0.5
Size X/Y/Z=0.05
に変更します。
緑色のマテリアルを作成しResizeHandle1に関連付けます。

これを各頂点にコピーすることになるのですが
動きを付けてからにします。

スクリプトを作成する

Projectウィンドウで「C# Script」を作成し
名称をResizeControllerに変更します。
これをResizeHandle1オブジェクトに関連付けておきます。

ResizeController.csをVisualStudioで開き以下の様に修正します。

using HoloToolkit.Unity.InputModule;
using UnityEngine;

public class ResizeController : MonoBehaviour, IInputHandler, ISourceStateHandler
{
    private GameObject targetObj;
    private bool isHold;
    private IInputSource currentInputSource;
    private uint currentInputSourceId;
    private Vector3 startHandPos;
    private Vector3 axisVect;
    private Vector3 startScale;

    // Use this for initialization
    void Start()
    {
        // 本来は外から設定した方が良いです
        targetObj = transform.parent.parent.gameObject;
    }

    // Update is called once per frame
    void Update()
    {
        if (!isHold) return;

        Vector3 handPos;
        currentInputSource.TryGetPosition(currentInputSourceId, out handPos);

        var diff = handPos - startHandPos;

        // 各軸ごとの方向を取得
        var xVect = axisVect.x >= 0 ? 1 : -1;
        var yVect = axisVect.y >= 0 ? 1 : -1;
        var zVect = axisVect.z >= 0 ? 1 : -1;

        targetObj.transform.localScale = startScale + new Vector3(diff.x * xVect, diff.y * yVect, diff.z * zVect);
    }

    public void OnInputDown(InputEventData eventData)
    {
        if (!eventData.InputSource.SupportsInputInfo(eventData.SourceId, SupportedInputInfo.Position))
            return;

        if (isHold) return;

        isHold = true;
        InputManager.Instance.PushModalInputHandler(gameObject);

        currentInputSource = eventData.InputSource;
        currentInputSourceId = eventData.SourceId;

        currentInputSource.TryGetPosition(currentInputSourceId, out startHandPos);

        startScale = targetObj.transform.localScale;

        axisVect = transform.position - targetObj.transform.position;
    }

    public void OnInputUp(InputEventData eventData)
    {
        if (!isHold) return;

        isHold = false;
        InputManager.Instance.PopModalInputHandler();
    }

    public void OnSourceDetected(SourceStateEventData eventData)
    {
    }

    public void OnSourceLost(SourceStateEventData eventData)
    {
        if (!isHold) return;

        isHold = false;
        InputManager.Instance.PopModalInputHandler();
    }
}

今回は、以前の移動時と違い手の位置を取れるように
IInputHandler、ISourceStateHandlerを使いました。
こちらの方が簡単でした。

あとは、各頂点にリサイズハンドルをコピーすればOKです。
最終的なHierarchyウィンドウは下図になります。
f:id:taktak1974:20170606141432p:plain

ここまでで拡大縮小が可能です。

対角点を基点にする

上記コードだと中心点が基点になって拡大縮小されますが
対角点を基点にしたいと思います。

以下のようにResizeController.csを変更します。

    void Update()
    {
        if (!isHold) return;

        ・・・

        targetObj.transform.localScale = startScale + new Vector3(diff.x * xVect, diff.y * yVect, diff.z * zVect);

        // 追加
        var scaleDiff = targetObj.transform.localScale - startScale;
        targetObj.transform.position = startPos + new Vector3(scaleDiff.x * xVect, scaleDiff.y * yVect, scaleDiff.z * zVect) / 2;
    }

    public void OnInputDown(InputEventData eventData)
    {
        ・・・

        startScale = targetObj.transform.localScale;
        // 追加
        startPos = targetObj.transform.position;

        axisVect = transform.position - targetObj.transform.position;
    }

拡大縮小しつつ同じ方向に移動しているので基点が対角点に見えます。
(もっと良い方法あるんですかね?)


それで今回作成したものはこんな感じに動きます。

hololens resize object




ワイヤーフレームやハンドルが一緒に拡大縮小しちゃうのが玉に瑕ですね。
課題は残しつつも一応変倍ができるようになりました。
後は回転ができれば基本的なオブジェクトの操作はできるようになりますね


弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
athome-inc.jp

HoloLensでオブジェクトを移動してみる

こんにちは、情報システム部の高野です。
今回は、オブジェクトの移動を試してみます。

前回作ったプロジェクトをそのまま利用します。


HoloLensでオブジェクト移動するために色々と調べてみたところ
いくつかの実装方法があることが分かりました。
今回は、そのうちの2つの方法を試してみます。
1つは、UnityのInteractionManagerのイベントを使う方法
もう1つは、HoloToolkitのIManipulationHandlerを実装する方法です。

InteractionManagerを利用した移動

前回、オブジェクトを生成できるようにしたので
その生成したオブジェクトを移動できるようにしていきます。

移動用のスクリプトを作成する

ProjectウィンドウのScriptsフォルダにGestureControllerというスクリプトを作成します。
作成してGestureControllerHierarchyウィンドウのObjectManager
ドラッグ&ドロップし設定します。

CubeプレハブにTagを設定する

後程、別オブジェクトを作成するので区別できるようにTagを設定しておきます。
ProjectウィンドウのPrefabsフォルダからCubeを選択します。
Inspectorウィンドウの上部にTagプルダウンがあるので「Add Tag」を選択します。
TagsにあるプラスボタンをクリックしTag名をInteractionとし保存します。
もう一度CubeプレハブのInspectorウィンドウを開き
Tagで「Interaction」を選択します。

Tagに設定した状態
f:id:taktak1974:20170522160017p:plain

移動処理を実装する

GestureControllerスクリプトをVisualStudioで開きます。
コードを以下の様に変更します

using HoloToolkit.Unity.InputModule;
using UnityEngine;
using UnityEngine.VR.WSA.Input;

public class GestureController : MonoBehaviour 
{

    private Vector3 prevPos;
    private bool isHold;
    private GameObject focusObj;

    // Use this for initialization
    void Start()
    {
        InteractionManager.SourcePressed += InteractionManager_SourcePressed;
        InteractionManager.SourceReleased += InteractionManager_SourceReleased;
        InteractionManager.SourceLost += InteractionManager_SourceLost;
        InteractionManager.SourceUpdated += InteractionManager_SourceUpdated;
    }

    // Update is called once per frame
    void Update()
    {
        var obj = GazeManager.Instance.HitObject;

        // ホールドしている時は、オブジェクト入れ替えない
        if (obj != null && !isHold)
        {
            // TagがInteractionのものだけを対象とする
            if (obj.tag == "Interaction")
            {
                focusObj = obj;
            }
        }
    }

    private void InteractionManager_SourcePressed(InteractionSourceState state)
    {
        if (focusObj == null) return;

        focusObj.GetComponent<Rigidbody>().useGravity = false;

        Vector3 handPosition;
        if (state.source.kind == InteractionSourceKind.Hand && 
            state.properties.location.TryGetPosition(out handPosition))
        {
            isHold = true;
            prevPos = handPosition;
        }
    }

    private void InteractionManager_SourceReleased(InteractionSourceState state)
    {
        if (focusObj == null) return;

        focusObj.GetComponent<Rigidbody>().useGravity = true;
        isHold = false;
        focusObj = null;
    }

    private void InteractionManager_SourceLost(InteractionSourceState state)
    {
        if (focusObj == null) return;

        focusObj.GetComponent<Rigidbody>().useGravity = true;
        isHold = false;
        focusObj = null;
    }

    private void InteractionManager_SourceUpdated(InteractionSourceState state)
    {
        if (!isHold || focusObj == null) return;

        Vector3 handPosition;
        state.properties.location.TryGetPosition(out handPosition);

        if (state.source.kind == InteractionSourceKind.Hand && 
            state.properties.location.TryGetPosition(out handPosition))
        {
            var moveVector = Vector3.zero;
            moveVector = handPosition - prevPos;

            prevPos = handPosition;

            var handDistance = Vector3.Distance(Camera.main.transform.position, handPosition);
            var objectDistance = Vector3.Distance(Camera.main.transform.position, focusObj.transform.position);

            focusObj.transform.position += (moveVector * (objectDistance / handDistance));
        }
    }
}

InteractionManager_SourcePressedで手の位置を取得しておき
InteractionManager_SourceUpdatedで移動距離を計算して
オブジェクトに新しい位置を割り当てます。
この移動方法は下記の記事を参考にさせていただきました。
qiita.com
もうちょっと複雑な計算をしているものも見かけたのですが
今のところは、この方法で支障がないような気がしています。

IManipulationHandlerを利用した移動

今度はHoloToolkitのIManipulationHandlerインターフェイスを実装する方法です。
HoloToolkitを利用しているので、できればこちらの方法で実装した方が良いかと思います。

新たなCubeプレハブを作成する

元々、用意してあったCubeとは別のオブジェクトを生成して
そちらをIManipulationHandlerで移動するようにしていきます。
前回、作成したようにCubeのプレハブを作成します。
見た目で判別できるように黄色にしておきます。
プレハブ名もYellowCubeにします。
RigidbodyCubeManagerスクリプトも設定しておきます。

YelloCubeを生成できるようにコードを変更する

前回作成したObejectManagerスクリプトのコードを変更します。

using HoloToolkit.Unity.InputModule;
using UnityEngine;

public class ObjectManager : MonoBehaviour, IInputClickHandler
{
    public GameObject obj1;
    public GameObject obj2;
    private bool toggle = true;

    private void Start()
    {
        InputManager.Instance.PushFallbackInputHandler(gameObject);
    }

    public void OnInputClicked(InputClickedEventData eventData)
    {
        var obj = toggle ? obj1 : obj2;
        toggle = !toggle;

        var pos = Camera.main.transform.position;
        var forword = Camera.main.transform.forward;

        Instantiate(obj, pos + forword, new Quaternion());
    }
}

Unityに戻りobj1にCubeプレハブをobj2にYellowCubeプレハブを設定します。

移動処理を実装する

Unityで新しいスクリプトManipulationControllerを作成します。
作成したスクリプトYellowCubeプレハブに設定します。

設定したスクリプトをVisualStudioで開き
下記の様にコードを変更します。

using HoloToolkit.Unity.InputModule;
using UnityEngine;

public class ManipulationController : MonoBehaviour, IManipulationHandler
{

    Vector3 prevPos;

    public void OnManipulationCanceled(ManipulationEventData eventData)
    {
        GetComponent<Rigidbody>().useGravity = true;
        InputManager.Instance.PopModalInputHandler();
    }

    public void OnManipulationCompleted(ManipulationEventData eventData)
    {
        GetComponent<Rigidbody>().useGravity = true;
        InputManager.Instance.PopModalInputHandler();
    }

    public void OnManipulationStarted(ManipulationEventData eventData)
    {
        GetComponent<Rigidbody>().useGravity = false;
        prevPos = eventData.CumulativeDelta;

        // これが無いとオブジェクトにフォーカス時しか操作ができない
        InputManager.Instance.PushModalInputHandler(gameObject);
    }

    public void OnManipulationUpdated(ManipulationEventData eventData)
    {
        Vector3 moveVector = Vector3.zero;
        moveVector = eventData.CumulativeDelta - prevPos;

        prevPos = eventData.CumulativeDelta;

        // 手の位置が取得できないので決め打ちで40cmに
        var handDistance = 0.4f;
        var objectDistance = Vector3.Distance(Camera.main.transform.position, gameObject.transform.position);

        gameObject.transform.position += (moveVector * (objectDistance / handDistance));
    }
}

移動のロジックは、InteractionManagerの方とあまり変わりはありませんが
IManipulationHandlerだと元記事にもありますが手の位置の取得方法が不明です。
私もいろいろと試しましたが今のところ分かっていません。
下記を実行するとfalseが返ってくるので取得できる望みは低いのはないかと思います。

eventData.InputSource.SupportsInputInfo(eventData.SourceId, SupportedInputInfo.Position)

OnManipulationUpdatedメソッドは、InteractionManager.SourceUpdatedイベントと違い
フォーカスが外れるとイベントが飛びません。
それを回避するためにInputManager.Instance.PushModalInputHandlerメソッドに
gameObjectを渡しています。


今回、作成したものはこんな感じで動きます。

HoloLensでオブジェクトを移動する
動画では分かりづらいのですが、
緑のオブジェクト(InteractionManagerを使った方)の方が
手の移動とオブジェクトの移動がしっくりきます。



HoloToolkitを利用してオブジェクトを移動するなら
IManipulationHandlerを実装する方法でやりたいところですが
手の位置の取得方法が分からないのでうーんって感じです。
(やり方分かる方は、こそっと教えてほしいです)
今回は以上になります。次もHoloLensネタになるかと思います。


弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
athome-inc.jp

HoloLensで新しいオブジェクトを生成してみる

こんにちは、情報システム部の高野です。
前回は、起動時にすでに存在するオブジェクトへの操作を試しましたが
今回は、新しくオブジェクトを生成する方法を試してみました。

前回の「カメラを変更する」までは手順は同じなので省略します。

カーソルとジェスチャーの設定をする

前回と同様ですが、InputManagerDefaultCursor
Hierarchyウィンドウにドラッグ&ドロップしておきます。
ここまででHierarchyウィンドウは下図のようになります。
f:id:taktak1974:20170515114532p:plain

3Dオブジェクトのプレハブを用意する

新しいオブジェクトを生成するには生成元のプレハブを作成しておく必要があります。*1
Hierarchyウィンドウの「Create」メニューから
「3D Object」→「Cube」を選択します。
InspectorウィンドウでScaleをすべて0.1に変更しておきます。

オブジェクトに色を付ける

ProjectウィンドウのAssetsフォルダにMaterialsというフォルダを作成します。
Materialsフォルダ内で右クリックし「Create」→「Material」を選択します。
今回は名前をGreenにしInspectorウィンドウの「Albedo」を緑っぽくしておきます。
作成したMaterialをHierarchyウィンドウ内のCubeにドラッグ&ドロップします。

オブジェクトに重力を与える

Cubeを選択した状態でInspectorウィンドウを開きます。
「Add Component」ボタンをクリックしRigidbodyを検索して選択します。

プレハブ化する

ProjectウィンドウのAssetsフォルダにPrefabsフォルダを作成します。
作成したPrefabsフォルダにHierarchyウィンドウからCubeをドラッグ&ドロップします。
これでCubeプレハブが作成されました。
HierarchyウィンドウからはCubeを削除しておきます。

動的に3Dオブジェクトを生成する

空のGameObjectを作成する

Hierarchyウィンドウの「Create」メニューから「Create Empty」を選択し空のGameObjectを作成します。
作成したGameObjectは名称をObjectManagerに変更しておきます。

オブジェクト生成用のスクリプトを作成する

ProjectウィンドウのAssetsフォルダにScriptsフォルダを作成します。
作成したScriptsフォルダの中にC#Scriptを作成し名称をObjectManagerに変更します。
作成したObjectManagerスクリプトObjectManagerオブジェクトに関連付けます。
(ドラッグ&ドロップでもInspectorからAdd ComponentでもOKです)
InspectorウィンドウでObjectManagerスクリプトを右クリックし「Edit Script」でVisualStudioを起動します。
コードを以下のように変更します。

using HoloToolkit.Unity.InputModule;
using UnityEngine;

public class ObjectManager : MonoBehaviour, IInputClickHandler
{
    public GameObject obj;

    private void Start()
    {
        InputManager.Instance.PushFallbackInputHandler(gameObject);
    }

    public void OnInputClicked(InputClickedEventData eventData)
    {
        var pos = Camera.main.transform.position;
        var forword = Camera.main.transform.forward;

        Instantiate(obj, pos + forword, new Quaternion());
    }
}

Instantiateメソッドで指定したオブジェクトを生成することができます。
生成する座標はどの向きを向いていても目の前に落としたかったので
上記のようにしてみました。
(もっといい方法もあるのかもしれませんが、Unity力0なので分かってません。)
このままではフィールドobjがnullのままなのでUnityに戻って設定します。
InspectorウィンドウにObjectManagerを表示します。
ObjectManagerスクリプトに「Obj」というプロパティが増えていると思います。*2
f:id:taktak1974:20170515134246p:plain
そこに作成しておいたCubeプレハブをドラッグ&ドロップします。
下図のようになります。
f:id:taktak1974:20170515134353p:plain


最後に床が検知できるようにSpatialMappingHierarchyウィンドウにドラッグ&ドロップします。
ここまでできたらビルドして実行してみましょう。
※ビルドから実行の流れも前回の記事を見てください。

うまくいっていればAirTapするとCubeが目の前に現れて下に落ちます。
こんな感じで足元にCubeが散らかります。
f:id:taktak1974:20170515143411j:plain

ちょっと動きを付けてみる

このままだと目の前にオブジェクトが生成された瞬間に下に落ちてしまいます。
ちょっと味気ないので少し改良してみます。

UnityでCubeプレハブを選択しInspectorウィンドウを開きます。
現状、Rigidbodyの「Use Gravity」にチェックが入っているので外します。
ProjectウィンドウのScriptsフォルダにCubeManagerというC#スクリプトを作成します。
CubeプレハブのInspectorで「Add Component」からCubeManagerスクリプトを選択します。
スクリプトをVisualStudioで開いて以下のように変更します。

using System;
using UnityEngine;

public class CubeManager : MonoBehaviour
{
    DateTime? createTime;

    // Use this for initialization
    void Start()
    {
        createTime = DateTime.Now;
    }

    // Update is called once per frame
    void Update()
    {
        if (createTime != null)
        {
            if ((DateTime.Now - createTime.Value).Seconds > 1)
            {
                GetComponent<Rigidbody>().useGravity = true;
                createTime = null;
            }
            else
            {
                var qua = gameObject.transform.rotation;
                qua.z += 0.1f;
                gameObject.transform.rotation = qua;
            }
        }
    }
}

1秒間は目の前に浮いているようにしています。
浮いている間は、z方向に回転します。


最終的にはこのような感じになります。

20170515holo


今回は以上です。
次回はできればオブジェクトを掴んで移動に挑戦したいです。(できればね)



弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
athome-inc.jp

*1:他の方法もあるかもしれませんが、今のところこの方法しか分かっていないです

*2:VisualStudioで保存しておかないと表示されないので注意が必要です

HololensでAirTapしてみる

こんにちは、情報システム部の高野です。
なんだかんだあって着手できなかったHololensの開発ですが
ようやく少しずつ触れるようになりました。
Unity初心者の私がなんとか動かせるようになるまでの記録を残しておこうかと思います。
私同様にHololensで初めてUnity触る方々の一助になれば幸いです。

今回は、タイトルの通りAirTapでオブジェクトに触れてみようかと思います。

HoloToolkitをパッケージ化しておく

matatabi-ux.hateblo.jp
こちらの記事を見てHoloToolkitをパッケージ化しておくことをお勧めします。
パッケージ化しなくてもAssetをコピーすれば同様のことはできます。
HoloToolKitが更新されておりExportする時に選択を外す項目が上記の記事とは変わっています。
f:id:taktak1974:20170509175706p:plain

Unityでプロジェクトを作成する

Unityを起動してプロジェクトを新規に作成します。
プロジェクト名だけ適当に付けて他はデフォルトのままで
「Create project」ボタンをクリックします。

HoloToolkitをインポートする

「Assets」メニューから「Import Package」→「Custom Package」を選択します。
作成しておいたHoloToolkitのPackageファイルを開きます。
Importダイアログが出るのでそのまま「Import」ボタンをクリックします。
インポートが終わるとProjectウィンドウのAssetsフォルダが下図のようになります。
f:id:taktak1974:20170509180807p:plain

プロジェクトなどの設定をする

「HoloToolkit」メニューから「Configure」→「Apply Hololens Project Settings」を選択します。
全ての項目にチェックがある状態で「Apply」ボタンを押下します。
Unityの再起動が求められるので再起動します。

「HoloToolkit」メニューから「Configure」→「Apply Hololens Scene Settings」を選択します。
全ての項目にチェックがある状態で「Apply」ボタンを押下します。*1


「HoloToolkit」メニューから「Configure」→「Apply Hololens Capability Settings」を選択します。
「Spatial Perception」にチェックを入れて「Apply」ボタンを押下します。
これは常に必要ではありませんが、今回のアプリでは必要になります。

カメラを変更する

HierarchyウィンドウのMain CameraDirectional Lightを右クリックし「Delete」メニューで削除します。
Projectウィンドウで「Assets」→「HoloToolkit」を選択しCameraと入力し検索します。
f:id:taktak1974:20170509182205p:plain
HololensCameraが検索されるのでHierarchyウィンドウのシーンの直下にドラッグ&ドロップします。
※今後特に指定しない場合はシーンの直下を指します。
Ctrl+SでSceneを保存します。ここではScene名を「Main」とします。
f:id:taktak1974:20170509182448p:plain

3Dオブジェクトを配置する

配置するオブジェクトはなんでも良いのですが、ここではSphereを配置することにします。
Hierarchyウィンドウの「Create」から「3D Object」→「Spehre」を選択します。
Inspectorウィンドウで位置と大きさを調整します。
Position Z = 1
Scale X/Y/Z = 0.2

デフォルトのMaterialだと味気ないので変更します。
ProjectウィンドウのAssetsフォルダで右クリックし「Create」→「folder」を選択します。
作成されたフォルダの名称をMaterialsに変更します。
Materialsフォルダを右クリックし「Create」→「Material」を選択します。
作成されたマテリアルの名称をBlueに変更します。
Blueマテリアルを選択した状態でInspectorウィンドウを開き
「Albedo」の白い部分をクリックします。
ColorPickerが表示されるので青っぽく変更します。
HierarchyウィンドウでSphereを選択しProjectウィンドウからBlueマテリアルを
Inspectorウィンドウの空き領域にドラッグ&ドロップします。

この状態で一度ビルドしてみます。
「File」メニューから「Build Settings」を選択します。
ダイアログが表示されるので「Unity C# Projects」にチェックをします。
それ以外はデフォルトのままで大丈夫です。
「Add Open Scenes」ボタンをクリックしMainシーンを登録します。
「Build」ボタンをクリックするとフォルダ選択ダイアログが表示されます。
ここでは新規フォルダAPPを作成し選択します。
ビルドが終わるとプロジェクトフォルダが開きますので先ほど作成したAPPフォルダを開きます。
ソリューションファイルがあるのでVisualStudioで開きます。

VisualStudioでソリューションを開いたら
ビルド設定の「プラットフォーム」をx86に「実行環境」をリモートコンピュータに変更します。
リモート接続ダイアログが表示されるのでHololensのIPアドレスを入力し「選択」ボタンをクリックします。
デバッグを実行するとHoloLensへの配置が開始されます。
初回配置時はかなり時間がかかります。
アプリケーションが起動されると下図のように青い球体が表示されます。
f:id:taktak1974:20170512101648j:plain

オブジェクトをAirTapする

Unityに戻ります。
ProjectウィンドウのHoloToolkitフォルダを選択しCursorと入力し検索します。
今回はDefaultCursorを使用しますのでDefaultCursorプレハブを
Hierarchyウィンドウにドラッグ&ドロップします。
ちなみにプレハブはこんなアイコンです。
f:id:taktak1974:20170512102431p:plain

次にもう一度Projectウィンドウでinputと入力し検索します。
InputManagerプレハブが検索されるのでそれをHierarchyウィンドウにドラッグ&ドロップします。
これでカーソルが目線の位置に表示されます。

AirTapでオブジェクトが反応するようにするにはスクリプトを書く必要があります。
ProjectウィンドウのAssetsフォルダ内にScriptsというフォルダを作成します。
Scriptsフォルダ内で右クリックし「Create」→「C# Script」を選択します。
作成されたScriptファイルの名称をSphereManagerに変更します。
HierarchyウィンドウでSphereを選択しInspectorウィンドウを開きます。
「Add Component」ボタンをクリックしSphere Managerを検索して選択します。*2
今回は、AirTapされたら重力で下に落ちるようにしたいので
さらに「Add Component」ボタンをクリックしRigidbodyを検索して選択します。
Rigidbody内の「Use Gravity」のチェックを外します。
このチェックが付いていると起動した瞬間に下に落ちてしまいます。
InspectorウィンドウでSphere Manager(Script)を右クリックし「Edit Script」を選択します。
VisualStudioが起動します。ターゲットフレームワークの選択ダイアログが出たらそのまま「OK」ボタンをクリックします。
.Editorが付いていない方のプロジェクトで
「Assets」→「Scripts」→「SphereManager.cs」を開きます。
下記の様に修正します。

using HoloToolkit.Unity.InputModule;
using UnityEngine;

public class SphereManager : MonoBehaviour, IInputClickHandler
{
    private void Start()
    {
        InputManager.Instance.PushFallbackInputHandler(gameObject);
    }

    public void OnInputClicked(InputClickedEventData eventData)
    {
        var rigid = GetComponent<Rigidbody>();
        rigid.useGravity = true;
    }
}

AirTap時にOnInputClickedメソッドが呼ばれるので
ここでuseGravityをtrueにし球体に重力を与えます。
※以前のHoloToolkitだとOnSelectでAirTapを拾えたようですが現在は動作しないようです。

ファイルを保存しUnityに戻り上記と同じ手順でビルドします。
現在2つVisualStudioが開いていると思いますが、Appフォルダ内のソリューションを開いている方を選択します。
ソリューションの再読み込みを促されるので実行します。
デバッグを開始して起動するとAirTapができるようになっています。
AirTapして球体が下に落ちれば成功です。

床を認識する

Unityに戻ります。
ProjectウィンドウのHoloToolkitフォルダ内でSpatialMappingを検索します。
SpatialMappingプレハブをHierarchyウィンドウにドラッグ&ドロップします。
再度ビルドしVisualStudioでHololensに配置します。
アプリが起動したらワイヤーフレームが表示されてからAirTapします。*3
床の上で止まれば成功です。
*4



最終的にはこのような動きになります。
f:id:taktak1974:20170512142908g:plain

今回はここまでになります。さて次回は何をやろうかな?


弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
athome-inc.jp

*1:後でカメラ削除するのであまり意味ないかも?

*2:InspectorにScriptファイルをドラッグ&ドロップでも可

*3:ワイヤーフレーム表示前にタップすると床が認識されずに突き抜けてしまいます

*4:「Apply Hololens Capability Settings」で「Spatial Perception」にチェックを入れておかないと 空間の認識がされないので注意が必要です。

dotnetコマンドで複数プロジェクトを持つソリューションを作成する

こんにちは、 情報システム部の高野です。
タイトル通りの内容です。社内勉強会のために調べたので載せておきます。
(VisualStudioを使えばいいじゃないって話なんですが、それを言ったらおしまいです)

環境

Windows10
.NET Tools 1.0.1

構成

最終的に下図のような構成にします。
f:id:taktak1974:20170324105015p:plain

作成手順

ソリューションのディレクトリを作成します。
ここではSampleプロジェクトとします。

mkdir Sample && cd Sample


ソリューションファイルを作成します。
デフォルトでディレクトリ名と同じ名前のソリューションファイルが作成されます。

dotnet new sln


ClassLibraryプロジェクトのディレクトリを作成します。

mkdir src\ClassLibrary && cd src\ClassLibrary


ClassLibraryプロジェクトを作成します。
デフォルトで言語はC#フレームワークはnetstandard1.4になります。
フレームワークは後からでもcsprojファイルを変更すれば変えられます。

dotnet new classlib


一応、問題ないか確認するためリストアとビルドをしておきます。

dotnet restore
dotnet build


ソリューションファイルにプロジェクトを追加します。
ソリューションファイルが存在するディレクトリで実行する場合は、ソリューションファイル名は省略可能です。

dotnet sln ..\..\Sample.sln add ClassLibrary.csproj


ちゃんと追加されたか確認します。

dotnet sln ..\..\Sample.sln list
Project reference(s)
--------------------
src
src\ClassLibrary\ClassLibrary.csproj


nugetからパッケージを追加してみます。
csprojファイルに追加されるのでリストアすれば利用できるようになります。

dotnet add package Microsoft.Data.SQLite


同様にテストプロジェクトも作成します。

cd ..\..
mkdir test\ClassLibraryTest && cd test\ClassLibraryTest
dotnet new xunit
dotnet sln ..\..\Sample.sln add ClassLibraryTest.csproj


テストプロジェクトはクラスライブラリプロジェクトを参照する必要があるので
参照するようにコマンドを実行します。

dotnet add reference ..\..\src\ClassLibrary\ClassLibrary.csproj

csprojファイルに参照設定が追記されます。



ここまでの手順で2つのプロジェクトを持つソリューションが作成できました。
手順としては、VisualStudioのGUIで作成するよりは手間がかかりますが
最初だけなので問題ない範囲なのではないでしょうか?

問題点

少し余談的な話になるのですが、テストを記述して実行します。

VSCodeのコードレンズのrun testからテストを実行すると
「Failed to run test because null」
と表示されテストが実行できない。
多分、下記の問題なので待つしか無さそう。
github.com


今のところはコマンドで

dotnet test

でもコマンドだと結果が文字化けします。
これはターミナルの文字コードUTF-8にすれば解消されます。

chcp 65001

さらに余談

以前は、dotnet builddotnet restoreはプロジェクトファイルがある場所で実行するか
コマンドにパスを指定して1つ1つ実行する必要があったのですが
現在は、ソリューションのディレクトリでコマンドを実行すると
ソリューションに属するプロジェクトすべてに実行が可能になってます。
便利になったというか以前が不便すぎましたね。




弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
athome-inc.jp

Xamarin社内勉強会を開きました

こんにちは、情報システム部の高野です。
弊社では、不定期で社内勉強会を開催しているのですが
私が検証でXamarinを触っていたのでXamarinについて勉強会を開催しました。

と言っても私も詳しいことが分かるわけじゃないので
概要を簡単に説明しただけです。

内容的には以下のようなことを話しました。

  • Xamarin.FormsとXamarin.Nativeの違い
  • Xamarin.FormsでiOSアプリとAndroidアプリを作るデモ
  • Xamarin.Formsのカスタムコントロールの作り方


Xamarin.Formsでアプリを作成するデモで簡単に両OSのアプリが作れることと、
用意されていないコントロールを使用したい場合は
自身で作成する必要があることを説明しました。
嵌れば工数がかなり削減できるが
逆にカスタムコントロールを沢山作成したり
細かいレイアウトへの要望があると工数増になる可能性があり、
その辺りの見極めが重要だと理解してもらいました。


まだ弊社でXamarinを採用するのは決まってないですが
今回の勉強会で少しでもXamarinに興味を持ってもらえれば良いなと思います。


最後に勉強会の様子です。
f:id:taktak1974:20170303142938j:plain
こんな感じで希望者8人くらいに受講してもらいました。
普段、モバイル開発をやってない人も参加してくれて
Xamarinが注目されているのが分かりました。