- 投稿日:2020-01-15T23:40:22+09:00
VRC上の表情アニメーションとまばたきの干渉を防ぐ
免責事項
本内容はVRoidモデルでしか試していません。
VRoid以外でできた/できなかったの報告ウェルカムです!くらげさんのBehaviour-Enabledを使う方法
手順
- zipを解凍して中の
Behaviour-Enabled.anim
を適当にUnityにインポート- 生贄プレハブを作り、アニメーションコントローラーの中に
Behaviour-Enabled.anim
を追加する- アニメーションウィンドウを開くと
Behaviour-Enabled.anim
が黄色(Missing)になっているので、選択+F2キー等からBody/Body-expression
をBody
にリネームしてあげる
- ※VRoidの仕様が変更になったら
↓
- アニメーションウィンドウ上でBody:Behaviour.Enabled
をコピーし、まばたきを止めたいアニメーションに貼り付ける
-Body:Behaviour.Enabled
の右側のチェックボックスを外した状態で全てのフレームにコピーする
~おわり~
- 投稿日:2020-01-15T23:23:51+09:00
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」を選択します
actions.jsonが無いみたいなことを言われるので、「Yes」を選択します
SteamVR Inputが開けたら、「Save and generate」を選択します
スクリプトの作成
InputTest.csusing 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.8indexCurl 人差し指の握りを表します middleCurl 中指の握りを表します ringCurl 薬指の握りを表します pinkyCurl 小指の握りを表します 実行の準備
Main Cameraを削除して[CameraRig]をHierarchyに追加します
適当にGameObjectを作成して先ほど作成したスクリプト「InputTest.cs」をアタッチします
inspectorから指の入力を取得する手を選択します
今回は左手の指入力を取得するので「\action\default\in\SkeletonLeftHand」を選択します
実行
ゲームを再生して左手のValveIndexコントローラーを握ると、デバッグログの値が変化します
参考サイト
- 投稿日:2020-01-15T19:07:03+09:00
【Unity】【Shader】周りのオブジェクトに影を描画する
はじめに
例えばDiffuseのShaderを自作すると、それだけでは周りのオブジェクトに影が描画されない。影の実装も自分で行う必要があるので、それをやってみる。
*ちなみに今回の実装は、自身が周りのオブジェクトに影を描画するだけで、自身が影を受け取る訳ではない。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.
multi_compile_shadowcasterを追加
#pragma multi_compile_shadowcaster
を追加する。これでコンパイラに影を描画したいって伝えるらしい?*要確認マクロ
実際の描画処理はマクロを呼び出すだけで完了する。使うのは以下の3つ。
V2F_SHADOW_CASTER TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) SHADOW_CASTER_FRAGMENT(i)結果
無事に影が描画されるようになりました。
今回のコード
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 }
- 投稿日:2020-01-15T19:07:03+09:00
【Unity】【Shader】影を描画する、影を受け取る
はじめに
例えばDiffuseのShaderを自作すると、それだけでは影が描画されない。影の実装も自分で行う必要があるので、それをやってみる。
周りのオブジェクトに影を描画する
まずは、自身が周りのオブジェクトから影を受けるようにする。
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.
multi_compile_shadowcasterを追加
#pragma multi_compile_shadowcaster
を追加する。これでコンパイラに影を描画したいって伝えるらしい?*要確認マクロを使用する
実際の描画処理はマクロを呼び出すだけで完了する。使うのは以下の3つ。
V2F_SHADOW_CASTER TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) SHADOW_CASTER_FRAGMENT(i)結果
無事に影が描画されるようになりました。
ここまでのコード
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に変更する。
以下のコードにコメントしてある箇所。結果
無事に影を受け取ることができた。
今回のコード
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 } } }
- 投稿日:2020-01-15T17:54:34+09:00
【Unity】Matrixを使って回転させる。
回転を扱いたい場合はQuaternion.Euler()を使うことが多いが、Matrixを使用すると便利なことがあったのでメモ。 図の場合でQuaternion.Euler()を使用した場合とMatrixを使用した場合を考えてみる。
赤いSphereが原点で、(1, 0, 0)にQuadを配置。
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; }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; } }
- 投稿日:2020-01-15T17:09:10+09:00
Unityゲーム制作 ライトのON/OFF 切り替え方
---ゲーム内で使用するライトのON/OFFのコード---
Zキーを押している間だけライトを消すシステムを今回はobjectを消すという手法で行いました。
まずUnityのHierarchyに、ライト本体を入れなくてはいけません
Asset storeからライト本体を探してきて(Flashlight)ダウンロードします。
https://assetstore.unity.com/packages/3d/props/electronics/flashlight-18972
そのあと、本体の中に光の部分(自分の場合 UnityEffectのSpot Light)を使って作っておきます。
次にこのコードを打ち込んで保存します。
GameManagerusing 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をアタッチしてください。
そして、ライトの光の部分(自分の場合Spot Light)のobjectをSphereにドラック&ドロップするだけで完成します。カメラの位置を調整・ゲームをスタートし、Zキーを押すとライトの光がつくと思います、離すと消えます。
以上ライトのON/OFFのスクリプト導入でした。お疲れさまです!!('ω')ノ
- 投稿日:2020-01-15T16:58:30+09:00
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化するとステージセレクトボタンを作る際に楽になると思います。
自分自身まだプログラムの経験が浅いためこのプログラムで大丈夫かが不安ですが役立つことができたら幸いです。
- 投稿日:2020-01-15T16:58:30+09:00
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化するとステージセレクトボタンを作る際に楽になると思います。
自分自身まだプログラムの経験が浅いためこのプログラムで大丈夫かが不安ですが役立つことができたら幸いです。
- 投稿日:2020-01-15T16:38:11+09:00
【Unity】Jsonファイルとしてデータを保存する
はじめに
このアカウントは、プログラムについて勉強し始めて1年立たずの専門学校生によるものです。
新たに学んだ技術や知識をなるべくわかりやすくまとめておこうと思います。
アドバイス・ご指摘などありましたら、どうぞご遠慮無く。データの保存
ゲーム制作をしていると必ずぶつかる壁にデータの保存があると思います。
今回は、私がゲーム制作で使用した保存方法とスクリプトをご紹介します。Jsonファイルとしてデータを保存・読み書きする
データの保存方法にも何種類かありますが、今回はJsonファイルで保存する方法を使ってみました。
今回のスクリプトはこちらtitleの記事を参考にさせていただきました。
(kanのメモ帳-個人ゲーム開発者kan.kikuchiのメモ的技術ブログ-/クラスを丸ごと保存するデータ管理方法【Unity】)スクリプト
魚を取るゲームを制作していたので、所々魚関係の変数や名前が出てきます。
PlayerDataInstance.csusing 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.csusing 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を利用する方法なんかが有名です。
また、改善点などありましたらコメントにてご教授いただけると、私のスキルアップにつながります。
ぜひよろしくお願いいたします。
- 投稿日:2020-01-15T15:51:20+09:00
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]手を追加
Hierarchy
Oculus > VR > Prefabs > OVRHandPrefab を LeftHandAnchor と RightHandAnchor の下に追加Inspector
OVRHand、OVRSkeleton、OVRMesh の各 HandType に Hand Left または Hand Right を選択
OVRSkeleton の Enable Physics Capsules にチェックスケルトン表示にする場合
OVRHandPrefab の OVR Skeleton Renderer にチェック
カスタムマテリアルにする場合
OVRHandPrefab の Skinned Mesh Renderer で Materials > Element0 を好きなマテリアルに変える
ビルド確認
ハンドトラッキングされた手が表示されていればOK
左がスケルトン、右がカスタムマテリアル
衝突イベント
キューブをタッチして色を変えてみます。
Hierarchy
Cube を追加して位置と大きさを適当に調整(size:0.2, position: 0,0.5,1)Inspector
Box Collider の Is TriggerをチェックChangeColor.csスクリプトを作成し、Cube に追加
ChangeColor.csusing 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.cspublic 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.csusing 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; } } }中指と親指をくっつけると青、離すと白にマテリアルを更新しています。
完成
ちゃんと中指以外は色が変わらない!
両手使える!公式サイト
https://developer.oculus.com/documentation/unity/latest/concepts/unity-handtracking/さいごに
薬指と小指は重なってしまって誤動作しやすい。
何か操作に使う場合は中指が良さそう。
次は SDK の SampleFramework を使った方法も試してみる。
- 投稿日:2020-01-15T15:51:20+09:00
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]手を追加
Hierarchy
Oculus > VR > Prefabs > OVRHandPrefab を LeftHandAnchor と RightHandAnchor の下に追加Inspector
OVRHand、OVRSkeleton、OVRMesh の各 HandType に Hand Left または Hand Right を選択
OVRSkeleton の Enable Physics Capsules にチェックスケルトン表示にする場合
OVRHandPrefab の OVR Skeleton Renderer にチェック
カスタムマテリアルにする場合
OVRHandPrefab の Skinned Mesh Renderer で Materials > Element0 を好きなマテリアルに変える
ビルド確認
ハンドトラッキングされた手が表示されていればOK
左がスケルトン、右がカスタムマテリアル
衝突イベント
キューブをタッチして色を変えてみます。
Hierarchy
Cube を追加して位置と大きさを適当に調整(size:0.2, position: 0,0.5,1)Inspector
Box Collider の Is TriggerをチェックChangeColor.csスクリプトを作成し、Cube に追加
ChangeColor.csusing 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.cspublic 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.csusing 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にするとピンチする指の指定ができます。
中指と親指をくっつけると青、離すと白にマテリアルを更新しています。完成
ちゃんと中指以外は色が変わらない!
両手使える!公式サイト
https://developer.oculus.com/documentation/unity/latest/concepts/unity-handtracking/さいごに
薬指と小指は重なってしまって誤動作しやすい。
何か操作に使う場合は中指が良さそう。(人差し指は選択)
次は SDK の SampleFramework を使った方法も試してみる。→ UnityでOculus IntegrationのSampleFrameworkを使ってOculus Questのハンドトラッキングを試す
- 投稿日:2020-01-15T13:10:34+09:00
触れたらプレイヤーの重力を反転する
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; } } }
- 投稿日:2020-01-15T13:10:02+09:00
【Unity】Canvasを使ったUIを複数の解像度に対応させる方法
はじめに
このアカウントは、プログラムについて勉強し始めて1年立たずの専門学校生によるものです。
新たに学んだ技術や知識をなるべくわかりやすくまとめておこうと思います。
アドバイス・ご指摘などありましたら、どうぞご遠慮無く。解像度について
Unityに限らず、アプリケーションの開発、特にスマートフォン向けのものである場合には必ず直面する問題だと思います。
同じ規格、同じ解像度の端末をすべてのユーザーが使用していることは、基本的には存在しません。
したがって、複数の解像度に対応したアプリケーションの開発が必要になります。
ここで紹介するのはその方法の1つです。
処理速度などは考慮していませんのでご了承ください。では早速…
Canvasの設定
UnityのUIは”Canvas”によって作ることができます。
まずはCanvasの設定から始めましょう。今回は3Dのプロジェクトで説明します。
まずは、Canvasを出しましょう。
タスクバーから”GameObject/UI/Canvas”でCancasを出します。
続いて、Canvasの設定をしていきます。
CanvasのInspectorを見ます。画面はこんな感じ。
設定するのは、”Canvas Scaler”の”UI Scale Mode”です。(下記画像の赤枠)
デフォルトでは”Constant Pixel Size”になっているので、”Scale with Screen Size”に変更します。
これで、Canvasの大きさが起動時の画面サイズと同じ大きさになります。UIオブジェクトの大きさ
Canvasの大きさが画面サイズに対応してもCanvas上のオブジェクトが対応しなければ意味がありません。
今回はオブジェクトにスクリプトをアタッチすることで対応させてみました。使用したスクリプトはこちら
SizeOptimise.rbusing 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型の変数を二つ用意して、縦と横のサイズ変更が別々にできるようにしました。
使い方は以下の通りです。
※public変数について
Canvas:このスクリプトをアタッチしたCanvasを選択。そのCanvasサイズがオブジェクトのサイズに対応します。
Horizontal:縦(垂直)方向のサイズを合わせるかどうか
Vertical:横(水平)方向のサイズを合わせるかどうかまとめ
かかった時間や手間はかなり少ないですが、しっかりと複数の解像度に対応させることができました。
困っていた人がいれば参考にしていただければ幸いです。
また、改善点などありましたらコメントにてご教授いただけると、私のスキルアップにつながります。
ぜひよろしくお願いいたします。
- 投稿日:2020-01-15T07:48:12+09:00
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
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してモーションを複製して使います
- 投稿日:2020-01-15T01:38:44+09:00
簡単に地形生成してUNITYに持ってくる!
簡単に地形生成してUNITYに持ってくる!
こういうソフトがありまして。
https://3dnchu.com/archives/terresculptor-2-free/
リアルな地形を生成するソフトです。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ファイルを書き出します。
UNITYを立ち上げ、上記のスクリプトをEditorフォルダに入れると、アセットが置いてある、Projectウィンドウで右クリックすると、ImportTSMapというメニューが追加されています。
それを実行して、TSmapファイルを選び、続いて保存先を選ぶとTerrainデータが作られます。
Terrainデータをシーン上にドラッグアンドドロップすると、地形をシーン上に配置することができます。TSmapは圧縮フォーマットもありますが、それに対応したい場合は、DotNetZipが必要になります。
https://github.com/DinoChiesa/DotNetZip
これを入れていれば、ZIP圧縮、解凍ができるようになります。
入れた後に、一行目のdefine TSMAP_SUPPORT_COMPRESSを定義してもエラーがでなくなります。見た目をよりよくしよう
上記のキャプチャー画像いまいちクォリティーが低く見えます。
もう少し、調整してみましょう。
Terrainのインスペクタから、Pixel Errorの値を1にしてみましょう。
そうすると、ポリゴン数が多くなりクォリティーが上がります。シェーダーも書いてみます。
TerrainGradient.shaderShader "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に設定してください。テクスチャのWrapModeはClampにしておきましょう。
UNITYでマテリアルを作って、上記シェーダーを設定し、このテクスチャを_ColorsetTexの項目に設定します。
Terrainにマテリアルを設定すると、下記のように表示されるようになりました。少しライトや、Smoothnessなどを調整しています。
見た目も、ほぼ同じにできたのではないでしょうか。
おわりに
ファイルフォーマットが結構簡単に解析できたので、対応しました。
ということで、UE4だけでなく、UNITYでも容易に扱えます。
3dnchu様の記事でも、この記事を紹介していただけました!
https://3dnchu.com/archives/terresculptor-2-free/
ありがとうございます。