20190829のUnityに関する記事は5件です。

【Unity】自分の思い描いたキャラクターを作成し、最速で動かす

はじめに

Unityでゲームを作る際、
自作のキャラクターをUnityの空間上で走り回らせたいとお考えの方へ

私もネットで調べて、候補となるツールのバリエーションに圧倒され、
「結局どれを使えばいいんだ!」と悩みました

そのため、各作業ごとのオススメのツール(無料)を紹介しながら、
実際に動き回るところまで説明していきたいと思います。

全体像

モデル作成から動かすまでの流れはこんな感じ
Untitled Diagram.png

最短で自作キャラをUnity上で動かすには、
Unityの「Humanoid」型のモデルを作成するのが手っ取り早いです。
Untitled Diagram (1).png

「Humanoid」型のモデルとは特定の骨を持つモデルのことで、
この骨格を持つモデルを作成することで、AssetStoreから取得したモデル(ユニティちゃんなど)のアニメーションを自作キャラクターに反映させることができます。

要は上記画像の「2.骨入れ」を決まった形で作成することで、
「3.アニメーションの作成」をすっ飛ばすことができます。

今回はこの方法で最速で作成をしていきます。

モデル作成

選択肢が多いモデル作成について、作りたいもの別に3種類紹介します。

Untitled Diagram (2).png

今回はSCULPTRISを使用してモンスターをつくていきたいと思います。
今回使用しないツールについては、参考リンクを貼っておきます。

・VRoid
「VRoid Studio」で作った3Dモデルを「Unity」で踊らせてみよう

・123D CATCH
友人をスマホで撮影して3Dモデル化してUnityで動かしてみた

モデル作成(SCULPTRIS)

直感的に作業を行えるため、深くは説明しません。

紹介・インストール方法が記載されているここを参考に、
思い思いのキャラクターを作ってみましょう。

最後にデータをエクスポートして完了です。
ここを参考に、.obj(モデルデータ)・.png(テスクチャーデータ)を保存しましょう。

公式ガイドは以下です
Sculptris Alpha6 日本語ガイド

骨入れ

上記モデル作成でどのツールをしようしたとしても、骨入れ作業はBlender一択になります。
Blenderでは骨入れ以外にもモデル作成など、様々なことができるそうなので詳しくはこちらをみてください。

インポート・位置調整

基本的にはこちら参考に進めてください。

1.ファイルから、インポート > Wavefront(.obj)を選択
スクリーンショット 2019-08-28 20.47.21.png

2.Cubeを削除し、Amatureを選択・下の箱のアイコンをクリックし、位置情報の表示
スクリーンショット 2019-08-28 20.34.45.png

3.青(上下)・赤(左右)・緑(前後)の矢印をスライドさせ、足の裏が赤緑の面にちょうどくっ付くように調整
スクリーンショット 2019-08-28 20.36.02.png

テスクチャの適用は行う必要ありません、これで準備は完了です。

骨入れ

基本的にはこちら参考に進めてください。

1.Shift + a でメニューを表示、アーマチェア > 単一ボーンを選択
スクリーンショット 2019-08-28 21.00.37.png

2.Amatureを選択した状態で、レントゲンオプションをオンにする
スクリーンショット 2019-08-28 21.04.48.png

3.参考リンクを参照に骨入れをする
(<重要>骨の数と名前を一致させてください)
スクリーンショット 2019-08-28 21.12.49.png

4.モデルとAmatureを選択
スクリーンショット 2019-08-28 21.15.03.png

5.ファイルから、エクスポート > FBX(.fbx)を選択肢し、指定フォルダに保存
スクリーンショット 2019-08-28 21.15.16.png

Unityでの作業

AssetStoreからモーションデータを取得し、モデルに設定しましょう。

1.AssetStoreから「Unity-Chan Model」をインポート
スクリーンショット 2019-08-28 22.26.45.png

2.右グリックでCreate > Animator Controllerを選択し、
作成したコントローラーをダブルクリック
スクリーンショット 2019-08-28 22.37.08.png

3.開いたウィンドウ上で右クリック、Create State > Emptyを選択
スクリーンショット 2019-08-28 22.41.30.png

4.Inspecterから名前を「Run」などに変更し、Motionを選択
※今回はUnity-Chanの「RUN00_F」を使用します
スクリーンショット 2019-08-28 22.43.23.png

5.もう一度Stateを作成し、Inspecter上で編集
※Stopと名付けて、「POSE01」を設定します
スクリーンショット 2019-08-28 22.47.59.png

6.AnimatorウィンドウでParametor上でBoolを選択し、run変数を作成
※アニメーションの状態遷移を切り替える変数を作成します。
スクリーンショット 2019-08-28 22.46.31.png

7.先ほど作ったRunとStopをそれぞれ右クリック、Make Transitionを選択し、
InspecterのConditionsを画像の通り設定
※run変数が指定した状態になるとアニメーションが切り替わる仕組みです。

スクリーンショット 2019-08-28 22.50.06.png
スクリーンショット 2019-08-29 20.59.04.png

8.作成したコントローラーをモデルのAnimatorに設定
スクリーンショット 2019-08-29 21.26.28.png

9.以下のスクリプトを作成し、モデルに設定

Model.cs
using 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);
      }
  }
}

完成!
スクリーンショット 2019-08-29 21.34.13.png

まとめ

sculptrisでキャラクターを作成するのに最初は時間がかかると思いますが、
慣れれば、この一連の作業で1時間かからずに終わると思います。

今回はAnimationをAssetStoreのものを使用しましたが、
Blenderを使えば自作アニメーションも作れます!

VtuberやVRChatなど遊びの幅も広がると思うので、ぜひ試してみてください!

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

Unityでよく使う色を指定して保存しておく方法

UI組んでて、指定の色を保存しておく方法はないかな~って見てたらあったのでそれの紹介

Swatches

Photoshopとかにも色の保存機能がありSwatchと言うらしいです
Unityにもあるのでそれの追加方法の紹介

新規Swatchesの作成手順

Inspectorの適当なColorをクリック→Swatches→CreateNewLibrary...をクリック
コメント 2019-08-29 181855.jpg

NameはSwatchesの表示名(例:"プロジェクト名"TextColors)
LocationはPreferences FolderとProject Folderの2つあるのでお好きな方へ

Preferences Folder - どのプロジェクトでも追加したSwatchesが表示される
Project Folder - 追加したプロジェクトでしか表示されない
コメント 2019-08-29 182052.jpg

あとはカラー指定→Swatchesのボタンクリックで追加されます
コメント 2019-08-29 182814.jpg

適当に複数追加
コメント 2019-08-29 182925.jpg

あと、間違って色を追加した場合は色を右クリック→ReplaceもしくはDeleteで変更/削除ができます

コメント 2019-08-29 183120.jpg

ちなみに表示はGridとListで切り替えることが出来て、Listだと色に名前をつけることが出来ます
コメント 2019-08-29 183229.jpg

終わり

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

[Unity] Shader Graph でノイズ関数を改造してタイリングに対応するカスタムノードを作る


追記:本記事の改良版
[Unity] Shader Graph でノイズ関数を改造してタイリングに対応するカスタムノードを作る(改)
を投稿しました。コードの修正があるので、冒頭のカスタムノードの作成方法について以外はそちらを参照してください。


この記事で使用したUnityバージョンは2019.2.1f1です。

はじめに

Shader Graph はUnityでコードを書かずにシェーダを記述できる素敵な機能です。これがたいへんよくできていて、テキストエディタが大好きな私でも「もうシェーダをテキストで書いてる場合じゃないな」と思わされます。

とはいえ既存のノードでやりたいことが何もかもカンタンにできるわけでもないようで、例えば標準のノイズ生成は繰り返したときの境界がリピートされていなかったりします。今回は、これを対応するためのカスタムノードを作成してみましょう。

現状確認

普通に Simple Noise を使用したノードの接続がこうなります。

Kobito.0Sk6d0.png

これをQuad4つ並べて貼り付けるとこう。

Kobito.aFozS1.png

UVの 0.0 と 1.0 が連続でないため、境界が見えてしまっています。円筒に貼り付ける場合などはこれがたいへん不便なので、カスタムノードで対応してみましょう。

カスタムノード作成

Shader Graph で右クリックして Create Node から
Kobito.J03XCF.png

Custom Functionを選びます。検索窓が便利。
Kobito.qSiEXs.png

こんな画面が出るので
Kobito.mKAtjh.png

Inputsに Vector2 で UV, Vector1 で Scale を追加、Outputs に Vector1 で Out を追加します。

Kobito.Xk3RtJ.png

この時点ではシェーダを記述していないので、エラーが出ています。
SimpleNoise を改造するので、名前は TiledSimpleNoise としましょう。Name に TiledSimpleNoiseを入れ、
TiledSimpleNoise.hlsl
というテキストファイルをプロジェクトのどこかに作成します。

てっとり早く結果を得たい方は、下にある改良後の TiledSimleNoise.hlsl の内容をテキストファイルとして TiledSimpleNoise.hlsl に保存し、ノードでこれを指定して完了です。

オリジナルの確認

SimpleNoise を参考にするため、既存ノードの SimpleNoise から Show Generated Code を選択すると、テキストエディタにソースコードが表示されます。

Kobito.4s1GOX.png

その記述の中で、自作の hlsl ファイルにおくべき部分は以下です。

TiledSimpleNoise.hlsl
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)
{
    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.hlsl
inline 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;
}

結果はこうなりました。目論見通り、境界が消えています。
Kobito.X2Q3W7.png

GradientNoise

同様にして GradientNoise も対応します。オリジナルではこうなってしまうので

Kobito.JZq6H6.png

改造します。同じ手順でカスタムノードを作り、オリジナルを改造した結果のソースコードはこうなります:

TiledGradientNoise.hlsl
inline 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;
}

結果:
Kobito.lyiA62.png

まとめ

ShaderGraph はとても便利で、カスタムノードも作れるともっと便利ですね。また、繰り返しに対応したノイズは使い勝手が良いと思います。自由に使っていただければ。

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

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を使っていけば良いんじゃないかなと思いました!

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

【Unity(C#)】VRカメラを任意のポジションに移動する方法

サマーアドベントカレンダー

Unityゆるふわサマーアドベントカレンダー 2019 #ゆるふわアドカレの枠が空いたそうなので
急遽代打で参加させていただきました!!

Unity関連の記事ということで本記事はスレスレですが、ゆるふわなのでセーフセーフ!

VRのカメラ

SteamVRやOculusIntegrationでのVR開発で独特なカメラの階層構造に行き詰まりました。

SteamVRCameraRig.jpg

OculusRig.PNG

ご覧の通り、CameraRigというゲームオブジェクトの子階層にカメラが存在しています。

このような階層構造になっている理由は、
VRのカメラがHMDを追従して動くからです。(他にも理由があるかもしれませんが)

つまり、直接カメラを動かすことはできず、親階層のCameraRigを動かすことで移動を再現します。

子階層のカメラは自由に動く

しかし、親階層のCameraRigを動かすことで移動を再現した場合、問題が発生します。
それは、子階層のカメラの位置を考慮しなければ任意の位置には移動できないことです。

カメラはユーザーの移動に伴って位置が変わるので、特定の位置に誘導が難しい場合は
強制ワープさせた先で壁にめり込んでしまうといった現象が引き起こされる恐れがあります。

図解

説明をわかりやすくするために図を用いて説明します。

赤い点Aの場所にプレイヤーをワープさせたいとします。
薄緑の枠は部屋、青い四角はCameraRig、黄色い点はCameraRigの中心、黒く塗りつぶされた四角はカメラ(プレーヤー)です。

Warp1.png

カメラ(プレイヤー)のポジションを直接変更することはできないので、
CameraRigのポジションを赤い点Aに移動させることで
カメラ(プレイヤー)移動を再現するのですが、そのまま移動させるとこうなります↓

赤い四角が移動後のCameraRigです。
Warp2.png
ご覧の通り、カメラが部屋から はみ出してしまいました。
カメラの位置を考慮して移動させる必要がある理由は以上です。

デモ

投げたキューブの位置にプレイヤーが移動するデモです。
少しわかりにくいですが、
キューブを持った状態で歩いてカメラの位置を初期位置からずらした後に投げてます。
Warp.gif

キューブと同じ座標にプレイヤーが移動していることがわかるかと思います。

コード

今回のデモに利用したコードです。

適当なオブジェクトにアタッチ
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);

のように回転させてから移動することで実装可能です。

実はもっと簡単な方法がありそうで怖いのですが、思いついたのでメモしときます。

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