20200115のUnityに関する記事は15件です。

VRC上の表情アニメーションとまばたきの干渉を防ぐ

免責事項

本内容はVRoidモデルでしか試していません。
VRoid以外でできた/できなかったの報告ウェルカムです!

くらげさんのBehaviour-Enabledを使う方法

手順

  • zipを解凍して中のBehaviour-Enabled.animを適当にUnityにインポート
  • 生贄プレハブを作り、アニメーションコントローラーの中にBehaviour-Enabled.animを追加する
  • アニメーションウィンドウを開くとBehaviour-Enabled.animが黄色(Missing)になっているので、選択+F2キー等からBody/Body-expressionBodyにリネームしてあげる
    • ※VRoidの仕様が変更になったら




- アニメーションウィンドウ上でBody:Behaviour.Enabledをコピーし、まばたきを止めたいアニメーションに貼り付ける
- Body:Behaviour.Enabledの右側のチェックボックスを外した状態で全てのフレームにコピーする


~おわり~

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

UnityでValveIndexコントローラーの指入力を取る

UnityでValveIndexコントローラーの指入力を取る方法です

前提条件

  • SteamVRは2.0以降を使用します
  • Action Setsはdefaultを使用します

実行環境

  • Windows10
  • Unity 2019.2.8f1
  • SteamVR Plugin 2.5.0

SteamVR Inputの設定

Asset StoreでSteamVRをimport後、メニューの「Window」タブから「SteamVR Input」を選択します
SaveImage2.png
actions.jsonが無いみたいなことを言われるので、「Yes」を選択します
notexit.png
SteamVR Inputが開けたら、「Save and generate」を選択します
saveandgene.png

スクリプトの作成

InputTest.cs
using UnityEngine;
using Valve.VR;

public class InputTest : MonoBehaviour
{
    // 指の入力
    public SteamVR_Action_Skeleton actionSkeleton;

    void Update()
    {
        Debug.Log(
            "親指:" + actionSkeleton.thumbCurl +
            "人差し指:" + actionSkeleton.indexCurl +
            "中指:"+actionSkeleton.middleCurl +
            "薬指:"+actionSkeleton.ringCurl +
            "小指:"+actionSkeleton.pinkyCurl
            );
    }
}

SteamVR_Action_Skeleton

指の入力アクションの状態を取ってくるためのクラスです

thumbCurl~pinkyCurl

それぞれの指をどれだけ握っているか「0~1.0」のfloat値で表現されます

パラメータ 説明
thumbCurl 親指の握りを表します
親指の値は少し特殊で、以下のような値が入ります
・スティックに指を置いた場合:約0.4
・タッチパッドに指を置いた場合:約0.5
・ボタンに指を置いた場合:約0.8
indexCurl 人差し指の握りを表します
middleCurl 中指の握りを表します
ringCurl 薬指の握りを表します
pinkyCurl 小指の握りを表します

実行の準備

Main Cameraを削除して[CameraRig]をHierarchyに追加します
scene.png
適当にGameObjectを作成して先ほど作成したスクリプト「InputTest.cs」をアタッチします
inspectorから指の入力を取得する手を選択します
今回は左手の指入力を取得するので「\action\default\in\SkeletonLeftHand」を選択します
ins.png

実行

ゲームを再生して左手のValveIndexコントローラーを握ると、デバッグログの値が変化します
input.png

参考サイト

Class SteamVR_Action_Skeleton

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

【Unity】【Shader】周りのオブジェクトに影を描画する

はじめに

例えばDiffuseのShaderを自作すると、それだけでは周りのオブジェクトに影が描画されない。影の実装も自分で行う必要があるので、それをやってみる。
*ちなみに今回の実装は、自身が周りのオブジェクトに影を描画するだけで、自身が影を受け取る訳ではない。

Screen Shot 2020-01-15 at 18.17.38.png

Passが二つ

今回の方法では、Pass{}を二つ記述する必要がある。一つ目のPassでオブジェクトの色を描画、二つ目のPassで影を描画することになる。

"LightMode"="ShadowCaster"

LightModeタグはそのPassが光を描画する中で、どんな役割を担うかを定義する。

LightMode tag defines Pass’ role in the lighting pipeline.

影を描画する場合は、Tags{ "LightMode"="ShadowCaster" }とする。これによって、周りのオブジェクトに影を描画するようになる。

ShadowCaster: Renders object depth into the shadowmap or a depth texture.

ShaderLab: Pass Tags

multi_compile_shadowcasterを追加

#pragma multi_compile_shadowcasterを追加する。これでコンパイラに影を描画したいって伝えるらしい?*要確認

マクロ

実際の描画処理はマクロを呼び出すだけで完了する。使うのは以下の3つ。

V2F_SHADOW_CASTER
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
SHADOW_CASTER_FRAGMENT(i)

結果

無事に影が描画されるようになりました。

Screen Shot 2020-01-15 at 18.24.40.png

今回のコード

Pass
{
    // 一つ目のパス。オブジェクト自身を描画する。
}
Pass
 {
     Tags{ "LightMode"="ShadowCaster" }

     CGPROGRAM
     #pragma vertex vert
     #pragma fragment frag
     #pragma multi_compile_shadowcaster

     #include "UnityCG.cginc"

     struct appdata
     {
         float4 vertex : POSITION;
         float3 normal : NORMAL;
         float2 texcoord : TEXCOORD0;
     };

     struct v2f
     {
         V2F_SHADOW_CASTER;
     };

     v2f vert (appdata v)
     {
         v2f o;
         TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
         return o;
     }

     fixed4 frag (v2f i) : SV_Target
     {
         SHADOW_CASTER_FRAGMENT(i)
     }
     ENDCG
 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】【Shader】影を描画する、影を受け取る

はじめに

例えばDiffuseのShaderを自作すると、それだけでは影が描画されない。影の実装も自分で行う必要があるので、それをやってみる。

Screen Shot 2020-01-15 at 18.17.38.png

周りのオブジェクトに影を描画する

まずは、自身が周りのオブジェクトから影を受けるようにする。

Passが二つ

今回の方法では、Pass{}を二つ記述する必要がある。一つ目のPassでオブジェクトの色を描画、二つ目のPassで影を描画することになる。

"LightMode"="ShadowCaster"

LightModeタグはそのPassが光を描画する中で、どんな役割を担うかを定義する。

LightMode tag defines Pass’ role in the lighting pipeline.

影を描画する場合は、Tags{ "LightMode"="ShadowCaster" }とする。これによって、周りのオブジェクトに影を描画するようになる。

ShadowCaster: Renders object depth into the shadowmap or a depth texture.

ShaderLab: Pass Tags

multi_compile_shadowcasterを追加

#pragma multi_compile_shadowcasterを追加する。これでコンパイラに影を描画したいって伝えるらしい?*要確認

マクロを使用する

実際の描画処理はマクロを呼び出すだけで完了する。使うのは以下の3つ。

V2F_SHADOW_CASTER
TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
SHADOW_CASTER_FRAGMENT(i)

結果

無事に影が描画されるようになりました。

Screen Shot 2020-01-15 at 18.24.40.png

ここまでのコード

Pass
{
    // 一つ目のパス。オブジェクト自身を描画する。
}
Pass
 {
     Tags{ "LightMode"="ShadowCaster" }

     CGPROGRAM
     #pragma vertex vert
     #pragma fragment frag
     #pragma multi_compile_shadowcaster

     #include "UnityCG.cginc"

     struct appdata
     {
         float4 vertex : POSITION;
         float3 normal : NORMAL;
         float2 texcoord : TEXCOORD0;
     };

     struct v2f
     {
         V2F_SHADOW_CASTER;
     };

     v2f vert (appdata v)
     {
         v2f o;
         TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
         return o;
     }

     fixed4 frag (v2f i) : SV_Target
     {
         SHADOW_CASTER_FRAGMENT(i)
     }
     ENDCG
 }

影を受け取る

ここまでの方法では自身が他のオブジェクトの影を受け取ることが出来ない。次にそこを改善してみる。

ひとつめのPassを変更する

他オブジェクトに影を落とすために二つ目のPassを記述したが、自身が影を受け取る場合は、ひとつめのPassを変更する。

pragmaを追加

もうなんの事か分からないけど、これを追加するらしい

#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight

includeも追加

これも何の事か分かってないけど追加する

#include "Lighting.cginc" 
#include "AutoLight.cginc"

Vertex and fragment shader examples

マクロを追加

SHADOW_COORDS, TRANSFER_SHADOW, SHADOW_ATTENUATIONを使用して影を計算する。
TRANSFER_SHADOWにv2fを渡す際はvertexという名前を受け付けないので、posに変更する。
以下のコードにコメントしてある箇所。

結果

無事に影を受け取ることができた。

Screen Shot 2020-01-16 at 8.35.08.png

今回のコード

Shader "Unlit/ShadowVF"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
            #include "UnityCG.cginc" 
            #include "UnityLightingCommon.cginc"
            #include "Lighting.cginc" 
            #include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                fixed4 diff : COLOR0;
                float4 pos : SV_POSITION; // posに変更する!!TRANSFER_SHADOWがposとうい名前でないと受け付けない。
                SHADOW_COORDS(1)
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                // ここの左辺もposに変更
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                half NdotL = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz));
                o.diff = NdotL * _LightColor0;
                TRANSFER_SHADOW(o)

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                // 影を計算
                fixed4 shadow = SHADOW_ATTENUATION(i);
                col *= i.diff * shadow;
                return col;
            }
            ENDCG
        }

        Pass
        {
            Tags{ "LightMode"="ShadowCaster" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                V2F_SHADOW_CASTER;
            };

            v2f vert (appdata v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }

    }
}

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

【Unity】Matrixを使って回転させる。

回転を扱いたい場合はQuaternion.Euler()を使うことが多いが、Matrixを使用すると便利なことがあったのでメモ。 図の場合でQuaternion.Euler()を使用した場合とMatrixを使用した場合を考えてみる。
赤いSphereが原点で、(1, 0, 0)にQuadを配置。
Screen Shot 2020-01-15 at 17.41.15.png

Quaternion.Euler()

下のコードのようにQuaternion.Euler()を使用して回転させると、Quadのポジションは(1, 0, 0)のままでその場で回転をすることになる。

/// <summary>
/// Quaternionを使用して回転
/// </summary>
void RotateWithQuaternion()
{
    var a = _tf.rotation.eulerAngles;
    // deltaTimeをy軸周りの回転に変換
    var newA = new Vector3(a.x, a.y + Time.deltaTime * 100, a.z);
    var newR = Quaternion.Euler(newA);
    _tf.rotation = newR;
}

では、原点を中心に回転させたい場合はどうするかってなると、Quaternion.Euler()ではちょっと難しそう。そこでMatrixを使用する。

Matrix4x4

とりあえずコードから。これでQuadが原点からの距離を一定に保ったまま、原点を中心にぐるぐる回ることになる。

/// <summary>
/// Matrixを使用して回転
/// </summary>
void RotateWithMatrix()
{
    // 回転行列を作成
    // deltaTimeをy軸周りの回転に変換。変化量を渡す
    var angle  = new Vector3(0f, Time.deltaTime * 100, 0f);
    var rotate = Quaternion.Euler(angle);
    var matrix = new Matrix4x4();
    matrix.SetTRS(Vector3.zero, rotate, Vector3.one);

    // 現在のモデル変換行列に変換行列をかけることによって回転させる
    var newM = matrix * _tf.localToWorldMatrix;

    // 移動成分をとりだす
    var pos = new Vector3(newM.m03, newM.m13, newM.m23);
    _tf.position = pos;
    // 回転成分をとりだす
    var rot = newM.rotation;
    _tf.rotation = rot;
    // 拡大縮小成分を取り出す
    var scale = newM.lossyScale;
    _tf.localScale = scale;
}

原点を中心に回転することが確認できた。
Screen Shot 2020-01-15 at 17.46.03.png

Quaternion.Eulerと違う点として、求める角度をそのまま渡すのではない点がある。var angle = new Vector3(0f, Time.deltaTime * 100, 0f);の部分では変化量を渡している。おそらくlocalToWorldMatrixが毎フレーム更新されるので、前回フレームまでの変更を反映したlocalToWorldMatrixに今回の変化量だけを反映すれば良いってことだと思われる。

Matrixを使いまわせる

ってことで、複数オブジェクトに同じ変更を反映させたい場合は同じMatrixを使い回すことができる。 次の例では回転用のMatrixをひとつ作成して、全てのquadを同じように回転させている。

using System.Collections.Generic;
using UnityEngine;

public class TestMatrix : MonoBehaviour
{
    [SerializeField] private GameObject quad;

    private List<GameObject> quads;

    void Start()
    {
        quads = new List<GameObject>();

        for (int i = 0; i < 36; i++) {
            var quad = Instantiate(this.quad, transform);
            var x = Mathf.Cos(i * 10 * Mathf.Deg2Rad);
            var z = Mathf.Sin(i * 10 * Mathf.Deg2Rad);
            quad.transform.position = new Vector3(x, 0, z);

            var angle = new Vector3(0, -i * 10f, 0);
            quad.transform.rotation = Quaternion.Euler(angle);

            quads.Add(quad);
        }

        Destroy(this.quad);
    }

    private void Update()
    {
        var matrix = GetMatrix();
        foreach (var quad in quads) {
            RotateWithMatrix(matrix, quad.transform);
        }
    }

    /// <summary>
    /// 回転させる為のMatrix
    /// </summary>
    /// <returns></returns>
    private Matrix4x4 GetMatrix()
    {
        // 回転行列を作成
        // deltaTimeをy軸周りの回転に変換。変化量を渡す
        var angle  = new Vector3(0f, Time.deltaTime * 100, 0f);
        var rotate = Quaternion.Euler(angle);
        var matrix = new Matrix4x4();
        matrix.SetTRS(Vector3.zero, rotate, Vector3.one);

        return matrix;
    }

    /// <summary>
    /// Matrixを使用して回転
    /// </summary>
    void RotateWithMatrix(Matrix4x4 matrix, Transform tf)
    {
        // 現在のモデル変換行列に変換行列をかけることによって回転させる
        var newM = matrix * tf.localToWorldMatrix;

        // 移動成分をとりだす
        var pos = new Vector3(newM.m03, newM.m13, newM.m23);
        tf.position = pos;
        // 回転成分をとりだす
        var rot = newM.rotation;
        tf.rotation = rot;
        // 拡大縮小成分を取り出す
        var scale = newM.lossyScale;
        tf.localScale = scale;
    }
}

結果はこんな感じ。全てのQuadが原点を中心に回転していることが確認できた。
Screen Shot 2020-01-15 at 17.49.55.png

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

Unityゲーム制作 ライトのON/OFF 切り替え方

---ゲーム内で使用するライトのON/OFFのコード---

Zキーを押している間だけライトを消すシステムを今回はobjectを消すという手法で行いました。

まずUnityのHierarchyに、ライト本体を入れなくてはいけません

Asset storeからライト本体を探してきて(Flashlight)ダウンロードします。
https://assetstore.unity.com/packages/3d/props/electronics/flashlight-18972
7.png

そのあと、本体の中に光の部分(自分の場合 UnityEffectのSpot Light)を使って作っておきます。
1.png

次にこのコードを打ち込んで保存します。

GameManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour
{

    public GameObject Sphere;
    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

        if (Input.GetKeyDown(KeyCode.Z))
        {

            Sphere.SetActive(true);

        }

        if (Input.GetKeyUp(KeyCode.Z))
        {

            Sphere.SetActive(false);

        }

    }
}

その後、HierarchyでCreate→Create Emptyを作成し名前をGameManagerとし、先ほど作ったScriptをアタッチしてください。

2.png
そして、ライトの光の部分(自分の場合Spot Light)のobjectをSphereにドラック&ドロップするだけで完成します。

カメラの位置を調整・ゲームをスタートし、Zキーを押すとライトの光がつくと思います、離すと消えます。

以上ライトのON/OFFのスクリプト導入でした。お疲れさまです!!('ω')ノ

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

Unityでボタンを使ってシーンをロードする際に汎用性のあるプログラム

いちいちシーンロードするプログラムを書かなくても簡単にロードしたいシーンに移動できるプログラムを自分なりに考えてみました。
ぜひ、参考にしてみてください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; //シーン移動するために必要
using UnityEngine.UI;              //UIを使う際に必要

public class LoadSceneButton : MonoBehaviour
{
    //[SerializeField]を使うことでpraivateの状態で
    //publicと同じようにunityのInspectorで入力できる
    [SerializeField] string putsceneName = "";

    public void LoadSceneButtonDown()
    {
        //Inspectorで入力された名前が(loadsceneName)に入り
        //そのシーンをロードする
        SceneManager.LoadScene(loadsceneName);
    }
}

上記のプログラムをボタンにアタッチしprefab化するとステージセレクトボタンを作る際に楽になると思います。
自分自身まだプログラムの経験が浅いためこのプログラムで大丈夫かが不安ですが役立つことができたら幸いです。

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

Unityでボタンを使ってシーンをロードする際のプログラム

いちいちシーンロードするプログラムを書かなくても簡単にロードしたいシーンに移動できるプログラムを自分なりに考えてみました。
ぜひ、参考にしてみてください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; //シーン移動するために必要
using UnityEngine.UI;              //UIを使う際に必要

public class LoadSceneButton : MonoBehaviour
{
    //[SerializeField]を使うことでpraivateの状態で
    //publicと同じようにunityのInspectorで入力できる
    [SerializeField] string putsceneName = "";

    public void LoadSceneButtonDown()
    {
        //Inspectorで入力された名前が(loadsceneName)に入り
        //そのシーンをロードする
        SceneManager.LoadScene(loadsceneName);
    }
}

上記のプログラムをボタンにアタッチしprefab化するとステージセレクトボタンを作る際に楽になると思います。
自分自身まだプログラムの経験が浅いためこのプログラムで大丈夫かが不安ですが役立つことができたら幸いです。

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

【Unity】Jsonファイルとしてデータを保存する

はじめに

このアカウントは、プログラムについて勉強し始めて1年立たずの専門学校生によるものです。
新たに学んだ技術や知識をなるべくわかりやすくまとめておこうと思います。
アドバイス・ご指摘などありましたら、どうぞご遠慮無く。

データの保存

ゲーム制作をしていると必ずぶつかる壁にデータの保存があると思います。
今回は、私がゲーム制作で使用した保存方法とスクリプトをご紹介します。

Jsonファイルとしてデータを保存・読み書きする

データの保存方法にも何種類かありますが、今回はJsonファイルで保存する方法を使ってみました。
今回のスクリプトはこちらtitleの記事を参考にさせていただきました。
(kanのメモ帳-個人ゲーム開発者kan.kikuchiのメモ的技術ブログ-/クラスを丸ごと保存するデータ管理方法【Unity】)

スクリプト

魚を取るゲームを制作していたので、所々魚関係の変数や名前が出てきます。

PlayerDataInstance.cs
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEditor;

/// <summary>
/// .jsonファイルでプレイヤーデータを保存する
/// </summary>
[Serializable]
public class PlayerDataInstance : ISerializationCallbackReceiver
{
    //プレイヤーデータの実態、初アクセス時にデータをロード
    private static PlayerDataInstance _instance = null;
    public static PlayerDataInstance Instance
    {
        get
        {
            if (_instance == null)
            {
                Load();
            }
            return _instance;
        }
    }

    [SerializeField]
    private static string _jsonText = "";

    //================================================
    //保存されるデータ
    //================================================

    [SerializeField]
    int                             money;         // 所持金
    [SerializeField]
    string                          lanceName;  // 装備しているLanceStatusDataの名前
    [SerializeField]
    private string                  _fishDictJson = "";
    public Dictionary<string, Fish> FishDict = new Dictionary<string,Fish>();// 銛で突いた魚の名前と数を保存

    //================================================
    //シリアライズ、デシリアライズ時のコールバック
    //================================================

    /// <summary>
    /// PlayerData->Jsonに変換される前に実行される。
    /// </summary>
    public void OnBeforeSerialize()
    {
        //Dictionaryはそのまま保存されないので、個別にシリアライズしてテキストで保存
        _fishDictJson = Serialize(FishDict);
    }


    /// <summary>
    /// Json->PlayerDataに変換された後に実行される。
    /// </summary>
    public void OnAfterDeserialize()
    {
        //保存されているテキストがあればDictionaryにデシリアライズ
        if (!string.IsNullOrEmpty(_fishDictJson))
        {
            FishDict = Deserialize<Dictionary<string, Fish>>(_fishDictJson);
        }
    }

    /// <summary>
    /// 引数のオブジェクトをシリアライズして返す
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <returns></returns>
    private static string Serialize<T>(T obj)
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        MemoryStream memoryStream       = new MemoryStream();
        binaryFormatter.Serialize(memoryStream, obj);
        return Convert.ToBase64String(memoryStream.GetBuffer());
    }
    /// <summary>
    /// 引数のテキストを指定されたクラスにデシリアライズして返す。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="str"></param>
    /// <returns></returns>
    private static T Deserialize<T>(string str)
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        MemoryStream memoryStream       = new MemoryStream(Convert.FromBase64String(str));
        return (T)binaryFormatter.Deserialize(memoryStream);
    }

    //================================================
    //取得
    //================================================

    /// <summary>
    /// データを再読み込みする。
    /// </summary>
    public void Reload()
    {
        JsonUtility.FromJsonOverwrite(GetJson(), this);
    }

    /// <summary>
    /// データを読み込む。
    /// </summary>
    private static void Load()
    {
        _instance = JsonUtility.FromJson<PlayerDataInstance>(GetJson());
    }


    private static string GetJson()
    {
        //既にJsonを取得している場合はそれを返す。
        if (!string.IsNullOrEmpty(_jsonText))
        {
            return _jsonText;
        }

        //Jsonを保存している場所のパスを取得
        string filePath = GetSaveFilePath();

        //Jsonが存在するか調べてから取得し変換する。存在しなければ新たなクラスを作成し、それをJsonに変換する。
        if (File.Exists(filePath))
        {
            _jsonText = File.ReadAllText(filePath);
        }
        else
        {
            _jsonText = JsonUtility.ToJson(new PlayerDataInstance());
        }

        return _jsonText;
    }

    //================================================
    //保存
    //================================================

    /// <summary>
    /// データをJsonにして保存する。
    /// </summary>
    public void Save()
    {
        _jsonText = JsonUtility.ToJson(this);
        File.WriteAllText(GetSaveFilePath(), _jsonText);
    }

    //================================================
    //削除
    //================================================

    /// <summary>
    /// データをすべて削除し、初期化する。
    /// </summary>
    public void Delete()
    {
        _jsonText = JsonUtility.ToJson(new PlayerDataInstance());
        Reload();
    }

    //================================================
    //保存先のパス
    //================================================

    private static string GetSaveFilePath()
    {
        string filePath = "PlayerDataInstance";
        //確認しやすいようにエディタではAssetsと同じ階層に保存
        //それ以外ではApplication.persistentDataPath以下に保存するように。
#if UNITY_EDITOR
        filePath += ".json";
#else
        filePath = Application.persistentDataPath + "/" + filePath;
#endif
        Debug.Log(filePath);
        return filePath;
    }

    //================================================
    //PlayerDataUtility
    //================================================

    /// <summary>
    /// 所持金を取得する。
    /// </summary>
    /// <returns></returns>
    public int GetMoney()
    {
        return money;
    }

    /// <summary>
    /// 装備品の名前を取得する
    /// </summary>
    /// <returns></returns>
    public string GetLanceName()
    {
        return lanceName;
    }

    /// <summary>
    /// 銛で獲った魚の名前と数を取得する
    /// </summary>
    /// <returns></returns>
    public Dictionary<string,Fish> GetFish()
    {
        return FishDict;
    }

    /// <summary>
    /// 保存するデータを設定する。
    /// </summary>
    /// <param name="data"></param>
    public void SetPlayerData(PlayerData data)
    {
        LanceStatusData _lsd = data.GetLance();
        lanceName            = _lsd.GetEquipmentName();
        money                = data.GetMoney();
        FishDict             = data.GetFish();
    }
}

使用例

上のスクリプトを実際に使用したものを以下に記載します。

PlayerData.cs
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEditor;

[Serializable]
public class PlayerData : MonoBehaviour
{
//(中略)
    private void Start()
    {
        //保存しておいたデータを取得する
        //static変数を取得
        PlayerDataInstance _pInstance = PlayerDataInstance.Instance;
        dataList                      = gameObject.GetComponent<DataList>();

        //各データを保存していたファイルから取得
        string lanceName = _pInstance.GetLanceName();

        //初期状態だとlanceNameが""なので、初期装備を設定
        //lanceNameが存在するなら該当する装備を取得
        if (lanceName == "")
        {
            lance          = dataList.GetLance("ボロのモリ");
            lance.IsBought = true;
        }
        else
        {
            lance = dataList.GetLance(lanceName);
        }
        money  = _pInstance.GetMoney();
        fishes = _pInstance.GetFish();
    }
//以下省略
}

このように、PlayerDataInstanceで設けたstatic変数を呼び出せば、保存していたデータを取得することができます。
データの上書き保存やデータ削除も同様に、static変数から“Save()”、“Delete()”を呼び出すことで可能になります。

使用上の注意

ここでは実際に私が失敗したり悩んだ部分、ほかの使い方などを書いておきます。

InstanceDataのスクリプト(1つ目のスクリプト)はオブジェクトにアタッチできない

スクリプトの属性がMonoBehaviorではないため、オブジェクトにアタッチすることができません。
その代わり、オブジェクトにアタッチしなくても、スクリプトがプロジェクト内に存在すれば機能してくれます。

InstanceDataのスクリプトのみで十分使用可能

作っていたゲームの構造上、プレイヤーデータ本体(2つ目のスクリプト)と保存部分(1つ目のスクリプト)を分けましたが、保存部分のみでも使用可能です。
例えば、ゲームを通して常にプレイヤーが存在する場合(RPGなど)はstatic変数を呼び出すことで問題なく動作するはずです。

.jsonファイルは開発中はプロジェクトのフォルダ内に作成される

.jsonファイルはプロジェクトのフォルダ(Assetsなどと同じ)内に生成されます。
DataPathを見ていただければわかるかと思いますが、iOSのみ保存場所が異なります。

まとめ

データの保存には、ほかにもいろいろな方法があります。
例えば、Unityに実装されているPlayerPrefsを利用する方法なんかが有名です。
また、改善点などありましたらコメントにてご教授いただけると、私のスキルアップにつながります。
ぜひよろしくお願いいたします。

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

UnityでOculus QuestハンドトラッキングSDKを使ってみる

Oculus Quest のハンドトラッキングで下記を試します。

  • 手の表示
  • キューブをタッチして色を変える
  • ピンチ操作でキューブの色を変える

環境

Unity 2019.2.17f1

事前準備

  • UnityでQuest用のビルドができること
  • Oculus Integration Assetsを追加していること

Scene作成

Hierarchy
Oculus > VR > Prefabs > OVRCameraRig を追加
(最初にあったMainCameraは削除しておく)

Inspector
OVR Manager (script) > Input > Hand Tracking Support → [Controllers and Hands]
OVR Manager (script) > Tracking > Tracking Origin Type → [Floor Level]

image.png

手を追加

Hierarchy
Oculus > VR > Prefabs > OVRHandPrefab を LeftHandAnchor と RightHandAnchor の下に追加

image.png

Inspector
OVRHand、OVRSkeleton、OVRMesh の各 HandType に Hand Left または Hand Right を選択
OVRSkeleton の Enable Physics Capsules にチェック

image.png

スケルトン表示にする場合

OVRHandPrefab の OVR Skeleton Renderer にチェック

カスタムマテリアルにする場合

OVRHandPrefab の Skinned Mesh Renderer で Materials > Element0 を好きなマテリアルに変える

ビルド確認

ハンドトラッキングされた手が表示されていればOK

v1nh4-ulwxs.gif

左がスケルトン、右がカスタムマテリアル

com.qvtecCompany.OculusQuestHandTracking-20200115-234849.jpg

衝突イベント

キューブをタッチして色を変えてみます。

Hierarchy
Cube を追加して位置と大きさを適当に調整(size:0.2, position: 0,0.5,1)

Inspector
Box Collider の Is Triggerをチェック

ChangeColor.csスクリプトを作成し、Cube に追加

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

public class CubeTouch : MonoBehaviour
{
    private Material material;

    void Start()
    {
        material = this.gameObject.GetComponent<Renderer>().material;
    }

    private void OnTriggerEnter(Collider collider)
    {
        if (collider.gameObject.name == "Hand_Index3_CapsuleCollider")
        {
            material.color = Color.red;
        }
    }

    private void OnTriggerExit(Collider collider)
    {
        if (collider.gameObject.name == "Hand_Index3_CapsuleCollider")
        {
            material.color = Color.white;
        }
    }
}

ヒットしたら赤、外れたら白にマテリアルを更新しています。
colliderの名前は Hand_XXX_CapsuleCollider だったので、BoneIdから判定したいボーンを指定します。

OVRPlugin.cs
public enum BoneId
{
  Invalid                 = -1,

  Hand_Start              = 0,
  Hand_WristRoot          = Hand_Start + 0, // root frame of the hand, where the wrist is located
  Hand_ForearmStub        = Hand_Start + 1, // frame for user's forearm
  Hand_Thumb0             = Hand_Start + 2, // thumb trapezium bone
  Hand_Thumb1             = Hand_Start + 3, // thumb metacarpal bone
  Hand_Thumb2             = Hand_Start + 4, // thumb proximal phalange bone
  Hand_Thumb3             = Hand_Start + 5, // thumb distal phalange bone
  Hand_Index1             = Hand_Start + 6, // index proximal phalange bone
  Hand_Index2             = Hand_Start + 7, // index intermediate phalange bone
  Hand_Index3             = Hand_Start + 8, // index distal phalange bone
  Hand_Middle1            = Hand_Start + 9, // middle proximal phalange bone
  Hand_Middle2            = Hand_Start + 10, // middle intermediate phalange bone
  Hand_Middle3            = Hand_Start + 11, // middle distal phalange bone
  Hand_Ring1              = Hand_Start + 12, // ring proximal phalange bone
  Hand_Ring2              = Hand_Start + 13, // ring intermediate phalange bone
  Hand_Ring3              = Hand_Start + 14, // ring distal phalange bone
  Hand_Pinky0             = Hand_Start + 15, // pinky metacarpal bone
  Hand_Pinky1             = Hand_Start + 16, // pinky proximal phalange bone
  Hand_Pinky2             = Hand_Start + 17, // pinky intermediate phalange bone
  Hand_Pinky3             = Hand_Start + 18, // pinky distal phalange bone
  Hand_MaxSkinnable       = Hand_Start + 19,
  // Bone tips are position only. They are not used for skinning but are useful for hit-testing.
  // NOTE: Hand_ThumbTip == Hand_MaxSkinnable since the extended tips need to be contiguous
  Hand_ThumbTip           = Hand_Start + Hand_MaxSkinnable + 0, // tip of the thumb
  Hand_IndexTip           = Hand_Start + Hand_MaxSkinnable + 1, // tip of the index finger
  Hand_MiddleTip          = Hand_Start + Hand_MaxSkinnable + 2, // tip of the middle finger
  Hand_RingTip            = Hand_Start + Hand_MaxSkinnable + 3, // tip of the ring finger
  Hand_PinkyTip           = Hand_Start + Hand_MaxSkinnable + 4, // tip of the pinky
  Hand_End                = Hand_Start + Hand_MaxSkinnable + 5,

  // add new bones here

  Max = Hand_End + 0,
}

https://en.wikipedia.org/wiki/Phalanx_bone#/media/File:Scheme_human_hand_bones-en.svg
0から3にかけて指先になる

ピンチ操作

ピンチ操作でキューブの色を変えてみます。

手の表示で追加した OVRHandPrefab に MyHand.cs スクリプトを追加

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

public class MyHand : MonoBehaviour
{    
    private OVRHand hand;
    private Material material;

    void Start()
    {
        material = GameObject.Find("Cube").GetComponent<Renderer>().material;
        hand = this.gameObject.GetComponent<OVRHand>();
    }

    void Update()
    {
        if (hand.GetFingerIsPinching(OVRHand.HandFinger.Middle))
        {
            material.color = Color.blue;
        }
        else if (material.color == Color.blue)
        {
            material.color = Color.white;
        }
    }
}

中指と親指をくっつけると青、離すと白にマテリアルを更新しています。

完成

ちゃんと中指以外は色が変わらない!
両手使える!

gumhj-05rps.gif

公式サイト
https://developer.oculus.com/documentation/unity/latest/concepts/unity-handtracking/

さいごに

薬指と小指は重なってしまって誤動作しやすい。
何か操作に使う場合は中指が良さそう。
次は SDK の SampleFramework を使った方法も試してみる。

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

UnityでOculus Questのハンドトラッキングを使ってみる(タッチ&ピンチ)

Oculus Quest のハンドトラッキングで下記を試します。

  • 手の表示
  • キューブをタッチして色を変える
  • ピンチ操作でキューブの色を変える

環境

Unity 2019.2.17f1

事前準備

  • UnityでQuest用のビルドができること
  • Oculus Integration Assetsを追加していること

Scene作成

Hierarchy
Oculus > VR > Prefabs > OVRCameraRig を追加
(最初にあったMainCameraは削除しておく)

Inspector
OVR Manager (script) > Input > Hand Tracking Support → [Controllers and Hands]
OVR Manager (script) > Tracking > Tracking Origin Type → [Floor Level]

image.png

手を追加

Hierarchy
Oculus > VR > Prefabs > OVRHandPrefab を LeftHandAnchor と RightHandAnchor の下に追加

image.png

Inspector
OVRHand、OVRSkeleton、OVRMesh の各 HandType に Hand Left または Hand Right を選択
OVRSkeleton の Enable Physics Capsules にチェック

image.png

スケルトン表示にする場合

OVRHandPrefab の OVR Skeleton Renderer にチェック

カスタムマテリアルにする場合

OVRHandPrefab の Skinned Mesh Renderer で Materials > Element0 を好きなマテリアルに変える

ビルド確認

ハンドトラッキングされた手が表示されていればOK

v1nh4-ulwxs.gif

左がスケルトン、右がカスタムマテリアル

com.qvtecCompany.OculusQuestHandTracking-20200115-234849.jpg

衝突イベント

キューブをタッチして色を変えてみます。

Hierarchy
Cube を追加して位置と大きさを適当に調整(size:0.2, position: 0,0.5,1)

Inspector
Box Collider の Is Triggerをチェック

ChangeColor.csスクリプトを作成し、Cube に追加

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

public class CubeTouch : MonoBehaviour
{
    private Renderer renderer;

    void Start()
    {
        renderer = this.gameObject.GetComponent<Renderer>();
    }

    private void OnTriggerEnter(Collider collider)
    {
        if (collider.gameObject.name == "Hand_Index3_CapsuleCollider")
        {
            renderer.material.color = Color.red;
        }
    }

    private void OnTriggerExit(Collider collider)
    {
        if (collider.gameObject.name == "Hand_Index3_CapsuleCollider")
        {
            renderer.material.color = Color.white;
        }
    }
}

ヒットしたら赤、外れたら白にマテリアルを更新しています。
colliderの名前は Hand_XXX_CapsuleCollider だったので、BoneIdから判定したいボーンを指定します。

OVRPlugin.cs
public enum BoneId
{
  Invalid                 = -1,

  Hand_Start              = 0,
  Hand_WristRoot          = Hand_Start + 0, // root frame of the hand, where the wrist is located
  Hand_ForearmStub        = Hand_Start + 1, // frame for user's forearm
  Hand_Thumb0             = Hand_Start + 2, // thumb trapezium bone
  Hand_Thumb1             = Hand_Start + 3, // thumb metacarpal bone
  Hand_Thumb2             = Hand_Start + 4, // thumb proximal phalange bone
  Hand_Thumb3             = Hand_Start + 5, // thumb distal phalange bone
  Hand_Index1             = Hand_Start + 6, // index proximal phalange bone
  Hand_Index2             = Hand_Start + 7, // index intermediate phalange bone
  Hand_Index3             = Hand_Start + 8, // index distal phalange bone
  Hand_Middle1            = Hand_Start + 9, // middle proximal phalange bone
  Hand_Middle2            = Hand_Start + 10, // middle intermediate phalange bone
  Hand_Middle3            = Hand_Start + 11, // middle distal phalange bone
  Hand_Ring1              = Hand_Start + 12, // ring proximal phalange bone
  Hand_Ring2              = Hand_Start + 13, // ring intermediate phalange bone
  Hand_Ring3              = Hand_Start + 14, // ring distal phalange bone
  Hand_Pinky0             = Hand_Start + 15, // pinky metacarpal bone
  Hand_Pinky1             = Hand_Start + 16, // pinky proximal phalange bone
  Hand_Pinky2             = Hand_Start + 17, // pinky intermediate phalange bone
  Hand_Pinky3             = Hand_Start + 18, // pinky distal phalange bone
  Hand_MaxSkinnable       = Hand_Start + 19,
  // Bone tips are position only. They are not used for skinning but are useful for hit-testing.
  // NOTE: Hand_ThumbTip == Hand_MaxSkinnable since the extended tips need to be contiguous
  Hand_ThumbTip           = Hand_Start + Hand_MaxSkinnable + 0, // tip of the thumb
  Hand_IndexTip           = Hand_Start + Hand_MaxSkinnable + 1, // tip of the index finger
  Hand_MiddleTip          = Hand_Start + Hand_MaxSkinnable + 2, // tip of the middle finger
  Hand_RingTip            = Hand_Start + Hand_MaxSkinnable + 3, // tip of the ring finger
  Hand_PinkyTip           = Hand_Start + Hand_MaxSkinnable + 4, // tip of the pinky
  Hand_End                = Hand_Start + Hand_MaxSkinnable + 5,

  // add new bones here

  Max = Hand_End + 0,
}

https://en.wikipedia.org/wiki/Phalanx_bone#/media/File:Scheme_human_hand_bones-en.svg
0から3にかけて指先になる

ピンチ操作

ピンチ操作でキューブの色を変えてみます。

手の表示で追加した OVRHandPrefab に MyHand.cs スクリプトを追加

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

public class MyHand : MonoBehaviour
{    
    private OVRHand hand;
    private Renderer renderer;

    void Start()
    {
        renderer = GameObject.Find("Cube").GetComponent<Renderer>();
        hand = this.gameObject.GetComponent<OVRHand>();
    }

    void Update()
    {
        if (hand.GetFingerIsPinching(OVRHand.HandFinger.Middle))
        {
            renderer.material.color = Color.blue;
        }
        else if (material.color == Color.blue)
        {
            renderer.material.color = Color.white;
        }
    }
}

hand.GetFingerIsPinching でピンチしているかどうかを取得できます。
OVRHand.HandFinger.Middle をIndexやRingにするとピンチする指の指定ができます。
中指と親指をくっつけると青、離すと白にマテリアルを更新しています。

完成

ちゃんと中指以外は色が変わらない!
両手使える!

gumhj-05rps.gif

公式サイト
https://developer.oculus.com/documentation/unity/latest/concepts/unity-handtracking/

さいごに

薬指と小指は重なってしまって誤動作しやすい。
何か操作に使う場合は中指が良さそう。(人差し指は選択)
次は SDK の SampleFramework を使った方法も試してみる。

UnityでOculus IntegrationのSampleFrameworkを使ってOculus Questのハンドトラッキングを試す

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

触れたらプレイヤーの重力を反転する

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    Rigidbody2D rigid2D;
    bool gravity;

    void Start()
    {
        this.rigid2D = GetComponent<Rigidbody2D>();
        gravity = false;
    }

    void Update()
    {
    }

  void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.CompareTag("gravity"))//オブジェクトにgravityタグ
        {
            if (gravity == false)
                gravity = true;
            else
                gravity = false;
            rigid2D.gravityScale *= -1;
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Canvasを使ったUIを複数の解像度に対応させる方法

はじめに

このアカウントは、プログラムについて勉強し始めて1年立たずの専門学校生によるものです。
新たに学んだ技術や知識をなるべくわかりやすくまとめておこうと思います。
アドバイス・ご指摘などありましたら、どうぞご遠慮無く。

解像度について

Unityに限らず、アプリケーションの開発、特にスマートフォン向けのものである場合には必ず直面する問題だと思います。
同じ規格、同じ解像度の端末をすべてのユーザーが使用していることは、基本的には存在しません。
したがって、複数の解像度に対応したアプリケーションの開発が必要になります。
ここで紹介するのはその方法の1つです。
処理速度などは考慮していませんのでご了承ください。

では早速…

Canvasの設定

UnityのUIは”Canvas”によって作ることができます。
まずはCanvasの設定から始めましょう。

今回は3Dのプロジェクトで説明します。
まずは、Canvasを出しましょう。
タスクバーから”GameObject/UI/Canvas”でCancasを出します。
Unity Canvas.png
続いて、Canvasの設定をしていきます。
CanvasのInspectorを見ます。画面はこんな感じ。
Unity Canvas Inspector.png
設定するのは、”Canvas Scaler”の”UI Scale Mode”です。(下記画像の赤枠)
デフォルトでは”Constant Pixel Size”になっているので、”Scale with Screen Size”に変更します。
Unity Canvas Inspector2.png
これで、Canvasの大きさが起動時の画面サイズと同じ大きさになります。

UIオブジェクトの大きさ

Canvasの大きさが画面サイズに対応してもCanvas上のオブジェクトが対応しなければ意味がありません。
今回はオブジェクトにスクリプトをアタッチすることで対応させてみました。

使用したスクリプトはこちら

SizeOptimise.rb
using UnityEngine;

public class SizeOptimise : MonoBehaviour
{
    public GameObject canvas;
    public bool       horizontal = true;
    public bool       vertical   = true;

    void Start()
    {
        float width  = gameObject.GetComponent<RectTransform>().sizeDelta.x;
        float height = gameObject.GetComponent<RectTransform>().sizeDelta.y;
        if (horizontal)
        {
            width = canvas.GetComponent<RectTransform>().sizeDelta.x;
        }
        if (vertical)
        {
            height = canvas.GetComponent<RectTransform>().sizeDelta.y;
        }

        gameObject.GetComponent<RectTransform>().sizeDelta     = new Vector2(width, height);
        gameObject.GetComponent<RectTransform>().localPosition = new Vector3(0, 0, 0);
    }
}

bool型の変数を二つ用意して、縦と横のサイズ変更が別々にできるようにしました。
使い方は以下の通りです。
Unity SizeOptimise.png
※public変数について
Canvas:このスクリプトをアタッチしたCanvasを選択。そのCanvasサイズがオブジェクトのサイズに対応します。
Horizontal:縦(垂直)方向のサイズを合わせるかどうか
Vertical:横(水平)方向のサイズを合わせるかどうか

まとめ

かかった時間や手間はかなり少ないですが、しっかりと複数の解像度に対応させることができました。
困っていた人がいれば参考にしていただければ幸いです。
また、改善点などありましたらコメントにてご教授いただけると、私のスキルアップにつながります。
ぜひよろしくお願いいたします。

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

Adobe Fuseで作ったモデルにモーションをつけてUnityで使えるようになるまで

Fuse CCとは

簡単にモデルが作成でき、アニメーションを選択して付けることができるソフト.

環境

Unity 2018.4
Fuse CC (Beta)

Fuse CC を使ってみる

モデルが完成したら、書き出し準備をします.
File -> Animate with Mixamo を選択

すると、下のようなものが出てくるので、saveをクリックしてアップロードを開始します.完了すると、ブラウザが開きます.

ブラウザが開いたら、FINISHを選択します.次にANIMETEを選択しアップロードしたモデルにアニメーションを付けます.

Runningを検索して、Downloadを選択しましょう.

形式に関しては、こんな感じです.

ダウンロードが終わったら、unityを開いてプロジェクト内にドロップします.

Unityにモデルを入れる

先ほどのモデルをヒエラルキーにドロップしてみましょう.

見事に色が落ちてます...
もう一度、Fuse CCで先ほどのモデルを開き、Textureをエクスポートします.
File -> Export -> Export Texturesを選択

形式はこんな感じです.

次にUnityのプロジェクト内にあるモデルと同じディレクトリーにtextureを入れます.
そしてモデルのインスペクターを表示して、MaterialからExtract Materials...を選択してApply

下のようなものが出てきたら、Fix nowを選択します.

ちゃんと色がつきました.

Unityでモデルのアニメーションを使う

まず、モデルに付いているモーションを選択、ctrl+Dで複製しておき、複製したモーションは別ファイルにまとめます.

今の状態で実行しても、モデルは動かないのでcontrollerを作成します.
右クリック -> Create -> Animation Controller
ここでは名前をControllerにしました.

作成したAnimation Controllerを開き、複製したモーションをAnimetor内にドロップ

次に先ほどのRunモーションをダブルクリックして表示したインスペクターから、Loop Timeにチェックを入れます.
すると、実行した時に繰り返しモーションが動きます.

これをモデルのAnimatorのControllerにつけます.

すると走るのですが、モーションが終わるたびに元の座標に戻っていると思います.
座標が変わるようにしましょう.
まず、ヒエラルキー内にあるモデルのAnimatorのApply Root Motionにチェックを入れます.

次にプロジェクト内の元のモデルを選択、Rig -> Avatar Definitionからモーション実行中に動いているパーツを選択します. 今回はHipsでした.
ここは、実際に実行してヒエラルキーにあるモデルのパーツを観察して選択します.そしてApply

すると、プロジェクト内のモデルに入っているモーションのヒエラルキーが変更されていると思います.
下のモーションが変更前です.

下の画像が変更後です. このようになっていれば成功です. ここからまたモーションを複製し、置き換えます

変更したアニメーションの名前をRe_Runningとします.
作ったAnimator Controllerを開き、Motionを置き換えます.

Re_Runningをダブルクリックして、Loop Timeにチェックを入れます.

実行して、遠くに走って行けば完成です.

おまけ

他のモーションをつけたい場合は、ブラウザからモーションを選び、without Skinを選択し、unityに落とします.
落としたfbxを選択し、上記と同様 Rig -> Avatar Definitionを変更、Applyしてモーションを複製して使います

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

簡単に地形生成してUNITYに持ってくる!

簡単に地形生成してUNITYに持ってくる!

こういうソフトがありまして。
https://3dnchu.com/archives/terresculptor-2-free/
リアルな地形を生成するソフトです。

image.png

TerreSculptor2というフリーのソフトなんですが。
UE4に取り込むフローは書いてあるけど、UNITYに取り込むフローは書いてない!

ということで作りました。

下記のファイルを作って、Editorフォルダ内に入れてください。

TSMapImporter.cs
//#define TSMAP_SUPPORT_COMPRESS

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using UnityEngine;
using UnityEditor;
#if TSMAP_SUPPORT_COMPRESS
using Ionic.Zlib;
#endif

public class TSMapImporter
{
    [MenuItem("Assets/ImportTSMap")]
    public static void ImportTSMap()
    {
        string folderPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
        var openPath = EditorUtility.OpenFilePanel("open TerreSculptor map", folderPath, "TSmap");
        var td = importTSMap(openPath);

        // create object
        if (td != null)
        {
            //            Terrain.CreateTerrainGameObject(td);

            int instanceID = Selection.activeInstanceID;
            string path = AssetDatabase.GetAssetPath(instanceID);
            string fullPath = System.IO.Path.GetFullPath(path);

            var exportFileName = EditorUtility.SaveFilePanelInProject("save Terrain data", "TerrainFromTS", "asset", "Terrain data", fullPath);
            AssetDatabase.CreateAsset(td, exportFileName);
            AssetDatabase.SaveAssets();
        }
    }

    static TerrainData importTSMap(string openPath)
    {
        string tsName = Path.GetFileNameWithoutExtension(openPath);
        using (var fs = new FileStream(openPath, FileMode.Open))
        {
            using (var br = new BinaryReader(fs))
            {
                var headFileType = br.ReadBytes(16);    // TerreSculptor
                var headDataType = br.ReadBytes(16);    // TSmap
                var version = br.ReadInt32();   // 0x0100
                var width = br.ReadInt32();   // width
                var length = br.ReadInt32();   // length
                var isCompress = br.ReadInt32();// isCompress 1:true 0:false
                br.ReadBytes(16);   // ?

                // check header string
                string strFileType = Encoding.UTF8.GetString(headFileType).TrimEnd('\0');
                string strDataType = Encoding.UTF8.GetString(headDataType).TrimEnd('\0');
                if ((strFileType == "TerreSculptor")
                    && (strDataType == "TSmap"))
                {
                    float[,] heightMap = new float[width + 1, length + 1];
                    float maxHeight = 0f;
                    if (isCompress != 0)
                    {
#if TSMAP_SUPPORT_COMPRESS
                        // 圧縮バージョン
                        int numUnknown = br.ReadInt32();
                        int numUnknown2 = br.ReadInt32();
                        int numUnknown3 = br.ReadInt16();

                        // ZIP展開
                        int zipLen = (int)(fs.Length - fs.Position);
                        byte[] buf = new byte[width * length * 4];
                        using (DeflateStream s = new DeflateStream(fs, CompressionMode.Decompress))
                        {
                            s.Read(buf, 0, buf.Length);
                        }

                        // ハイトの設定
                        for (int y = 0; y < length; ++y)
                        {
                            for (int x = 0; x < width; ++x)
                            {
                                int idx = (x + (y * width))*4;
                                heightMap[x, y] = BitConverter.ToSingle(buf,idx);
                                maxHeight = Mathf.Max(maxHeight, heightMap[x, y]);
                            }
                        }
#else
                        Debug.Log("NOT SUPPORT COMPRESS TSMAP");
                        return null;
#endif
                    }
                    else
                    {
                        // ハイトの設定
                        for (int y = 0; y < length; ++y)
                        {
                            for (int x = 0; x < width; ++x)
                            {
                                heightMap[x, y] = br.ReadSingle();
                                maxHeight = Mathf.Max(maxHeight, heightMap[x, y]);
                            }
                        }
                    }
                    // 正規化する
                    for (int y = 0; y < length; ++y)
                    {
                        for (int x = 0; x < width; ++x)
                        {
                            heightMap[x, y] /= maxHeight;
                        }
                    }
                    // add 1 pixel
                    // UNITYは、2のべき乗の数+1でハイトマップが作られるので合わせる
                    for (int x = 0; x < width; ++x)
                    {
                        heightMap[x, length] = heightMap[x, length - 1];
                    }
                    for (int x = 0; x < length + 1; ++x)
                    {
                        heightMap[width, x] = heightMap[width - 1, x];
                    }

                    // modifyResolution
                    int heightRes = Mathf.ClosestPowerOfTwo(Mathf.Max(width, length)) + 1;
                    if ((heightMap.GetLength(0) != heightRes)
                        || (heightMap.GetLength(1) != heightRes))
                    {
                        heightMap = modifyResolution(heightMap, heightRes, heightRes);
                    }

                    // スケール(適当)
                    float scale = 10f / (heightRes-1);

                    // create TerrainData
                    TerrainData td = new TerrainData();
                    td.size = new Vector3(width * scale, maxHeight, length * scale);
                    td.heightmapResolution = heightRes;
                    td.SetDetailResolution(width, 16);
                    td.SetHeights(0, 0, heightMap);

                    return td;
                }
            }
        }
        return null;
    }

    // 解像度を変更
    static float[,] modifyResolution(float[,] src,int targetWidth,int targetLength)
    {
        int srcW = src.GetLength(0);
        int srcL = src.GetLength(1);
        float[,] dst = new float[targetWidth, targetLength];
        for(int y=0;y<targetLength;++y)
        {
            for (int x = 0; x < targetWidth; ++x)
            {
                float fx = (x * srcW) / (float)targetWidth;
                float fy = (y * srcL) / (float)targetLength;
                int x0 = Mathf.Min(Mathf.CeilToInt(fx), srcW - 1);
                int y0 = Mathf.Min(Mathf.CeilToInt(fy), srcL - 1);
                int x1 = Mathf.Min(x0 + 1, srcW-1);
                int y1 = Mathf.Min(y0 + 1, srcL-1);
                float ax = fx - x0;
                float ay = fy - y0;
                // four sample
                var s0 = Mathf.Lerp(src[x0, y0],src[x1, y0], fx);
                var s1 = Mathf.Lerp(src[x0, y1], src[x1, y1], fx);
                dst[x, y] = Mathf.Lerp(s0, s1, fy);
            }
        }
        return dst;
    }
}

そうすると、TerreSculptor2から書き出せるTSmapファイルを読み込んでUNITYにインポートできるようになります。

使い方

TerreSculptorからFile->Save Terrain Asを選び、TSmapファイルを書き出します。
image.png

UNITYを立ち上げ、上記のスクリプトをEditorフォルダに入れると、アセットが置いてある、Projectウィンドウで右クリックすると、ImportTSMapというメニューが追加されています。

image.png

それを実行して、TSmapファイルを選び、続いて保存先を選ぶとTerrainデータが作られます。
Terrainデータをシーン上にドラッグアンドドロップすると、地形をシーン上に配置することができます。

image.png

TSmapは圧縮フォーマットもありますが、それに対応したい場合は、DotNetZipが必要になります。
https://github.com/DinoChiesa/DotNetZip
これを入れていれば、ZIP圧縮、解凍ができるようになります。
入れた後に、一行目のdefine TSMAP_SUPPORT_COMPRESSを定義してもエラーがでなくなります。

見た目をよりよくしよう

上記のキャプチャー画像いまいちクォリティーが低く見えます。
もう少し、調整してみましょう。

image.png
Terrainのインスペクタから、Pixel Errorの値を1にしてみましょう。
そうすると、ポリゴン数が多くなりクォリティーが上がります。

シェーダーも書いてみます。

TerrainGradient.shader
Shader "Custom/TerrainGradient"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _ColorsetTex ("Colorset (RGB)", 2D) = "white" {}
        _Height("Height", Range(0,1000)) = 100.0
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        sampler2D _ColorsetTex;
        half _Height;

        struct Input
        {
            float2 uv_MainTex;
            float3 worldPos;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            c = c * tex2D(_ColorsetTex, float2(0, IN.worldPos.y / _Height));
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

_ColorsetTexに縦方向へのグラデーションテクスチャを設定すると高さに応じて色が変わるようにしました。
_Heightパラメータの操作で、高さに応じたUVの調整ができます。

ColorsetTexの画像は、下記の画像を参考に作ります。
TerraSculpterのMaterialボタンを押します。
その右のMaterial typeをColorsetにして、その下のViewボタンを押します。
ダイアログが現れるので、Colorsetを選んでPrintScreenを押すなどしてキャプチャーします。
ペイントソフトなどで、切り抜いて、サイズは1x256などにして保存します。
これをUNITYで
ColorsetTexに設定してください。

image.png

テクスチャのWrapModeはClampにしておきましょう。
UNITYでマテリアルを作って、上記シェーダーを設定し、このテクスチャを_ColorsetTexの項目に設定します。
Terrainにマテリアルを設定すると、下記のように表示されるようになりました。

image.png

少しライトや、Smoothnessなどを調整しています。

image.png

見た目も、ほぼ同じにできたのではないでしょうか。

おわりに

ファイルフォーマットが結構簡単に解析できたので、対応しました。

ということで、UE4だけでなく、UNITYでも容易に扱えます。

3dnchu様の記事でも、この記事を紹介していただけました!
https://3dnchu.com/archives/terresculptor-2-free/
ありがとうございます。

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