MEBIUSTOSのブログ

主にUnityに関する技術的な事を書いていきます。 Twitter @xflutexx

OVRPlayerControllerのカメラの高さを操作する

そろそろOculus DK2を被っても快適な季節になってきたので、Oculusについて。

頭の高さを操作したい

まぁ、色々とあると思うんですよ。片手で頭の高さを操作したいシチュエーション。たとえば、ソファに座ってシャム猫を撫でつつも仮想世界でパンツ覗きたい時とか。

本来であれば、床に頭をこすりつけ無様な格好で覗くのが正しいVR作法な気もしますが……。でも、ほらあれですよ!小人になったシチュエーションとかにも使えるじゃないですか!!(適当)

コード

このコードをOVRPlayerControllerにアタッチすればオッケーです。UnityOculusUtilities_0_1_0_betaで動作確認しています。仕組みはTrackingSpaceのローカルポジションを弄っているだけです。

using UnityEngine;

public class OVRHeightController : MonoBehaviour {

    public float InitialAdjustment;

    public string UpKeyKeybord = "2";
    public KeyCode UpKeyCode = KeyCode.JoystickButton3;
    public string DownKeyKeybord = "1";
    public KeyCode DownKeyCode = KeyCode.JoystickButton0;
    public string RecenterKeybord = "r";
    public KeyCode RecenterCode = KeyCode.JoystickButton6;

    Transform TrackingSpaceTransform = null;

    void Start() {
        OVRCameraRig[] rigs;
        rigs = gameObject.GetComponentsInChildren<OVRCameraRig>();

        if (rigs.Length == 0)
            Debug.LogWarning("OVRCameraRig not found.");
        else if (rigs.Length > 1)
            Debug.LogWarning("OVRCameraRig some found.");
        else {
            this.TrackingSpaceTransform = rigs[0].transform.FindChild("TrackingSpace");
            var p = this.TrackingSpaceTransform.transform.localPosition;
            p.y += InitialAdjustment;
            this.TrackingSpaceTransform.transform.localPosition = p;
        }
    }

    void LateUpdate() {
        if (this.TrackingSpaceTransform) {
            var p = this.TrackingSpaceTransform.transform.localPosition;
            if (Input.GetKey(UpKeyKeybord) || Input.GetKey(UpKeyCode)) {
                p.y += 0.01f;
            } else if (Input.GetKey(DownKeyKeybord) || Input.GetKey(DownKeyCode)) {
                p.y -= 0.01f;
            }
            this.TrackingSpaceTransform.transform.localPosition = p;

            // Recenter Pose
            if (Input.GetKeyDown(RecenterKeybord) || Input.GetKeyDown(RecenterCode)) {
                OVRManager.display.RecenterPose();
            }
        }
    }
}

操作方法(初期設定の場合)

カメラ上昇 … 2キー or ジョイスティック ボタン3
カメラ下降 … 1キー or ジョイスティック ボタン0
カメラ中央位置再設定 … rキー or ジョイスティック ボタン6

【アップデート】 MMD4MecanimFaciem関連

MMDモデルの表情制御アセット「MMD4MecanimFaciem」関連をアップデートしました。

アップデート内容

MMD4MecanimFaciemアセット

FaciemForMecanimアセット

  • 表情オブジェクトにアタッチされているWeightコンポーネントに「Create AnimationClip File」ボタンを追加。ボタンで表情Weight値がプロパティとして設定されたAnimationClipを作成できます。

スクリプトでアニメーションクリップファイルを作成する

Unityのエディタスクリプトから、アニメーションクリップ(Animation Clip)ファイルを作成する方法です。

はじめに

先日公開したブレンドシェイプ値をアニメーションクリップとしてファイルに吐き出すコレ。

当初は複数のスキンメッシュに分散したブレンドシェイプを一箇所で操作したい!と、いうのが目的だったのですが、頑張ったらアニメーションクリップファイルを作成するまで出来てしまったので、その方法を記載しておきます。

余談ですが、後でMMD4MecanimFaciemForMecanim(MMD4Mecanimモデルの表情をメカニムで制御するツール)にもクリップ作成機能を搭載しようかと考え中。

超基本形

空のアニメーションクリップファイルを作るだけなら簡単です。

var animclip = new AnimationClip();
AssetDatabase.CreateAsset(
        animclip,
        AssetDatabase.GenerateUniqueAssetPath("Assets/test.anim")
    );
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();

うん。実にシンプル。
CreateAssetで作成して、SaveAssetsで保存して、Refreshで念のためアセットデータベースをリフレッシュ。

キーフレームとプロパティ値の設定

空のアニメーションクリップを作成しても何も嬉しくないので、プロパティを追加してみます。以下に示すコードはブレンドシェイプ「あ」のWeight値をプロパティとして設定したアニメーションクリップを作成するソースコードです。

var animclip = new AnimationClip();

EditorCurveBinding curveBinding = new EditorCurveBinding();
curveBinding.path = "SkinnedMeshObject";
curveBinding.type = typeof(SkinnedMeshRenderer);
curveBinding.propertyName = "blendShape.あ";

AnimationCurve curve = new AnimationCurve();
curve.AddKey(0f, 12.3f);

AnimationUtility.SetEditorCurve(animclip, curveBinding, curve);

AssetDatabase.CreateAsset(
        animclip,
        AssetDatabase.GenerateUniqueAssetPath("Assets/test.anim")
    );
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();

流れは下記のとおりです。

  1. EditorCurveBindingで、どのプロパティを操作するのかの指定。
  2. AnimationCurveで、キーフレームとプロパティ値の指定。
  3. AnimationUtility.SetEditorCurveで、アニメーションクリップにEditorCurveBindingとAnimationCurveを設定。

EditorCurveBinding

操作するプロパティを特定するため、3つのパラメータを指定します。

1. path

操作対象となるオブジェクトの名前を相対パスで指定します。ルートの場合は""になります。
先のソースコードではルートとなるオブジェクトの一階層下にある"SkinnedMeshObject"という名前のゲームオブジェクトを指定しています。

curveBinding.path = "SkinnedMeshObject";

別例として、以下のような階層構造をもつオブジェクトにおいてBオブジェクトを指定したい場合は次のようになります。

アニメーションクリップを適用するルートオブジェクト
 └Aオブジェクト(name:a_object)
  └Bオブジェクト(name:b_object)
curveBinding.path = "a_object/b_object";
2. type

操作するコンポーネントタイプを指定します。
先のソースコードではブレンドシェイプを操作するためSkinnedMeshRendererを指定しています。

curveBinding.type = typeof(SkinnedMeshRenderer);
3. propertyName

操作するプロパティを指定します。
ブレンドシェイプの場合は"blendShape." + ブレンドシェイプ名で指定します。(これを調べるのに紆余曲折あったのですが、それについては後ほど。)
先のソースコードではブレンドシェイプ「あ」を指定しています。

curveBinding.propertyName = "blendShape.あ";

AnimationCurve

キーフレーム時間と、プロパティの値を設定します。
AddKey(float time, float value)という感じで、第一引数にキーフレームの時間を、第二引数にプロパティの値を指定します。
先のソースコードではキーフレーム0秒、ブレンドシェイプのWeight値に12.3fを設定しています。

curve.AddKey(0f, 12.3f);

propertyNameに設定すべき値が分からなかった

当初、EditorCurveBindingのpropertyNameに対し、どのような形式で対象のブレンドシェイプを設定するのかが分かりませんでした。ネットを漁ったところ、エディタ拡張マニアクス 2015 (PDF)のP44-P49に、スプライトアニメーションを作成する場合におけるpropertyNameに設定すべき名前の調べ方がありました。そこで、ブレンドシェイプについても同様の方法にて調べてみたところ、SkinnedMeshRenderer内のm_BlendShapeWeights.Array.data[0]というパスに辿り着いたので、「これか!」と思い意気揚々とpropertyNameに設定してみたんですが、残念ながら動きませんでした。
悩んだ結果、正しく設定されたアニメーションクリップのpropertyNameがどうなっているのか調べるのが手っ取り早い!という結論に至り、アニメーションクリップ内のpropertyNameをDebugLogに出力するツールを作成しました。
それがこれです。(プロジェクトウィンドウ内のアニメーションクリップを選択)

using UnityEngine;
using UnityEditor;

public class ClipPropertyName : EditorWindow {
    [MenuItem("Window/Disp curve.propertyName")]
    static void ShowWindow() {
        EditorWindow.GetWindow<ClipPropertyName>();
    }

    void OnSelectionChange() {
        if (Selection.objects.Length > 1) {
            Debug.Log("Length: " + Selection.objects.Length);
        } else if (Selection.activeObject is AnimationClip) {
            foreach (var curve in AnimationUtility.GetCurveBindings((AnimationClip)Selection.activeObject)) {
                Debug.Log(curve.propertyName);
            }
        }
    }

    void OnGUI() {
        GUILayout.Label("Please select an Animation Clip");
    }
}

で、調べたらブレンドシェイプの場合は"blendShape." + ブレンドシェイプ名だった。という感じです。

pathの謎

EditorCurveBindingのpathはルートオブジェクトからの相対パスをゲームオブジェクトの名前で表すわけですが……。
「ゲームオブジェクトの名前って同じ場合あるよね?」
という疑問。同じ名前のオブジェクトがあった場合どうやって指定するんだろう……。
まぁとりあえず、現状困ってないので記憶の隅に追いやる。

ブレンドシェイプをAnimationClipとして保存する | Unity

f:id:mebius-tos:20150913203843j:plain
ブレンドシェイプ値を元にAnimationClipを作成するツールが出来ました。

特徴

  • 現在のブレンドシェイプ値を元にAnimationClipファイルを作成します。
  • 複数のSkinnedMeshに分散されたブレンドシェイプを一箇所で操作できます。

ダウンロード

BlendShapeEditHelper
(2015/11/29 リンク先を変更しました)

使い方

  • 対象となるモデル等にBlendShapeEditHelperスクリプトをアタッチして使用してください。
    f:id:mebius-tos:20150913211803j:plain
  • Create Clipボタンを押すと現在の全プレンドシェイプ値をAnimationClipファイルとして保存します。保存場所はAssets直下になります。
  • Hide Wireframeボタンでワイヤーフレームが消えるので表情を編集しやすくなります。
  • コンポーネントコンテキストメニューからタブ表示とリスト表示を切替える事が出来ます。
    f:id:mebius-tos:20150913211913j:plain
    f:id:mebius-tos:20150913211922j:plain

作成されるAnimationClip

  • Create Clipボタンを押すと、Assets直下に作成されます。
  • ファイル名は「オブジェクト名 + Clip」となります。
  • 同一名のAnimationClipが既に存在する場合は、末尾に数字が付きます。

AnimationClipの利用方法

Animatorにて表情の変更等に利用できます。ユニティちゃんアセットに付属されているシーン"ActionCheck"のAnimatorとFaceUpdateスクリプトが参考になると思います。

ユニティちゃん

本記事の説明にてユニティちゃんアセットを使用させて頂きました。

ユニティちゃんライセンス

このコンテンツは、『ユニティちゃんライセンス』で提供されています

特定のギズモをオン・オフするエディタ拡張 | Unity

シーンビューにおいて特定のギズモの表示をオン・オフするエディタ拡張例です。指定したギズモのみを操作する例がネット上で見つからず、かつ自分で必要になったので作りました。

下記の例では実行する度にCapsuleColliderのオン・オフをトグル操作しています。(Unity5.2にて動作確認)

using UnityEngine;
using UnityEditor;
using System.Reflection;

public static class GizmoToggle {
    [MenuItem("Tools/GizmoToggle(CapsuleCollider)")]
    private static void Toggle() {
        // Annotation
        var annotationType = System.Type.GetType("UnityEditor.Annotation, UnityEditor");
        var classIdField = annotationType.GetField("classID");
        var scriptClassField = annotationType.GetField("scriptClass");
        var gizmoEnabledField = annotationType.GetField("gizmoEnabled");

        // AnnotationUtility
        var annotationUtilityType = System.Type.GetType("UnityEditor.AnnotationUtility, UnityEditor");
        var getAnnotationsMethod = annotationUtilityType.GetMethod("GetAnnotations", BindingFlags.NonPublic | BindingFlags.Static);
        var setGizmoEnabledMethod = annotationUtilityType.GetMethod("SetGizmoEnabled", BindingFlags.NonPublic | BindingFlags.Static);

        // BaseObjectTools
        var baseObjectToolsType = System.Type.GetType("UnityEditorInternal.BaseObjectTools, UnityEditor");
        var ClassIDToStringMethod = baseObjectToolsType.GetMethod("ClassIDToString", BindingFlags.Public | BindingFlags.Static);

        // 
        var annotations = getAnnotationsMethod.Invoke(null, null) as System.Array;
        foreach (var n in annotations) {
            // getClassName
            object[] parameters = new object[] {
                    (int)classIdField.GetValue(n)
                };
            var className = (string)ClassIDToStringMethod.Invoke(null, parameters);
            Debug.LogFormat("className: {0}", className);

            if (className == "CapsuleCollider") {
                // getGizmoEnable
                var gizmoEnabled = (int)gizmoEnabledField.GetValue(n);

                // toggle (0:off, 1:on)
                parameters = new object[] {
                    (int)classIdField.GetValue(n),
                    (string)scriptClassField.GetValue(n),
                    gizmoEnabled == 1 ? 0 : 1
                };
                setGizmoEnabledMethod.Invoke(null, parameters);
            }
        }
    }
}

下記のUnityDecompiledソースがとても役に立ちました。

【アップデート】HeadLookControllerHelper

f:id:mebius-tos:20150906173657j:plain

お知らせ

HeadLookControllerHelperをアップデートしました。

ダウンロード

ダウンロードは下記ページからお願いします。

更新内容

Delay Setup機能を追加しました。デフォルトONになっていて、HeadLookControllerをモデルにAddComponentするタイミングを2フレーム遅らせます。
これにより、ポーズによってはずれた方向を向いてしまう事象が解決します。

問題事象

プロ生ちゃんに女豹のポーズ(ユニティちゃん Pose16)で見つめてもらいたい!とします。

シーン実行前の状態。シーンビューでは棒立ちでAnimatorのデフォルトは女豹(Pose16)。
f:id:mebius-tos:20150906221418j:plain

この状態でDelaySetupをOFFのままシーンを実行してみると、ターゲットからずれた方向を向きます。
f:id:mebius-tos:20150906221441j:plain

これはシーン開始直後はAnimatorがデフォルトステート(Pose16)に遷移しておらず、HeadLookControllerがシーン開始前のポーズ(この例では棒立ち)を基本ポーズとして認識してしまう為と思われます。
そこで、シーン開始後に完全にポーズを取るのを待ってからHeadLookControllerをAddComponentした。というわけです。
f:id:mebius-tos:20150906221454j:plain

使用モデル

説明に使用したモデルは「プロ生ちゃん(暮井 慧)」です。

使用ポーズ

ポーズにはユニティちゃんアセットを使用しました。

ユニティちゃんライセンス

このコンテンツは、『ユニティちゃんライセンス』で提供されています

プロ生ちゃん壁紙

前回の記事で使用したプロ生ちゃん画像の壁紙化要望があったので、改めてUnityで頑張って作ってみました。プロ生ちゃんの魅力が伝われ!
(2015/9/5 追記:プログラミング生放送さんの記事にて紹介して頂きました。感謝!)

※ 高画質版はOneDriveよりダウンロードして下さい。それぞれ1920x1080、3570x2008のサイズを用意しています。

f:id:mebius-tos:20150905144924j:plain
f:id:mebius-tos:20150905144954j:plain

ダウンロード

OneDrive

プロ生ちゃん公式壁紙はこちら

pronama.azurewebsites.net