- 投稿日:2019-08-29T21:46:00+09:00
【Unity】自分の思い描いたキャラクターを作成し、最速で動かす
はじめに
Unityでゲームを作る際、
自作のキャラクターをUnityの空間上で走り回らせたいとお考えの方へ私もネットで調べて、候補となるツールのバリエーションに圧倒され、
「結局どれを使えばいいんだ!」と悩みましたそのため、各作業ごとのオススメのツール(無料)を紹介しながら、
実際に動き回るところまで説明していきたいと思います。全体像
最短で自作キャラをUnity上で動かすには、
Unityの「Humanoid」型のモデルを作成するのが手っ取り早いです。
「Humanoid」型のモデルとは特定の骨を持つモデルのことで、
この骨格を持つモデルを作成することで、AssetStoreから取得したモデル(ユニティちゃんなど)のアニメーションを自作キャラクターに反映させることができます。要は上記画像の「2.骨入れ」を決まった形で作成することで、
「3.アニメーションの作成」をすっ飛ばすことができます。
今回はこの方法で最速で作成をしていきます。モデル作成
選択肢が多いモデル作成について、作りたいもの別に3種類紹介します。
今回はSCULPTRISを使用してモンスターをつくていきたいと思います。
今回使用しないツールについては、参考リンクを貼っておきます。・VRoid
「VRoid Studio」で作った3Dモデルを「Unity」で踊らせてみよう・123D CATCH
友人をスマホで撮影して3Dモデル化してUnityで動かしてみたモデル作成(SCULPTRIS)
直感的に作業を行えるため、深くは説明しません。
紹介・インストール方法が記載されているここを参考に、
思い思いのキャラクターを作ってみましょう。最後にデータをエクスポートして完了です。
ここを参考に、.obj(モデルデータ)・.png(テスクチャーデータ)を保存しましょう。公式ガイドは以下です
Sculptris Alpha6 日本語ガイド骨入れ
上記モデル作成でどのツールをしようしたとしても、骨入れ作業はBlender一択になります。
Blenderでは骨入れ以外にもモデル作成など、様々なことができるそうなので詳しくはこちらをみてください。インポート・位置調整
基本的にはこちら参考に進めてください。
1.ファイルから、インポート > Wavefront(.obj)を選択
2.Cubeを削除し、Amatureを選択・下の箱のアイコンをクリックし、位置情報の表示
3.青(上下)・赤(左右)・緑(前後)の矢印をスライドさせ、足の裏が赤緑の面にちょうどくっ付くように調整
テスクチャの適用は行う必要ありません、これで準備は完了です。
骨入れ
基本的にはこちら参考に進めてください。
1.Shift + a でメニューを表示、アーマチェア > 単一ボーンを選択
2.Amatureを選択した状態で、レントゲンオプションをオンにする
3.参考リンクを参照に骨入れをする
(<重要>骨の数と名前を一致させてください)
5.ファイルから、エクスポート > FBX(.fbx)を選択肢し、指定フォルダに保存
Unityでの作業
AssetStoreからモーションデータを取得し、モデルに設定しましょう。
1.AssetStoreから「Unity-Chan Model」をインポート
2.右グリックでCreate > Animator Controllerを選択し、
作成したコントローラーをダブルクリック
3.開いたウィンドウ上で右クリック、Create State > Emptyを選択
4.Inspecterから名前を「Run」などに変更し、Motionを選択
※今回はUnity-Chanの「RUN00_F」を使用します
5.もう一度Stateを作成し、Inspecter上で編集
※Stopと名付けて、「POSE01」を設定します
6.AnimatorウィンドウでParametor上でBoolを選択し、run変数を作成
※アニメーションの状態遷移を切り替える変数を作成します。
7.先ほど作ったRunとStopをそれぞれ右クリック、Make Transitionを選択し、
InspecterのConditionsを画像の通り設定
※run変数が指定した状態になるとアニメーションが切り替わる仕組みです。9.以下のスクリプトを作成し、モデルに設定
Model.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class Model : MonoBehaviour { private Animator animator; // Use this for initialization void Start () { animator = GetComponent<Animator>(); } // Update is called once per frame void Update () { if (Input.GetKey("up")) { transform.position += transform.forward * 0.05f; animator.SetBool("run", true); } else { animator.SetBool("run", false); } if (Input.GetKey("right")) { transform.Rotate(0, 10, 0); } if (Input.GetKey ("left")) { transform.Rotate(0, -10, 0); } } }まとめ
sculptrisでキャラクターを作成するのに最初は時間がかかると思いますが、
慣れれば、この一連の作業で1時間かからずに終わると思います。今回はAnimationをAssetStoreのものを使用しましたが、
Blenderを使えば自作アニメーションも作れます!VtuberやVRChatなど遊びの幅も広がると思うので、ぜひ試してみてください!
- 投稿日:2019-08-29T18:33:59+09:00
Unityでよく使う色を指定して保存しておく方法
UI組んでて、指定の色を保存しておく方法はないかな~って見てたらあったのでそれの紹介
Swatches
Photoshopとかにも色の保存機能がありSwatchと言うらしいです
Unityにもあるのでそれの追加方法の紹介新規Swatchesの作成手順
Inspectorの適当なColorをクリック→Swatches→CreateNewLibrary...をクリック
NameはSwatchesの表示名(例:"プロジェクト名"TextColors)
LocationはPreferences FolderとProject Folderの2つあるのでお好きな方へPreferences Folder - どのプロジェクトでも追加したSwatchesが表示される
Project Folder - 追加したプロジェクトでしか表示されない
あとはカラー指定→Swatchesのボタンクリックで追加されます
あと、間違って色を追加した場合は色を右クリック→ReplaceもしくはDeleteで変更/削除ができます
ちなみに表示はGridとListで切り替えることが出来て、Listだと色に名前をつけることが出来ます
終わり
- 投稿日:2019-08-29T13:31:04+09:00
[Unity] Shader Graph でノイズ関数を改造してタイリングに対応するカスタムノードを作る
追記:本記事の改良版
[Unity] Shader Graph でノイズ関数を改造してタイリングに対応するカスタムノードを作る(改)
を投稿しました。コードの修正があるので、冒頭のカスタムノードの作成方法について以外はそちらを参照してください。
この記事で使用したUnityバージョンは2019.2.1f1です。
はじめに
Shader Graph はUnityでコードを書かずにシェーダを記述できる素敵な機能です。これがたいへんよくできていて、テキストエディタが大好きな私でも「もうシェーダをテキストで書いてる場合じゃないな」と思わされます。
とはいえ既存のノードでやりたいことが何もかもカンタンにできるわけでもないようで、例えば標準のノイズ生成は繰り返したときの境界がリピートされていなかったりします。今回は、これを対応するためのカスタムノードを作成してみましょう。
現状確認
普通に Simple Noise を使用したノードの接続がこうなります。
これをQuad4つ並べて貼り付けるとこう。
UVの 0.0 と 1.0 が連続でないため、境界が見えてしまっています。円筒に貼り付ける場合などはこれがたいへん不便なので、カスタムノードで対応してみましょう。
カスタムノード作成
Shader Graph で右クリックして Create Node から
Inputsに Vector2 で UV, Vector1 で Scale を追加、Outputs に Vector1 で Out を追加します。
この時点ではシェーダを記述していないので、エラーが出ています。
SimpleNoise を改造するので、名前は TiledSimpleNoise としましょう。Name に TiledSimpleNoiseを入れ、
TiledSimpleNoise.hlsl
というテキストファイルをプロジェクトのどこかに作成します。てっとり早く結果を得たい方は、下にある改良後の TiledSimleNoise.hlsl の内容をテキストファイルとして TiledSimpleNoise.hlsl に保存し、ノードでこれを指定して完了です。
オリジナルの確認
SimpleNoise を参考にするため、既存ノードの SimpleNoise から Show Generated Code を選択すると、テキストエディタにソースコードが表示されます。
その記述の中で、自作の hlsl ファイルにおくべき部分は以下です。
TiledSimpleNoise.hlslinline float Unity_SimpleNoise_RandomValue_float (float2 uv) { return frac(sin(dot(uv, float2(12.9898, 78.233)))*43758.5453); } inline float Unity_SimpleNnoise_Interpolate_float (float a, float b, float t) { return (1.0-t)*a + (t*b); } inline float Unity_SimpleNoise_ValueNoise_float (float2 uv) { float2 i = floor(uv); float2 f = frac(uv); f = f * f * (3.0 - 2.0 * f); uv = abs(frac(uv) - 0.5); float2 c0 = i + float2(0.0, 0.0); float2 c1 = i + float2(1.0, 0.0); float2 c2 = i + float2(0.0, 1.0); float2 c3 = i + float2(1.0, 1.0); float r0 = Unity_SimpleNoise_RandomValue_float(c0); float r1 = Unity_SimpleNoise_RandomValue_float(c1); float r2 = Unity_SimpleNoise_RandomValue_float(c2); float r3 = Unity_SimpleNoise_RandomValue_float(c3); float bottomOfGrid = Unity_SimpleNnoise_Interpolate_float(r0, r1, f.x); float topOfGrid = Unity_SimpleNnoise_Interpolate_float(r2, r3, f.x); float t = Unity_SimpleNnoise_Interpolate_float(bottomOfGrid, topOfGrid, f.y); return t; } void TiledSimpleNoise_float(float2 UV, float Scale, out float Out) { float t = 0.0; float freq = pow(2.0, float(0)); float amp = pow(0.5, float(3-0)); t += Unity_SimpleNoise_ValueNoise_float(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp; freq = pow(2.0, float(1)); amp = pow(0.5, float(3-1)); t += Unity_SimpleNoise_ValueNoise_float(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp; freq = pow(2.0, float(2)); amp = pow(0.5, float(3-2)); t += Unity_SimpleNoise_ValueNoise_float(float2(UV.x*Scale/freq, UV.y*Scale/freq))*amp; Out = t; }これを先ほどの TiledSimpleNoise.hlsl にコピペして、エラーがなくなってノイズが使えるようになったら改造準備完了です。
ファイルの最後に書いてある関数名は
TiledimpleNoise_float
と変更してあります。改造
コードを見つめてみます。
Unity_SimpleNoise_ValueNoise_float
この関数がキモでfloat2 c0 = i + float2(0.0, 0.0); float2 c1 = i + float2(1.0, 0.0); float2 c2 = i + float2(0.0, 1.0); float2 c3 = i + float2(1.0, 1.0);この部分で隣接の値を生成しています。剰余を返す modulo という関数を
inline float2 modulo(float2 value, float2 scale) { return frac(value/scale)*scale; }と作っておいて、
lsl float2 c0 = i + float2(0.0, 0.0); float2 c1 = i + float2(1.0, 0.0); float2 c2 = i + float2(0.0, 1.0); float2 c3 = i + float2(1.0, 1.0); c0 = modulo(c0, Period); c1 = modulo(c1, Period); c2 = modulo(c2, Period); c3 = modulo(c3, Period);のように剰余にすることでタイルを作ります。ここのPeriodは、TiledimpleNoise_float という関数で freq がScaleの最大4倍になるので、Scale/4 を与えることにします。Period をノードの引数で与えて調整する方法もあると思います。
結果、以下のようなコードになります。TiledSimpleNoise.hlslinline float2 modulo(float2 value, float2 scale) { return frac(value/scale)*scale; } inline float Unity_SimpleNoise_RandomValue_float (float2 uv) { return frac(sin(dot(uv, float2(12.9898, 78.233)))*43758.5453); } inline float Unity_SimpleNnoise_Interpolate_float (float a, float b, float t) { return (1.0-t)*a + (t*b); } inline float Unity_SimpleNoise_ValueNoise_float (float2 uv, float Period) { float2 i = floor(uv); float2 f = frac(uv); f = f * f * (3.0 - 2.0 * f); // uv = abs(frac(uv) - 0.5); この行はオリジナルに存在するが無意味なので削除 float2 c0 = i + float2(0.0, 0.0); float2 c1 = i + float2(1.0, 0.0); float2 c2 = i + float2(0.0, 1.0); float2 c3 = i + float2(1.0, 1.0); c0 = modulo(c0, Period); c1 = modulo(c1, Period); c2 = modulo(c2, Period); c3 = modulo(c3, Period); float r0 = Unity_SimpleNoise_RandomValue_float(c0); float r1 = Unity_SimpleNoise_RandomValue_float(c1); float r2 = Unity_SimpleNoise_RandomValue_float(c2); float r3 = Unity_SimpleNoise_RandomValue_float(c3); float bottomOfGrid = Unity_SimpleNnoise_Interpolate_float(r0, r1, f.x); float topOfGrid = Unity_SimpleNnoise_Interpolate_float(r2, r3, f.x); float t = Unity_SimpleNnoise_Interpolate_float(bottomOfGrid, topOfGrid, f.y); return t; } void TiledSimpleNoise_float(float2 UV, float Scale, out float Out) { float t = 0.0; float2 uv = UV*Scale; float Period = Scale/4; // since the freq below can be 4 maximum. float freq = pow(2.0, float(0)); float amp = pow(0.5, float(3-0)); t += Unity_SimpleNoise_ValueNoise_float(float2(uv.x/freq, uv.y/freq), Period)*amp; freq = pow(2.0, float(1)); amp = pow(0.5, float(3-1)); t += Unity_SimpleNoise_ValueNoise_float(float2(uv.x/freq, uv.y/freq), Period)*amp; freq = pow(2.0, float(2)); amp = pow(0.5, float(3-2)); t += Unity_SimpleNoise_ValueNoise_float(float2(uv.x/freq, uv.y/freq), Period)*amp; Out = t; }GradientNoise
同様にして GradientNoise も対応します。オリジナルではこうなってしまうので
改造します。同じ手順でカスタムノードを作り、オリジナルを改造した結果のソースコードはこうなります:
TiledGradientNoise.hlslinline float2 modulo(float2 value, float2 scale) { return frac(value/scale)*scale; } float2 Unity_GradientNoise_Dir_float(float2 p, float Period) { p = modulo(p, Period); // Permutation and hashing used in webgl-nosie goo.gl/pX7HtC p = p % 289; float x = (34 * p.x + 1) * p.x % 289 + p.y; x = (34 * x + 1) * x % 289; x = frac(x / 41) * 2 - 1; return normalize(float2(x - floor(x + 0.5), abs(x) - 0.5)); } void TiledGradientNoise_float(float2 UV, float Scale, out float Out) { float2 p = UV * Scale; float Period = Scale; float2 ip = floor(p); float2 fp = frac(p); float d00 = dot(Unity_GradientNoise_Dir_float(ip, Period), fp); float d01 = dot(Unity_GradientNoise_Dir_float(ip + float2(0, 1), Period), fp - float2(0, 1)); float d10 = dot(Unity_GradientNoise_Dir_float(ip + float2(1, 0), Period), fp - float2(1, 0)); float d11 = dot(Unity_GradientNoise_Dir_float(ip + float2(1, 1), Period), fp - float2(1, 1)); fp = fp * fp * fp * (fp * (fp * 6 - 15) + 10); Out = lerp(lerp(d00, d01, fp.y), lerp(d10, d11, fp.y), fp.x) + 0.5; }まとめ
ShaderGraph はとても便利で、カスタムノードも作れるともっと便利ですね。また、繰り返しに対応したノイズは使い勝手が良いと思います。自由に使っていただければ。
- 投稿日:2019-08-29T03:26:37+09:00
IEnumerableをforeachする際の速度検証
なぜこんなことをするのか
IEnumerableをforeachしたらオブジェクト化が起きるのでは...?と思ったから。
今回はIEnumerable,配列,リストで検証を行う。計測環境
Core i7-7700HQ 16GB DDR4 2400MHz NVIDIA GeForce GTX 1060 6GB 256GB NVMe SSD + 1TB HDD検証コード
var enumerable = Enumerable.Range(0, 10); var array = enumerable.ToArray(); var list = enumerable.ToList(); var sw = new Stopwatch(); sw.Start(); foreach (var i in enumerable){} sw.Stop(); Console.WriteLine(sw.Elapsed.ToString()); sw.Restart(); foreach (var i in list){} sw.Stop(); Console.WriteLine(sw.Elapsed.ToString()); sw.Restart(); foreach (var i in array){} sw.Stop(); Console.WriteLine(sw.Elapsed.ToString());結果
試行回数 IEnumerable List Array n = 10 0.0631ms 0.085ms 0.001ms n = 100 0.0689ms 0.0865ms 0.0015ms n = 1000 0.073ms 0.0922ms 0.0022ms n = 10000 0.1473ms 0.1360ms 0.0088ms 予想どうり!配列が一番速かった!
内部動作
展開されているコード
ヒープの状態
メモリの状態
上記の3つを見ていただければわかるのですが、配列が他2つに比べてかなりわかりやすいのではないかと。
IEnumerableはRangeIteratorに-1から9までの値を保存しているようで、配列やリストとはかなり違うアプローチをとっています。
ListはItemsにT[]を持っており、実質的に配列のようなものです。IEnumerableと異なる点は_size(リストのサイズ)があることくらいです。
配列はかなりシンプルで、0から10までの値をヒープに保存しています。感想
どうしても速度を求めたいのならば配列を使うといいと思います。そこまで遅い訳ではないので、バンバンListやEnumerableを使っていけば良いんじゃないかなと思いました!
- 投稿日:2019-08-29T00:20:57+09:00
【Unity(C#)】VRカメラを任意のポジションに移動する方法
サマーアドベントカレンダー
Unityゆるふわサマーアドベントカレンダー 2019 #ゆるふわアドカレの枠が空いたそうなので
急遽代打で参加させていただきました!!Unity関連の記事ということで本記事はスレスレですが、ゆるふわなのでセーフセーフ!
VRのカメラ
SteamVRやOculusIntegrationでのVR開発で独特なカメラの階層構造に行き詰まりました。
ご覧の通り、CameraRigというゲームオブジェクトの子階層にカメラが存在しています。
このような階層構造になっている理由は、
VRのカメラがHMDを追従して動くからです。(他にも理由があるかもしれませんが)つまり、直接カメラを動かすことはできず、親階層のCameraRigを動かすことで移動を再現します。
子階層のカメラは自由に動く
しかし、親階層のCameraRigを動かすことで移動を再現した場合、問題が発生します。
それは、子階層のカメラの位置を考慮しなければ任意の位置には移動できないことです。カメラはユーザーの移動に伴って位置が変わるので、特定の位置に誘導が難しい場合は
強制ワープさせた先で壁にめり込んでしまう
といった現象が引き起こされる恐れがあります。図解
説明をわかりやすくするために図を用いて説明します。
赤い点Aの場所にプレイヤーをワープさせたいとします。
薄緑の枠は部屋、青い四角はCameraRig、黄色い点はCameraRigの中心、黒く塗りつぶされた四角はカメラ(プレーヤー)です。カメラ(プレイヤー)のポジションを直接変更することはできないので、
CameraRigのポジションを赤い点Aに移動させることで
カメラ(プレイヤー)移動を再現するのですが、そのまま移動させるとこうなります↓赤い四角が移動後のCameraRigです。
ご覧の通り、カメラが部屋から はみ出してしまいました。
カメラの位置を考慮して移動させる必要がある理由は以上です。デモ
投げたキューブの位置にプレイヤーが移動するデモです。
少しわかりにくいですが、
キューブを持った状態で歩いてカメラの位置を初期位置からずらした後に投げてます。
キューブと同じ座標にプレイヤーが移動していることがわかるかと思います。
コード
今回のデモに利用したコードです。
適当なオブジェクトにアタッチusing UnityEngine; public class WarpCenterCamera : MonoBehaviour { [SerializeField] GameObject ovr_Rig; [SerializeField] GameObject centerCamera; [SerializeField] GameObject warpPointCube; void Update() { Vector3 ovr_Rig_Pos = ovr_Rig.transform.position; Vector3 centerCamera_Pos = centerCamera.transform.position; if (OVRInput.GetDown(OVRInput.RawButton.RIndexTrigger)) { ovr_Rig.transform.position = warpPointCube.transform.position; ovr_Rig.transform.position += new Vector3(ovr_Rig_Pos.x - centerCamera_Pos.x, 0, ovr_Rig_Pos.z - centerCamera_Pos.z); } } }下記の箇所でカメラの座標を考慮したCameraRigの座標の計算を行っています。
ovr_Rig.transform.position += new Vector3(ovr_Rig_Pos.x - centerCamera_Pos.x, 0, ovr_Rig_Pos.z - centerCamera_Pos.z);特定の向きを指定してなおかつ移動も行いたい場合は
ovr_Rig.transform.Rotate(new Vector3(0, 90, 0)); ovr_Rig.transform.position = warpPointCube.transform.position; ovr_Rig.transform.position += new Vector3(ovr_Rig_Pos.x - centerCamera_Pos.x, 0, ovr_Rig_Pos.z - centerCamera_Pos.z);のように回転させてから移動することで実装可能です。
実はもっと簡単な方法がありそうで怖いのですが、思いついたのでメモしときます。