20190822のUnityに関する記事は7件です。

【Unity】Animator.GetCurrentAnimatorStateInfoでステート遷移を待機するときの注意

バージョン

Unity 2019.1.9f1

はじめに

コルーチンを使ってステート遷移待ちをするときやらかしがちなミス。
ステート「Idle」から抜けたら何か処理をしたいとします。

NG例

NG
    // NG
    private IEnumerator WaitStateChange()
    {
        var currentAnimatorState = _animator.GetCurrentAnimatorStateInfo(0);
        yield return new WaitWhile(() => currentAnimatorState.IsName("Idle"));
        Debug.Log("Do Something.");
    }

上のコードは一見問題なさそうですが、実際に実行すると上手く動作しません。
「Idle」から抜けているはずなのに、いつまでたってもWaitWhileから抜けてくれないのです。

…それもそのはず。
Animator.GetCurrentAnimatorStateInfoの戻り値をキャッシュしたものでチェックしていますが、戻り値のAnimatorStateInfoは構造体なので値型。
それをいくら監視していても書き換わらないですね。

OK例

正しくは下記のように、毎回取る形にします。

OK
    // OK
    private IEnumerator WaitStateChange()
    {
        yield return new WaitWhile(() => _animator.GetCurrentAnimatorStateInfo(0).IsName("Idle"));
        Debug.Log("Do Something.");
    }

当たり前と言えば当たり前なんですが、私はこれで躓いてしばらく悩みました…。

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

【Unity(C#)】Animatorを使わずにパラパラ漫画作成

SpriteAnimation

Unityの機能を利用してきれいに作れるみたいです。
参考リンク:Unity でスプライトアニメーションを作る

しかし、ここまでガッツリと2Dのアニメーションを作りたいわけではありません。

何より、Animatorの設定を毎回やるのはタフなので
パラパラ漫画のような簡易スプライトアニメーションを作れないかと思いやってみました。

指定した秒数間隔で画像を切り替える

処理の内容としては
ImageコンポーネントのSource Imageを指定秒数後に別のTextureに入れ替えるだけです。

以下コードです。

using System.Collections;
using UnityEngine.UI;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditorInternal;
#endif

public class SpriteAnimation : MonoBehaviour
{
    [SerializeField]
    Image spriteImage;

    [SerializeField,HideInInspector]
    Sprite[] spriteTextures;

    [SerializeField]
    float animationFrameSeconds;

    [SerializeField]
    bool debug;

    public bool loop;

    Coroutine runCoroutine;

    void Update()
    {
        //デバッグキー
        if (Input.GetKeyDown(KeyCode.T) && debug)
        {
            SpriteAnimeStart();
        }
    }

    public void SpriteAnimeStart()
    {
        if (runCoroutine == null)
        {
            runCoroutine = StartCoroutine(SpriteAnimeCoroutine());
        }     
    }

    IEnumerator SpriteAnimeCoroutine()
    {
        if(loop)
        {
            while(loop)
            {
                for (int i = 0; i < spriteTextures.Length; i++)
                {
                    spriteImage.sprite = spriteTextures[i];
                    yield return new WaitForSeconds(animationFrameSeconds);
                }
            }

            runCoroutine = null;
            yield break;
        }
        else
        {
            for (int i = 0; i < spriteTextures.Length; i++)
            {
                spriteImage.sprite= spriteTextures[i];
                yield return new WaitForSeconds(animationFrameSeconds);
            }
            runCoroutine = null;
        }
    }


#if UNITY_EDITOR
    [CustomEditor(typeof(SpriteAnimation))]
    public class SpriteAnimationEditor : Editor
    {
        ReorderableList reorderableList;

        void OnEnable()
        {
            SerializedProperty prop = serializedObject.FindProperty("spriteTextures");

            reorderableList = new ReorderableList(serializedObject, prop);

            reorderableList.drawHeaderCallback = (rect) => EditorGUI.LabelField(rect, "アニメーションに使用する画像");
            reorderableList.drawElementCallback = (rect, index, isActive, isFocused) =>
            {
                SerializedProperty element = prop.GetArrayElementAtIndex(index);
                rect.height -= 4;
                rect.y += 2;
                EditorGUI.PropertyField(rect, element, new GUIContent("フレーム" + index));
            };
        }

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            serializedObject.Update();
            reorderableList.DoLayoutList();
            serializedObject.ApplyModifiedProperties();
        }
    }
#endif
}

Inspectorはこんな感じです。
Loopのチェックをオフにすると単発再生ができて、途中でオフにすると止まります。
SpriteAnimation.PNG

ReorderableListを使っているのでなかなかの使い心地です。

chain_2.gif

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

夏休みのVRoid自由研究

 環境

  • MacBook  macOS Mojave 10.14.5
  • CLIP STUDIO MODELER
  • CLIP STUDIO CLIP STUDIO PAINT
  • VRoid Studio VRoid公式
  • Unity
  • nanoem
  • VRoidmobile 作成したVRoidをカメラで撮影できてたのしい?

VRoid Studioでオリキャラの作成

2012年にAndroidアプリ『電卓天使れん君』を作成...いつか自分で3Dつくって動かしてみたいと思っていて、やっと念願のれんくん作成?✨
ざっくりだけど2日でできた!ありがたい?✨✨かわいい?
2019-06-8--03-35-091.png

VRoid Studioの操作

回転:右ドラッグ
移動:Space
ズーム:マウスホイール
VRoidヘルプ/ 操作方法 ショートカットキー

エクスポート、インポートでテクスチャ編集できる
VRoidヘルプ/便利なテクニック集はこちら

VRoid HubにアップロードしてVRoidmobileでも遊べるようになる
(Pixivのアカウントが必要。自分はキャラクターを非公開で登録。)
VRoidヘルプ/VRoid StudioからVRoid Hubへモデルデータをアップロードする

作成したキャラをクリスタの素体として使いたい

参考サイト
VroidモデルをCLIP STUDIOにインポートする方法【VRM/FBX】
VroidモデルをCLIP STUDIOでポーズ適用させる方法【ボーンの設定】

  • VRoid Hub
  • Unity
  • CLIP STUDIO MODELER
  • CLIP STUDIO CLIP STUDIO PAINT

ボーンの設定あっという間だった?‍♂️✨
スクリーンショット 2019-08-22 3.04.21.png

作成したキャラを動かしてみたい

参考サイト
qiita/ガチ初心者がMacでVRoidモデルをMMDモデルに変換して動かそうとして1週間とかした

  • VRoid Hub
  • Unity
  • nanoem

MMD動かすの憧れてたけど動いた✨けど、なんか、すごく難しい...。

スクリーンショット 2019-08-17 22.05.22 2.png

Vroidキャラクターをもっと素敵にしたい

意識していなかった点やいろんな人の作品をみたり、メンズモデルを作成してる途中なので筋肉や影の描き方を参考にしてます。

参考サイト
qiita/VRMモデルの視線制御(目の可動範囲)の設定方法
booth/VRoid
pinterest/筋肉 描き方

8月の自由研究の様子

8/3 れんくんVRoid Studioで作成、操作覚える
8/4 VRoid Hubにアップロード、いろんなサイトや動画で勉強
8/5 クリスタに素材としてつかう(ボーンなし)、Unityとクリスタモデラー使用
8/6 VRoidmobileでAR撮影で写真撮る
8/7 クリスタモデラーでボーン入れる、クリスタで素体にポーズ取り込むことができた
8/8 メンズモデルのVRoid作成、pinterestで筋肉や影や服の参考を調べる
8/10 メンズモデルのVRoidに筋肉やガイド線書き込む
8/11 メンズモデルのVRoidに耳やガイド線書き込む
8/12 メンズモデルのVRoidに書き込んだ線などをクリスタでキレイに
8/13 メンズモデルのVRoidのスーツ書き込む
8/14 クリスタ素材の3Dキャラクターのバリエーション登録方法調べるも、1体になったfdxをパーツ分割しないといけないぽく、Blenderまだわからないのでいったん保留...
兄弟にVRoid Studio、VRoidmobileの使い方教えてあげた?自分より髪つくるのうまい?
8/15 メンズモデルのVRoidに肌影書き込んで立体的に、スーツ靴をしっかり目に作成
8/16 メンズモデルのVRoidのスーツ・ジャケットを作成、修正中
8/17 nanoemにモデル、アイテム、モーションを取り込んでれんくんダンスしてくれた!すごい!
8/18 VRchatのアカウント作成してみたもののVR機器持ってないのでいったん保留...
8/19 れんくん洋服パーカー、半ズボン、バッシュ2種作成
8/21 兄弟にVRoid Studio、VRoidmobileの使い方教えてあげた?洋服かわいいのできてた⭐️

すごい!かわいい!!こっちみてる✨
2019-19-8--03-16-401 2.png

おわりに

VRoidに毎日さわるのを意識して取り組んで、でもやっぱり3Dは苦手分野で多少グロッキーになりつつも、いい感じになっていくのすごい楽しかったです?✨

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

Unity+VSCodeで「The reference assemblies for framework ".NETFramework,Version=*" were not found.」を解決する

経緯

UnityのスクリプトエディタにVisual StudioでなくVSCodeを使う環境を構築していたところ、「.NET Framework 4.7.1のアセンブリ参照が見つからない」という旨のエラーが出てデバッグ実行できない状況になりました。解決したので、備忘録として残します。

Visual Studioがあれば解決できるようなのですが、私はVSCode環境を構築するのにVisual Studioを入れるのは嫌だったのでやってません。

エラーについて

下記のようなエラーです。本当はもっと長い。

The reference assemblies for framework ".NETFramework,Version=v4.7.1" were not found.

解決策 (2019/08/22)

  1. VSCodeが認識できてない.NET Frameworkのバージョンをメモ
  2. .NET Frameworkのダウンロードページから、メモしたバージョンのDeveloper Packをインストールする(私の場合は4.7.1)
    image.png

  3. UnityエディタとVSCodeを再起動

これで直りました。

参考

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

ドットインストールのUnity入門の補足説明

この記事は、筆者が大学でUnityを教えるにあたって、事前準備として夏休みの宿題として学生にドットインストールのUnity入門を学習させるための補足説明です。

学生用に書いたものですが他にも役に立つ人がいるかもしれないので公開します。

ドットインストール - Unity入門

この記事の目的

ドットインストールのレッスンを否定している訳ではなく、夏休みの宿題用の素晴らしい教材としてありがたく使わせていただいています。その上で、本当に何も知らない学生にとっていきなり出てくる様々な概念や用語について、さらに詳しく説明することでより理解度を深めることを目的としています。

この記事の対象者

この記事はUnity入門のためにドットインストールで学ぶ、プログラミング初心者を対象にしています。

目次

この記事には以下のような構成でUnity入門について解説しています。カッコの中の#11などはドットインストールのレッスンの中で対応する動画の番号を示しています。

  1. まずは一度レッスンを全部通してやってみる
  2. スクリプトおよび GameObject の関連付けについて (#11)
  3. スクリプトを作成すると自動的に挿入されているコードについて (#11)
  4. 型についての補足説明 (#12)
  5. クラスについての補足説明 (#12)
  6. #12のスクリプトの補足説明 (#12)
  7. #14のスクリプトの補足説明 (#14)
  8. プレハブ(Prefab)って何? (#16)
  9. なぜ空のGameObjectを作成するのか? (#16)
  10. RigidBodyって何? (#19)
  11. OnCollisionEnterって何? (#19)

1. まずは一度レッスンを全部通してやってみる

まずは頭の中に?がいっぱいになったとしてもとりあえず、言われるがままにレッスンを進めて最後までやってみてください。数時間で終わると思います。その後この記事を見ながら2周目をやるとかなり理解が深まると思います。

ドットインストール Unity入門

2. スクリプトおよび GameObject の関連付けについて (#11)

いきなり Add Component してスクリプトを作って、さらっと紐づけられていますが、はじめてUnityに触れる人にとっては以下のような疑問が出てくると思いますので補足として解説します。

スクリプトって何?

「スクリプト」とはプログラミング言語(UnityではC#)で書かれたプログラムのことです。Unityでは(最初のうちは)1つの GameObject に対して1つのスクリプトを書いて、その GameObject の挙動を制御します。

コンポーネントって何?

コンポーネントは、GameObject に対してさまざまな見た目や、機能、挙動を加えることができます。このレッスンではスクリプトをコンポーネントとしてGameObjectに追加していますが、GameObject の位置や大きさ、回転を指定する Transform、オブジェクトの色や質感を指定する Material などもコンポーネントの1種です。

紐づけるってどういうこと?

GameObject にコンポーネントとしてスクリプトを追加すると、GameObject とスクリプトが関連づけられた状態になります。その状態になってはじめて、スクリプトに紐づけられた GameObject がシーン上にあるときに、そのスクリプトが実行されます。スクリプトを書いただけでは実行されませんので注意してください。

翻訳が中途半端ですが、以下のUnity公式マニュアルでも解説されています。
https://docs.unity3d.com/ja/current/Manual/CreatingAndUsingScripts.html

3. スクリプトを作成すると自動的に挿入されているコードについて (#11)

Unityでスクリプトを作成すると、自動的に以下のようなコードが書かれたスクリプトが作成されます。
この内容についてそれぞれ説明していきます。

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

public class BallScript : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {

    }

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

    }
}

using

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

Unityのスクリプトではさまざまな機能が使えますが、それらはいきなり使うことはできず、使いたい機能を呼び出さなければいけません。例えば、スマホでは本当にいろんなことができますが、アプリをまったくインストールしていないスマホでは何もできないことと似ています。

C#では、さまざまな機能が「名前空間」というものの中にまとめられています。コンピュータのフォルダにのような概念です。これらの行では using の後に続いて名前空間を記述することで、 System.CollectionsSystem.Collections.GenericUnityEngine という名前空間を利用するということを宣言しています。

「名前空間」という変な名前ですが、スクリプトを書いていると「Ball」とか「BallFactory」とか「speed」とかクラスや変数にいろんな名前を付けます。自分で適当に付けた名前と、Unityに標準で用意されている名前と被ってしまっても大丈夫なように、さまざまな機能が名前空間で分けられているのでちゃんと区別できるようになっています。なのでこの名前が被らないようにするという意味で「名前空間」(name space)という名前が付いています。

UnityEngine

Unityで標準装備されている基本的なC#の部品がこの中にまとまっています。
以下のUnity公式スクリプトリファレンスにアクセスしてください。

https://docs.unity3d.com/ja/current/ScriptReference/index.html

左側の「UnityEngine」の「+」をクリックして開いて、
スクリーンショット 2019-08-23 22.05.01.png

その中にある「Classes」の「+」をクリックすると UnityEngine の中にあるたくさんのC#の部品を確認することができます。
スクリーンショット 2019-08-23 22.05.18.png

このようにUnityには大量の便利な機能(クラス)が用意されています。ただその全てを覚えることはできないので、分からないことがあれば、ますスクリプトリファレンスを見るようにしてください。

セミコロン

;(セミコロン)はC#の命令の区切りです。それぞれの命令の最後には必ず ; が必要です。これを書き忘れるとエラーになるので、スクリプトを実行したときにエラーが発生したときはまず ; を忘れてないか確認してみましょう。

コメント

// Start is called before the first frame update

// Update is called once per frame

スクリプトを書くときにちょっとしたメモを書いたり、書いたスクリプトを部分的に無効にしたいといった場合には、「コメント」を使います。Unityが生成するスクリプトにも上記のような解説がコメントとして記述されています。
コメントを書くには // と記述するとその行の // 以降の文字はコメントとなり、スクリプトを実行するときに無視されるようになります。 // は1行ずつしかコメントにできないので、複数行まとめてコメントにしたい場合は /* と書くと、そこから */ と記述するまでがコメントになります。

// 1行コメント

/*
複数行の
コメント
です
*/

クラス

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    // クラスの中身
}

この行では PaddleScript というクラスを宣言しています。クラスはスクリプトで使うさまざまな部品のことで、あらかじめさまざまなC#標準のクラスやUnity標準のクラスが用意されており、ここで宣言しているように、自分で作ることもできます。

後ろの : MonoBehaviour の部分は、このクラスはUnityで標準装備されている MonoBehaviour というクラスの機能を継承するということを指定しています。機能を継承することで、もともとの MonoBehaviour が持っている機能にさらに自分で機能を追加した新しいクラスを作ることができます。

MonoBehaviourUnityEngine という名前空間で定義されているので、これを利用するには1行目の using UnityEngine; が必要になります。宣言したクラスの中身のスクリプトはすべてここに書かれた中カッコ { から最後の中カッコ } の内側に記述します。

メソッド

PaddleScript.cs
void Start()
{
    // メソッドの中身
}

ここではクラス宣言の内側に Start というメソッド(関数、ファンクションとも言う)を宣言しています。メソッドはクラスの持つ機能のことで、自由に名前をつけていくつでも作ることができます。ここで宣言している Start メソッドはもともと継承元の MonoBehaviour クラスで宣言されていて、紐づけられた GameObject が作成された時に一度だけ実行されます。

ここではその Start メソッドを上書きすることで、GameObject が作成されたときに実行したい独自の処理を記述することができます。メソッドの中身のスクリプトは、ここにかかれた中カッコ { からメソッドの終わりの中カッコ } の内側に記述します。

メソッドは何らかの処理を実行した後に、結果を返すことができますが、ここにある void は結果を何も返さないということを示しています。

Updateメソッド

PaddleScript.cs
void Update()
{
    // メソッドの中身
}

ここは Start メソッドと同様に継承元の MonoBehaviour クラスで宣言されている Update というメソッドを上書きしています。Update メソッドは 紐付けられた GameObject がシーンに配置されている間、画面が更新される度に実行されます。通常だと1秒間に60回ほど実行されます。

インデントについて

PaddleScript.cs
public class BallScript : MonoBehaviour
{
    private float speed;
    // Start is called before the first frame update
    void Start()
    {
        speed = Random.Range(5f, 15f);
    }

    // Update is called once per frame
    void Update()
    {
        transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);
    }
}

このスクリプトではクラスの宣言の後の中カッコ { 以降、クラス宣言の終わりの中カッコ } まで、それぞれの行の始まりが右に一段ずれています。また、クラス内のメソッド宣言の後の中カッコ { 以降さらに右にずれます。
こうやって、始まりの中カッコ { が入力される度に行の始まりを1段ずつ右にずらしていくことを「インデント」と言います。
インデントすることで、今クラス宣言の中身を書いているのか、メソッド宣言の中身を書いているのかが分かりやすくなります。逆に、インデントされていないスクリプトは長くなってくると解読が困難になるため、必ずインデントをする癖をつけましょう。

実際にはタブを入力することでインデントできるのですが、最近のエディタでは自動的にインデントしてくれるものが多いです。

少し古い記事ですが、Unityが最初に生成するスクリプトの中身について、この記事でもう少し詳しく解説されています。
https://qiita.com/JunShimura/items/3c2e23bb77cc9085bfda

4. 変数と型についての補足説明(#12)

変数について

プログラミングをする上でとても基本的な概念に「変数」があります。数学で出てくるx, yといった変数とは少し違います。プログラミングでは、数値や文字列など様々な値を扱いますが、そういう値を保持しておく箱のようなものがプログラミングにおける変数です。変数について簡単にまとめると以下のような特徴があります。

  • 変数は値を保存したり読み出したりできる箱のようなもの
  • 変数と言っても数値だけだはなく文字列やオブジェクトなどなんでも保存できる
  • 変数には自由な名前をつけることができる
  • 変数を作るには、宣言をする必要がある
  • 変数には型という概念があり、宣言するときに型の指定が必要
  • 変数に値を保存することを「代入」という

変数を宣言するには、型の後に変数名を記述します。

int x;

この例では x という名前で int という型の変数を宣言しています。( int は整数値を扱う型)
変数に値を保存することを「代入」と言いますが、値を代入するには = を使います。

int x;
x = 2;

この例では1行目で int 型の変数 x を宣言して、2行目で x2 という値を代入しています。
値の代入は = の右側に記述された値が、左側に記述された変数に保存されます。必ず右→左です。左→右はありませんので注意してください。

また、変数の宣言と同時に値を代入して初期化することもできます。その場合は以下のように記述します。

int x = 2;

また、変数に値を代入する場合の = の左側に変数を使うこともできます。
以下の例では変数 x に 2 を代入し、変数 y に x + 1 の計算結果を代入しているので、 y の中身は3になります。

int x = 2;
int y;
y = x + 1;

0ff って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

C#のスクリプトでは整数値や小数値、文字列などいろいろな値を扱います。それらはの値の種類を「型」と言います。
例えば整数を扱うには int という型を利用し、小数を扱うには精度の高い double とその半分の精度の float といった種類の型があります。
C#では 01 のような整数の値は自動的に int として扱われ、 0.00.5 のように記述した値は自動的に double として扱われます。 0f0.5f のように数値の後に f を付けるとその数値は double ではなく float として扱われるようになります。

ここで使われている Vector3double ではなく float を扱うクラスなので、 0 ではなく 0f とする必要があります。(ドットインストールでは浮動小数点数を渡す決まりになっているので f を付けると解説されていますが、実際には浮動小数点数を渡すから f を付けるのではなく、 浮動小数点数の中でも double ではなくて float を渡す決まりになっているので f を付ける必要があるということになります)

5. クラスについての補足説明 (#12)

Unityで書く1つのスクリプトは基本的に1つのクラスになります。クラスはスクリプトで扱うオブジェクトの型の種類の1つで、クラスを作ることで型を自分でつくることができます。クラスを作るとその型のオブジェクトをいくつでも作ることができ、そのオブジェクトのことを「インスタンス」と言います。

クラス(型)はオブジェクトの設計図のようなもので、それを元に実際に生成されたオブジェクトがインスタンスです。

スクリーンショット 2019-08-27 20.32.54.png

いきなり出てくる transform って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

ここで書いている PaddleScriptMonoBehaviour というクラスの持つメソッド(機能)や変数を全て継承して、それに独自の機能を新たに追加しているクラスです。 transformMonoBehaviour クラスが持つ変数の1つで位置情報や変形などに関する情報がまとめて格納されています。

Unity公式スクリプトリファレンスに書いてありますが MonoBehaviour クラスは transform という変数を持つと書いてあります。なので、 MonoBehaviour を継承している PaddleScript も変数 transform を持つことになります。

MonoBehaviour クラスのリファレンス(継承メンバーの変数のところに transform の記述がある)
https://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.html

そしてそこをクリックすると transformTransform というクラスの変数だということが分かります。

スクリーンショット 2019-08-26 22.30.31.png

transform.position って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

スクリプトリファレンスを見ると、 Transform クラスは position という変数を持っていて、この変数 position は Vector3 という型だということが分かります。

Transform クラスのリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Transform.html

Transform クラスの変数 position のリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Transform-position.html

なので先ほどの図に position を付け加えると以下のような相関関係になります。


スクリーンショット 2019-08-26 22.22.59.png

Vector3 って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

Vector3x, y, z の3つの float の値をまとめて扱えるので3D空間での位置情報を扱うのに便利なオブジェクトです。transform.position の型は Vector3 なので、ここでは Vector3 を使う必要があります。
Vector3 はクラスではなく構造体というものですが、今さらに新たな概念を理解すると大変だし、ほぼクラスと同じように使えるので今はほぼクラスみたいなものだと思ってもらって大丈夫です。)

Vector3 の公式リファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Vector3.html

new って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

new はクラス(構造体も含む)のインスタンスを新しく作成する時に使うキーワードです。
new を使ってクラスのインスタンスを新しく作るときには「コンストラクタ」と言う特殊なメソッドが実行されます。コンストラクタはインスタンスが新しく作成されるときに最初にやっておくべき初期化処理をしたり、パラメータ(引数)を受け取ってインスタンスの持つ変数に値をセットするといった処理をします。
new キーワードを使ってクラスのインスタンスを作成するには、 new の後にクラス名(構造体名)を記述して、その後にカッコ ( ) を書いて、コンストラクタに渡すパラメータを書きます。

コンストラクタに渡すパラメータについて調べるには、Vector3 の公式リファレンスを確認してください。
https://docs.unity3d.com/ja/current/ScriptReference/Vector3.html

「コンストラクタ」の部分の Vector3 をクリックするとコンストラクタのページに移動します。
https://docs.unity3d.com/ja/current/ScriptReference/Vector3-ctor.html

Vector3 のリファレンスを見てみるとコンストラクタで3つの float の値をパラメータとして受け取ることが分かります。

なので、

new Vector3(0f, 0f, 0f)

と書くと、Vector3 のインスタンスを新たに1つ作成して、コンストラクタに 0f, 0f, 0f を渡しているので、x, y, z が全部ゼロのVector3 のインスタンスが作成されます。

+= って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

transofrm.position の後に +=new Vector3が続いていますが、これは、PaddleScript クラスのインスタンスが持っている変数 transformposition= の右側で新しく作られる Vector3 クラスのインスタンスを足しているということになります。

transform.positionnew Vector3 で作られるオブジェクトもどちらも Vector3 のインスタンスで、それぞれのインスタンスの持つ x, y, z の変数が足し合わされた結果が transform.position に保存されます。

6. その他スクリプトの補足説明(#12)

Input って何?

PaddleScript.cs
void Update()
{
    transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime * speed, 0f, 0f);
}

Input はUnityで、キーボードやマウス、タッチディスプレイ、ゲームパッド、ジョイスティックなどのユーザーのさまざまな入力を取得することができるクラスです。

UnityのInputで入力を扱う - Qiita
https://qiita.com/yando/items/c406690c9ad87ecfc8e5

Unity公式マニュアル - 一般的なゲーム入力
https://docs.unity3d.com/ja/current/Manual/ConventionalGameInput.html

Input のスクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Input.html

Input.GetAxis("Horizontal") って何?

PaddleScript.cs
void Update()
{
    transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime * speed, 0f, 0f);
}

Input.GetAxis("Horizontal") はゲームコントローラーの十字キーやジョイスティックの水平方向の状態を float 型の数値として取得します。垂直方区の状態は GetAxis に渡すパラメータを "Horizontal" ではなく "Vertical" に変えることで取得できます。

Input.GetAxis のスクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Input.GetAxis.html

この部分の1行はとても長いので、スクリプトを以下のように書きかえてみましょう。

PaddleScript.cs
void Update()
{
    float horizontalAxis = Input.GetAxis("Horizontal");
    float x = horizontalAxis * Time.deltaTime * speed;
    transform.position += new Vector3(x, 0f, 0f);
    Debug.Log(horizontalAxis);
}

まず、 float 型の変数 horizontalAxisInput.GetAxis("Horizontal") で取得したゲームコントローラーの状態を取得しています。キーボードのキーを何も押していないときには、変数 horizontalAxis には 0f が入り、左キーを押しているときは -1f が、右キーを押しているときは 1f が入ります。

次の行では、それに Time.deltaTimespeed をかけた値を float 型の変数 x に代入しています。

その次の行では new Vector3 でコンストラクタのパラメータに x を渡しています。これらを1行で書いていましたが、こうやって分けて書くとよりわかりやすいと思います。

次の行の Debug.Log(horizontalAxis); をすることで、実行した時にキーボードの状態に応じて horizontalAxis の値が変化することがコンソールで確認できると思います。(この行は確認した後は不要なので消しましょう)

スクリーンショット 2019-08-25 22.07.40.png

キーボードはゲームコントローラーがない場合にコントローラーの代わりに使うことができるので、Input.GetAxis("Horizontal") で矢印キーの状態が取得できます。

Input.GetAxis("Horizontal") を使っておけば、USBのゲームコントローラーなどUnityが対応するさまざまなコントローラーを接続した時にプログラムを書き変えることなく、そのままプレイできます。

Time.deltaTime って何?

PaddleScript.cs
transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);

Time.deltaTime は、前回のフレーム(前回 Update メソッドが実行されたタイミング)からの経過時間(秒)を取得することができます。 Update メソッドは必ずしも一定間隔で実行されるわけではありません。コンピュータの負荷によって遅くなったり早くなったりします。

例えば1フレームごとに1ピクセルずつ動くプログラムを書いた場合、フレームの間隔が安定して0.1秒だとすると1秒間に10回実行され、10ピクセル動くことになりますが、もしコンピュータの負荷によりフレームの実行間隔が遅くなり、1秒間に5回しか実行されなければ、5ピクセルしか動かないことになります。

スクリーンショット 2019-08-27 10.33.01.png

このようにフレームごとに一定の値だけ動かすといったスクリプトを書くとフレームの実行間隔により見た目のスピードは一定ではなくなります。なので、フレームの実行回数によりオブジェクトを動かすのではなく、経過時間によりオブジェクトを動かす方が正しくスムーズにオブジェクトを動かすことができます。そのために Time.deltaTime を利用します。

スクリーンショット 2019-08-27 10.33.36.png

Unity公式マニュアル - タイムとフレームレートの管理
https://docs.unity3d.com/ja/current/Manual/TimeFrameManagement.html

7. #14のスクリプトの補足説明 (#14)

変数 speed はなぜその場所に記述するのか?

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    private float speed;

    // Start is called before the first frame update
    void Start()
    {
        speed = 5f;
    }

クラスの記述の決まりとして、class の宣言の後に中カッコがあり、その中にメソッドを記述しますが、class 宣言の中にメソッドと同列で変数を宣言すると、その変数は「インスタンス変数」としてそのクラスのインスタンスが持つ変数になります。ドットインストールの例で言うと、Ball の数だけそれぞれがこの speed という変数を持っていて、Start() でランダムな値が設定されるので、インスタンスごとに個別の値を保持しています。

スクリーンショット 2019-08-27 20.41.23.png

変数のスコープ(有効範囲)について

変数にはスコープ(有効範囲)というものがあり、宣言する場所によってスコープが変わります。ここでは、クラス宣言の内側で変数 speed を宣言しています。こうすることで、このクラスのインスタンスが存在する限り、変数 speed も存在します。

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    private float speed;
    void Start()
    {
        speed = 5f;
    }
}

メソッドの中で変数を宣言することもできます。ただし、メソッドの中で宣言した変数はそのメソッドの実行が終了した時点で破棄されてしまうので、メソッドの実行時に一時的に使いたいといった場合にメソッド内で変数を宣言します。

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    void Start()
    {
        float speed;
        speed = 5f;
        // Start()メソッドはここまでなので、ここで変数 speed は破棄される
    }

    void Update()
    {
        // ここでは speed 変数は存在しない
    }
}

private って何?

PaddleScript.cs
public class PaddleScript : MonoBehaviour
{
    private float speed;

クラスの持つインスタンス変数は、そのクラスの外側からアクセス可能にするか、そのクラス内でのみアクセスできるようにするかを設定できます。変数宣言の前に private と付けるとその変数にはクラス内からしかアクセスできないようになります。private の代わりに public とすると、その変数には外部からアクセスできるようになり、クラスの外部(他のクラスやUnityのInspectorなど)から値を参照したり変更したりすることができるようになります。

8. プレハブ(Prefab)って何? (#16)

ドットインストールではさらっと登場して使われているプレハブについて、とても重要な概念なのでもう少し説明しておきます。

プレハブは、同じような GameObject をたくさん配置して、それぞれを個別に動かしたいような場合のテンプレートのような機能です。スクリプトで使うクラスと似た概念です。

Ballのプレハブをドラッグ&ドロップでシーンにたくさん配置するとヒエラルキービューでは青く表示されます。これはこのオブジェクトがプレハブから作られたことを意味します。

スクリーンショット 2019-08-26 17.45.25.png

これらの球体にまとめて変更を加えたければ、元のプレハブのScaleを変えればシーンに配置した全てのオブジェクトに反映されます。また、色を変えたければ、元のプレハブにマテリアルを適用することで全てのオブジェクトに反映されます。

スクリーンショット 2019-08-26 17.52.14.png

ただ、これらのオブジェクトは個別にそれぞれの設定をすることも出来て、個別のオブジェクトのプロパティをプレハブのデフォルトの値から変更すると、それらは元のプレハブの設定を上書きした個別の値を持つことができます。

スクリーンショット 2019-08-26 17.54.50.png

また、ドットインストールでやっているようにスクリプトからプレハブを元にオブジェクトをシーンに追加することもできます。

Unity公式マニュアル - プレハブ
https://docs.unity3d.com/ja/2018.1/Manual/Prefabs.html

9. なぜ空のGameObjectを作成するのか? (#16)

ドットインストールでは BallFactory という空の GameObject を作成し、そこの BallFactoryScript という名前でスクリプトを関連づけています。
このスクリプトは Ball のプレハブから 実際のシーンに Ball を生成して配置するためのスクリプトですが、スクリプトを作っただけでは実行することが出来ないので、スクリプトを実行するためだけの空の GameObject を生成して、それに関連付けすることで、スクリプトを実行できるようにしています。
また、Ballに関連付けられた BallScript は、BallFactoryScript から Ball のオブジェクトが生成される度に Start() が実行されて、画面上の Ball の数だけそれぞれの Update() が実行されます。

10. RigidBodyって何?(#19)

RigidBody はコンポーネントの1つで、 このコンポーネントを追加した GameObject は物理演算により動くようになります。物理演算によって動くということは、現実世界の物理の法則と同じように、Unityの3D空間で、重力により落下したり、他のオブジェクトと衝突して跳ね返ったり、空気抵抗を受けたりといったことをシミュレーションすることができます。

Inspector で RigidBody の設定項目を見てみると、重量や空気抵抗の大きさ、重力の影響を受けるかどうか、他のオブジェクトとの衝突を検知するかといった項目があります。これらを設定することで GameObject にさまざまな挙動をさせることができます。

スクリーンショット 2019-08-26 18.07.55.png

Unity公式マニュアル - RigidBody
https://docs.unity3d.com/ja/current/Manual/class-Rigidbody.html

11. OnCollisionEnter って何?(#19)

BallScript.cs
private void OnCollisionEnter(Collision collision)
{
    if (collision.gameObject.CompareTag("Paddle"))
    {
        Destroy(gameObject);
    }
}

OnCollisionEnter はこのスクリプトに紐づけられた GameObject が他のオブジェクトと衝突した場合に実行されるメソッドです。( RigidBody コンポーネントが追加されている必要があります)

MonoBehaviour のスクリプトリファレンスを見てみると「メッセージ」の欄に OnCollisionEnter が記載されています。

MonoBehaviour クラスのスクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.html

そこをクリックして OnCollisionEnter のリファレンスを見てみると、パラメータ(引数)として Collision という型のデータを受け取ることが分かります。

さらに Collision をクリックして Collision クラスのリファレンスに移動すると、 Collision クラスは衝突した相手のオブジェクトや、衝突時のスピードなどの情報をまとめたクラスだということが分かります。

このようにして、はじめて使うクラスやメソッドが出てきた時は、スクリプトリファレンスをたどって読んでいくと理解が深まると思います。

最後に

ドットインストールのUnity入門をやって、ある程度Unityの雰囲気が分かった後は、Qiitaにある以下の素晴らしい入門記事を読むと良いと思います。

UnityをC#で超入門してみる #0 超目次

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

[Unity]シリアライズできるWaitForSeconds

GCをなるべく抑えるためにWaitForSecondをキャッシュして、さらに待ち時間をエディットできるようにしようと思うと
下記のコードのように変数と初期化処理が無駄に増えて面倒です

キャッシュ.cs
        [SerializeField]
        private float waitSecond;

        private WaitForSeconds wait;

        private void Awake()
        {
            wait = new WaitForSeconds(WaitSecond);
        }

        IEnumerator Coroutine()
        {
            while (true)
            {
                yield return wait;
            }
        }

シリアライズできるWaitForSecondがあればすべて解決というわけで作りました

SerializableWaitForSeconds.cs
    [Serializable]
    public class SerializableWaitForSecond : CustomYieldInstruction
    {
        public SerializableWaitForSecond(float second)
        {
            wait_Sec = second;
        }

        [SerializeField]
        private float wait_Sec;

        private float deltaTime;

        private bool isFirstMoveNext = true;
        public override bool keepWaiting
        {
            get
            {

                deltaTime += isFirstMoveNext ? 0 : Time.deltaTime;
                var waiting = wait_Sec >= deltaTime;
                isFirstMoveNext = false;
                if (!waiting)
                {
                    deltaTime = 0;
                    isFirstMoveNext = true;
                }
                return waiting;
            }
        }
    }

使う側のコードはこんな感じ

使い方
        [SerializeField]
        private SerializableWaitForSecond Wait;

        IEnumerator Coroutine()
        {
            while (true)
            {
                yield return Wait;
            }
        }

keepWaitngにUnityからアクセスされたタイミングでDeltaTimeを加算すると
そのフレームのDeltaTime分ずれるので最初だけスキップしています
whileループ時に使えるように自動でリセットがかかるようにしているので
概ね標準のWaitForSecondと変わらない感じで使えると思います

GCも極力抑えられるはずです

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

ML-Agentsで模倣学習(GAIL)を取り入れた強化学習を行う

要約

  • コンセプト:報酬がスパースな環境だとエージェントが報酬に辿り着けず、学習が進まない・・ので模倣学習で人間が手本を見せる
  • UnityのML-Agentsで強化学習+模倣学習(GAIL)をする手順の紹介

はじめに

Unityでは、ML-Agentsというライブラリを使うことで機械学習を行うことができます。ML-Agentsは特に強化学習(Reinforcement learning)を容易に行うことが可能であり、Unityエディタ上で容易に環境を構築、スクリプトで報酬の設定等を行うこともできます。
ですが、ゲームにおいて強化学習でうまく挙動を学習させることが難しい場合があります。

強化学習とその課題

強化学習は、エージェントの一連の行動の結果として報酬を与えることで学習が進行します。しかし、報酬がスパースな(少ない)環境では、エージェントが報酬となる行動に辿り着くことが難しいため、エージェントの学習が進まない、といった問題があります。

この問題の解決策はいくつかの方法が存在しますが、今回は模倣学習を取り入れた場合について紹介します。

模倣学習

模倣学習(Imitation learning)は、教師となる人間(TeacherやExpertとか言ったりします)のデモンストレーションをエージェントが模倣するように学習させます。
これを利用し、人間が報酬にたどり着くような行動を模倣学習でエージェントに学習させることで、エージェントは報酬を得ることが容易になります。

強化学習と模倣学習を組み合わせると、エージェントは強化学習のみの場合よりも早く報酬にたどり着く行動を学習することが可能になります。

ML-Agentsでは現在Behavioral Cloning(BC)とGenerative Adversarial Imitaiton Learning(GAIL)の2種類の模倣学習をサポートしています。

公式のドキュメントによると、それぞれ以下の特徴があるそうです。
詳しく知りたい方は敵対的模倣学習の紹介をみるといいかもです

Behavioral Cloning(BC)

  • 高速に学習させられる
  • デモンストレーションを正確に模倣するように学習させられる
  • 多数の学習用のデモンストレーションが必要
  • Unityの場合はエディタ上でリアルタイムにデモンストレーションしながら学習させられる

Generative Adversarial Imitaiton Learning(GAIL)

  • デモンストレーションの数が少ない場合でも効果的
  • 強化学習と組み合わせた学習が可能
  • 事前学習が可能

今回は強化学習と組み合わせたいので、GAILを使う方針で進めていきます

動作環境

Unity ML-Agents 0.9.0を導入しています。
Unityではじめる強化学習 / Unity ML-Agents 0.9.0のチュートリアルを参考にして、導入と仮想環境の構築を行っています。

UnityのバージョンはUnity 2019.1.3f1、OSはWindows 10を使っています。

Pyramidsサンプルで強化学習+模倣学習を試す

ML-Agentsに入っているPyramidsサンプルを使って、強化学習+模倣学習でエージェントに課題を解かせてみます。
Pyramidsサンプルでは、エージェントは9つの部屋に区切られた環境で、以下の手順で報酬を得ます

  1. エージェントは赤色のボタンを探索する
  2. エージェントがボタンに接触すると赤色のボタンが緑色に変化。同時にゴールがランダムな位置に出現する
  3. エージェントはゴールを探索し、衝突によりゴールを突き崩す。
  4. エージェントがゴール上部の黄色のキューブに接触すると報酬をGET。
  5. 終了

pyramid1.png
pyramid2.png

報酬獲得までの流れをみると、よくある強化学習用のサンプル課題に比べて手順が多く、報酬が得るまでの道のりが長いようにみえます。
このような環境では、模倣学習を活用するのが有効そうです。模倣学習のために、まずはデモンストレーション用のデータを用意します。

デモンストレーション用データの作成

UnityのProjectタブから、Assets > ML-Agents > Examples > Pyramids > Scenes > PyramidsIL を開きます。
PyramidsILシーンでは、模倣学習用に予め教師用のエージェントを動かすための環境と、人間が操作するためのPlayerBrainが用意されているで、今回はこれを利用してデモンストレーション用データを作成します。

準備

PyramidsILシーンには、TeacherAreaPBとStudentAreaPBという2つのエージェント実行環境がありますが、今回はTeacherAreaPBのみを使用するので、StudentAreaPBはDisableにしておきましょう。
また、エージェントから見た視点をもとにしたデモンストレーション用データを生成したいので、OverviewCameraもDisableにし、代わりにTeacherAreaPBの中のAgentの子として新規にカメラを追加しておきます(位置・角度とかはエージェントから見た視点っぽくなっていればOKだと思います)。

image.png

次に、TeacherAreaPBの中のAgentに、Demonstration Recorderをアタッチし、Recordにチェックをいれ、Demonstration Nameには適当な文字列をいれておきます(ここでいれた文字列が生成されるデータのファイル名になります。今回はPyramidTeacherDemonstrationにしておきます。)

record1.png

最後に、Academyに登録されたBrainのうち、PyramidsLearningのControllをチェックを外しておきます。
image.png

これでデモンストレーション用データ作成の準備は完了です。

実際にプレイしてデモンストレーション用データを作成する

Playボタンを押すとゲームが始まります。Play中はデモンストレーションが記録されます。
今回は約30回の課題を行いました。

再度Playボタンを押し、ゲームを止めるとデモンストレーションデータがAssets > Demonstrationsに生成されます。生成されるデータのファイル名はDemonstration RecorderのDemonstration Nameに入力した文字列に対応しています。ですので今回はPyramidTeacherDemonstration.demoが生成されます。

demo1.PNG
28回のデモンストレーションを行い。平均で1.7程度の報酬を得ています。

学習する

次に、生成されたデモンストレーション用データを使ってエージェントの学習を行います。
学習用のシーンとして、既に用意されているPyramidsシーンを利用します(Assets > ML-Agents > Examples > Pyramids > Scenes > Pyramids)

準備

Pyramidsシーンを開いたら、Academyの設定を行います。BrainのPyramidsLearningのControllにチェックを入れておきましょう。Time Scaleなどの設定も適宜変更しておきます(デフォルトのままでも問題ないです)
image.png

次に、先ほど生成したPyramidTeacherDemonstration.demoを、demosフォルダ(git cloneしてきたときにできたフォルダの中にあります)にコピーします。
また、同じdemosと同じ階層にある、configファイルの中の、gail_config.yamlのうち、PyramidsLearningについての設定を以下のように書き換えます。

PyramidsLearning:
    summary_freq: 2000
    time_horizon: 128
    batch_size: 128
    buffer_size: 2048
    hidden_units: 512
    num_layers: 2
    beta: 1.0e-2
    max_steps: 5.0e4
    num_epoch: 3
    pretraining:
        demo_path: ./demos/PyramidTeacherDemonstration.demo.demo
        strength: 0.5
        steps: 10000
    reward_signals:
        extrinsic:
            strength: 1.0
            gamma: 0.99
        curiosity:
            strength: 0.02
            gamma: 0.99
            encoding_size: 256
        gail:
            strength: 0.01
            gamma: 0.99
            encoding_size: 128
            demo_path: demos/PyramidTeacherDemonstration.demo.demo

今回は、強化学習と模倣学習(GAIL)に加え、エージェントを報酬に早く導くために、デモンストレーションによるpretraining(BCののようなもの)を行い、また、curiosityによる報酬信号(エージェントの行動に対する結果の予測と実際の結果の差が大きいほど報酬がもらえる)を与えています。

準備ができたら、実際の学習を行っていきます。

学習を行う

ml-agentsディレクトリ(git cloneしてできるものです)上で、以下のコマンドを実行します。
(run-idは今回PyramidTrainingとしていますが、モデル生成時のフォルダ名になるだけなので、適当な文字列で構いません)

mlagents-learn .\config\gail_config.yaml --run-id=PyramidTraining --train

実行するとUnityエディタ上でプレイボタンを押して学習の開始を促されるので、UnityエディタのPyramidsシーン上でPlayボタンを押しましょう。
Playボタンを押すと、学習が始まります。しばらくエージェントの動きを眺めましょう。

学習結果

今回は50000ステップの学習を行いました。
2000ステップごとのMean Rewardをまとめてみました。
参考までに、デモンストレーション用データを使わない場合(GAIL+pretrainingなし)で学習させたときの結果を並べています。

result.png

GAIL + pretrainingありの場合、早い段階でRewardを獲得できており、学習が早く進んでいることが分かります。
デモのMean Rewardが1.7くらいだったことを考えると、いい線いってるのではないでしょうか

学習したエージェントの動きを確認する

上記で学習したエージェントの振る舞いを実際に確認してみます。

準備

学習が完了すると、modelsフォルダの中に学習済みのモデルデータ(.nn形式)が作成されます。
今回の場合、models > PyramidTraining > PyramidsLearning.nn がモデルデータです。
このPyramidsLearning.nnをUnityのProjectタブにD&Dします。

学習の時と同様に、Pyramidsシーンを開きます。

次に、Assets > ML-Agents > Examples > Pyramids > Brainsにある、PyramidsLearningのModelの箇所にPyramidsLearning.nnを適用します。
model1.png
これでPyramidsLearning Brainをもつエージェントは、学習済みモデルで定義された振る舞いをとるようになります。

最後に、AcademyのControllのチェックを外します。

これで準備は完了です。

確認する

ここまでの設定ができたら、Playボタンを押して動作を確認しましょう。

ちゃんとオブジェクトを探索したり、ブロックを突き崩す動作をしていますね。
もう少し学習stepを増やせばよりスマートな挙動になるかもしれません。

まとめ

強化学習だけでうまくいかない場合でも模倣学習を組み合わせて報酬までたどり着く見本を教えてやれば、早く学習してくれることがわかりました。

今まで強化学習だけで開発していた独自のプロジェクトで利用する場合も基本的に設定用のyamlを記述して設定するだけでよいので、試してみてはどうでしょうか 

参考

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