- 投稿日:2019-08-22T23:59:14+09:00
【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."); }当たり前と言えば当たり前なんですが、私はこれで躓いてしばらく悩みました…。
- 投稿日:2019-08-22T23:59:13+09:00
【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のチェックをオフにすると単発再生ができて、途中でオフにすると止まります。
ReorderableListを使っているのでなかなかの使い心地です。
- 投稿日:2019-08-22T23:41:36+09:00
夏休みの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日でできた!ありがたい?✨✨かわいい?
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
作成したキャラを動かしてみたい
参考サイト
qiita/ガチ初心者がMacでVRoidモデルをMMDモデルに変換して動かそうとして1週間とかした
- VRoid Hub
- Unity
- nanoem
MMD動かすの憧れてたけど動いた✨けど、なんか、すごく難しい...。
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の使い方教えてあげた?洋服かわいいのできてた⭐️おわりに
VRoidに毎日さわるのを意識して取り組んで、でもやっぱり3Dは苦手分野で多少グロッキーになりつつも、いい感じになっていくのすごい楽しかったです?✨
- 投稿日:2019-08-22T19:47:53+09:00
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)
- VSCodeが認識できてない.NET Frameworkのバージョンをメモ
.NET Frameworkのダウンロードページから、メモしたバージョンのDeveloper Packをインストールする(私の場合は4.7.1)
UnityエディタとVSCodeを再起動
これで直りました。
参考
- 投稿日:2019-08-22T18:57:29+09:00
ドットインストールのUnity入門の補足説明
この記事は、筆者が大学でUnityを教えるにあたって、事前準備として夏休みの宿題として学生にドットインストールのUnity入門を学習させるための補足説明です。
学生用に書いたものですが他にも役に立つ人がいるかもしれないので公開します。
この記事の目的
ドットインストールのレッスンを否定している訳ではなく、夏休みの宿題用の素晴らしい教材としてありがたく使わせていただいています。その上で、本当に何も知らない学生にとっていきなり出てくる様々な概念や用語について、さらに詳しく説明することでより理解度を深めることを目的としています。
この記事の対象者
この記事はUnity入門のためにドットインストールで学ぶ、プログラミング初心者を対象にしています。
目次
この記事には以下のような構成でUnity入門について解説しています。カッコの中の#11などはドットインストールのレッスンの中で対応する動画の番号を示しています。
- まずは一度レッスンを全部通してやってみる
- スクリプトおよび GameObject の関連付けについて (#11)
- スクリプトを作成すると自動的に挿入されているコードについて (#11)
- 型についての補足説明 (#12)
- クラスについての補足説明 (#12)
- #12のスクリプトの補足説明 (#12)
- #14のスクリプトの補足説明 (#14)
- プレハブ(Prefab)って何? (#16)
- なぜ空のGameObjectを作成するのか? (#16)
- RigidBodyって何? (#19)
- OnCollisionEnterって何? (#19)
1. まずは一度レッスンを全部通してやってみる
まずは頭の中に?がいっぱいになったとしてもとりあえず、言われるがままにレッスンを進めて最後までやってみてください。数時間で終わると思います。その後この記事を見ながら2周目をやるとかなり理解が深まると思います。
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.html3. スクリプトを作成すると自動的に挿入されているコードについて (#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.Collections、System.Collections.Generic、UnityEngineという名前空間を利用するということを宣言しています。「名前空間」という変な名前ですが、スクリプトを書いていると「Ball」とか「BallFactory」とか「speed」とかクラスや変数にいろんな名前を付けます。自分で適当に付けた名前と、Unityに標準で用意されている名前と被ってしまっても大丈夫なように、さまざまな機能が名前空間で分けられているのでちゃんと区別できるようになっています。なのでこの名前が被らないようにするという意味で「名前空間」(name space)という名前が付いています。
UnityEngine
Unityで標準装備されている基本的なC#の部品がこの中にまとまっています。
以下のUnity公式スクリプトリファレンスにアクセスしてください。https://docs.unity3d.com/ja/current/ScriptReference/index.html
左側の「UnityEngine」の「+」をクリックして開いて、
その中にある「Classes」の「+」をクリックすると UnityEngine の中にあるたくさんのC#の部品を確認することができます。
このようにUnityには大量の便利な機能(クラス)が用意されています。ただその全てを覚えることはできないので、分からないことがあれば、ますスクリプトリファレンスを見るようにしてください。
セミコロン
;(セミコロン)はC#の命令の区切りです。それぞれの命令の最後には必ず;が必要です。これを書き忘れるとエラーになるので、スクリプトを実行したときにエラーが発生したときはまず;を忘れてないか確認してみましょう。コメント
// Start is called before the first frame update // Update is called once per frameスクリプトを書くときにちょっとしたメモを書いたり、書いたスクリプトを部分的に無効にしたいといった場合には、「コメント」を使います。Unityが生成するスクリプトにも上記のような解説がコメントとして記述されています。
コメントを書くには//と記述するとその行の//以降の文字はコメントとなり、スクリプトを実行するときに無視されるようになります。//は1行ずつしかコメントにできないので、複数行まとめてコメントにしたい場合は/*と書くと、そこから*/と記述するまでがコメントになります。// 1行コメント /* 複数行の コメント です */クラス
PaddleScript.cspublic class PaddleScript : MonoBehaviour { // クラスの中身 }この行では
PaddleScriptというクラスを宣言しています。クラスはスクリプトで使うさまざまな部品のことで、あらかじめさまざまなC#標準のクラスやUnity標準のクラスが用意されており、ここで宣言しているように、自分で作ることもできます。後ろの
: MonoBehaviourの部分は、このクラスはUnityで標準装備されているMonoBehaviourというクラスの機能を継承するということを指定しています。機能を継承することで、もともとのMonoBehaviourが持っている機能にさらに自分で機能を追加した新しいクラスを作ることができます。
MonoBehaviourはUnityEngineという名前空間で定義されているので、これを利用するには1行目のusing UnityEngine;が必要になります。宣言したクラスの中身のスクリプトはすべてここに書かれた中カッコ{から最後の中カッコ}の内側に記述します。メソッド
PaddleScript.csvoid Start() { // メソッドの中身 }ここではクラス宣言の内側に
Startというメソッド(関数、ファンクションとも言う)を宣言しています。メソッドはクラスの持つ機能のことで、自由に名前をつけていくつでも作ることができます。ここで宣言しているStartメソッドはもともと継承元のMonoBehaviourクラスで宣言されていて、紐づけられた GameObject が作成された時に一度だけ実行されます。ここではその
Startメソッドを上書きすることで、GameObject が作成されたときに実行したい独自の処理を記述することができます。メソッドの中身のスクリプトは、ここにかかれた中カッコ{からメソッドの終わりの中カッコ}の内側に記述します。メソッドは何らかの処理を実行した後に、結果を返すことができますが、ここにある
voidは結果を何も返さないということを示しています。Updateメソッド
PaddleScript.csvoid Update() { // メソッドの中身 }ここは
Startメソッドと同様に継承元のMonoBehaviourクラスで宣言されているUpdateというメソッドを上書きしています。Updateメソッドは 紐付けられた GameObject がシーンに配置されている間、画面が更新される度に実行されます。通常だと1秒間に60回ほど実行されます。インデントについて
PaddleScript.cspublic 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/3c2e23bb77cc9085bfda4. 変数と型についての補足説明(#12)
変数について
プログラミングをする上でとても基本的な概念に「変数」があります。数学で出てくるx, yといった変数とは少し違います。プログラミングでは、数値や文字列など様々な値を扱いますが、そういう値を保持しておく箱のようなものがプログラミングにおける変数です。変数について簡単にまとめると以下のような特徴があります。
- 変数は値を保存したり読み出したりできる箱のようなもの
- 変数と言っても数値だけだはなく文字列やオブジェクトなどなんでも保存できる
- 変数には自由な名前をつけることができる
- 変数を作るには、宣言をする必要がある
- 変数には型という概念があり、宣言するときに型の指定が必要
- 変数に値を保存することを「代入」という
変数を宣言するには、型の後に変数名を記述します。
int x;この例では
xという名前でintという型の変数を宣言しています。(intは整数値を扱う型)
変数に値を保存することを「代入」と言いますが、値を代入するには=を使います。int x; x = 2;この例では1行目で
int型の変数xを宣言して、2行目でxに2という値を代入しています。
値の代入は=の右側に記述された値が、左側に記述された変数に保存されます。必ず右→左です。左→右はありませんので注意してください。また、変数の宣言と同時に値を代入して初期化することもできます。その場合は以下のように記述します。
int x = 2;また、変数に値を代入する場合の
=の左側に変数を使うこともできます。
以下の例では変数xに2を代入し、変数yにx + 1の計算結果を代入しているので、yの中身は3になります。int x = 2; int y; y = x + 1;
0fのfって何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);C#のスクリプトでは整数値や小数値、文字列などいろいろな値を扱います。それらはの値の種類を「型」と言います。
例えば整数を扱うにはintという型を利用し、小数を扱うには精度の高いdoubleとその半分の精度のfloatといった種類の型があります。
C#では0や1のような整数の値は自動的に int として扱われ、0.0や0.5のように記述した値は自動的にdoubleとして扱われます。0fや0.5fのように数値の後にfを付けるとその数値はdoubleではなくfloatとして扱われるようになります。ここで使われている
Vector3はdoubleではなくfloatを扱うクラスなので、0ではなく0fとする必要があります。(ドットインストールでは浮動小数点数を渡す決まりになっているのでfを付けると解説されていますが、実際には浮動小数点数を渡すからfを付けるのではなく、 浮動小数点数の中でもdoubleではなくてfloatを渡す決まりになっているのでfを付ける必要があるということになります)5. クラスについての補足説明 (#12)
Unityで書く1つのスクリプトは基本的に1つのクラスになります。クラスはスクリプトで扱うオブジェクトの型の種類の1つで、クラスを作ることで型を自分でつくることができます。クラスを作るとその型のオブジェクトをいくつでも作ることができ、そのオブジェクトのことを「インスタンス」と言います。
クラス(型)はオブジェクトの設計図のようなもので、それを元に実際に生成されたオブジェクトがインスタンスです。
いきなり出てくる
transformって何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);ここで書いている
PaddleScriptはMonoBehaviourというクラスの持つメソッド(機能)や変数を全て継承して、それに独自の機能を新たに追加しているクラスです。transformはMonoBehaviourクラスが持つ変数の1つで位置情報や変形などに関する情報がまとめて格納されています。Unity公式スクリプトリファレンスに書いてありますが
MonoBehaviourクラスはtransformという変数を持つと書いてあります。なので、MonoBehaviourを継承しているPaddleScriptも変数transformを持つことになります。
MonoBehaviourクラスのリファレンス(継承メンバーの変数のところにtransformの記述がある)
https://docs.unity3d.com/ja/current/ScriptReference/MonoBehaviour.htmlそしてそこをクリックすると
transformはTransformというクラスの変数だということが分かります。
transform.positionって何?PaddleScript.cstransform.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を付け加えると以下のような相関関係になります。
Vector3って何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);
Vector3はx,y,zの3つのfloatの値をまとめて扱えるので3D空間での位置情報を扱うのに便利なオブジェクトです。transform.positionの型は Vector3 なので、ここでは Vector3 を使う必要があります。
(Vector3はクラスではなく構造体というものですが、今さらに新たな概念を理解すると大変だし、ほぼクラスと同じように使えるので今はほぼクラスみたいなものだと思ってもらって大丈夫です。)
Vector3の公式リファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Vector3.html
newって何?PaddleScript.cstransform.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.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);
transofrm.positionの後に+=、new Vector3が続いていますが、これは、PaddleScriptクラスのインスタンスが持っている変数transformのpositionに=の右側で新しく作られるVector3クラスのインスタンスを足しているということになります。
transform.positionもnew Vector3で作られるオブジェクトもどちらもVector3のインスタンスで、それぞれのインスタンスの持つ x, y, z の変数が足し合わされた結果がtransform.positionに保存されます。6. その他スクリプトの補足説明(#12)
Inputって何?PaddleScript.csvoid Update() { transform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime * speed, 0f, 0f); }
InputはUnityで、キーボードやマウス、タッチディスプレイ、ゲームパッド、ジョイスティックなどのユーザーのさまざまな入力を取得することができるクラスです。UnityのInputで入力を扱う - Qiita
https://qiita.com/yando/items/c406690c9ad87ecfc8e5Unity公式マニュアル - 一般的なゲーム入力
https://docs.unity3d.com/ja/current/Manual/ConventionalGameInput.html
Inputのスクリプトリファレンス
https://docs.unity3d.com/ja/current/ScriptReference/Input.html
Input.GetAxis("Horizontal")って何?PaddleScript.csvoid 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.csvoid Update() { float horizontalAxis = Input.GetAxis("Horizontal"); float x = horizontalAxis * Time.deltaTime * speed; transform.position += new Vector3(x, 0f, 0f); Debug.Log(horizontalAxis); }まず、
float型の変数horizontalAxisにInput.GetAxis("Horizontal")で取得したゲームコントローラーの状態を取得しています。キーボードのキーを何も押していないときには、変数horizontalAxisには0fが入り、左キーを押しているときは-1fが、右キーを押しているときは1fが入ります。次の行では、それに
Time.deltaTimeとspeedをかけた値をfloat型の変数xに代入しています。その次の行では
new Vector3でコンストラクタのパラメータにxを渡しています。これらを1行で書いていましたが、こうやって分けて書くとよりわかりやすいと思います。次の行の
Debug.Log(horizontalAxis);をすることで、実行した時にキーボードの状態に応じてhorizontalAxisの値が変化することがコンソールで確認できると思います。(この行は確認した後は不要なので消しましょう)キーボードはゲームコントローラーがない場合にコントローラーの代わりに使うことができるので、
Input.GetAxis("Horizontal")で矢印キーの状態が取得できます。
Input.GetAxis("Horizontal")を使っておけば、USBのゲームコントローラーなどUnityが対応するさまざまなコントローラーを接続した時にプログラムを書き変えることなく、そのままプレイできます。
Time.deltaTimeって何?PaddleScript.cstransform.position += new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0f, 0f);
Time.deltaTimeは、前回のフレーム(前回Updateメソッドが実行されたタイミング)からの経過時間(秒)を取得することができます。Updateメソッドは必ずしも一定間隔で実行されるわけではありません。コンピュータの負荷によって遅くなったり早くなったりします。例えば1フレームごとに1ピクセルずつ動くプログラムを書いた場合、フレームの間隔が安定して0.1秒だとすると1秒間に10回実行され、10ピクセル動くことになりますが、もしコンピュータの負荷によりフレームの実行間隔が遅くなり、1秒間に5回しか実行されなければ、5ピクセルしか動かないことになります。
このようにフレームごとに一定の値だけ動かすといったスクリプトを書くとフレームの実行間隔により見た目のスピードは一定ではなくなります。なので、フレームの実行回数によりオブジェクトを動かすのではなく、経過時間によりオブジェクトを動かす方が正しくスムーズにオブジェクトを動かすことができます。そのために
Time.deltaTimeを利用します。Unity公式マニュアル - タイムとフレームレートの管理
https://docs.unity3d.com/ja/current/Manual/TimeFrameManagement.html7. #14のスクリプトの補足説明 (#14)
変数
speedはなぜその場所に記述するのか?PaddleScript.cspublic class PaddleScript : MonoBehaviour { private float speed; // Start is called before the first frame update void Start() { speed = 5f; }クラスの記述の決まりとして、
classの宣言の後に中カッコがあり、その中にメソッドを記述しますが、class宣言の中にメソッドと同列で変数を宣言すると、その変数は「インスタンス変数」としてそのクラスのインスタンスが持つ変数になります。ドットインストールの例で言うと、Ball の数だけそれぞれがこのspeedという変数を持っていて、Start()でランダムな値が設定されるので、インスタンスごとに個別の値を保持しています。変数のスコープ(有効範囲)について
変数にはスコープ(有効範囲)というものがあり、宣言する場所によってスコープが変わります。ここでは、クラス宣言の内側で変数
speedを宣言しています。こうすることで、このクラスのインスタンスが存在する限り、変数speedも存在します。PaddleScript.cspublic class PaddleScript : MonoBehaviour { private float speed; void Start() { speed = 5f; } }メソッドの中で変数を宣言することもできます。ただし、メソッドの中で宣言した変数はそのメソッドの実行が終了した時点で破棄されてしまうので、メソッドの実行時に一時的に使いたいといった場合にメソッド内で変数を宣言します。
PaddleScript.cspublic class PaddleScript : MonoBehaviour { void Start() { float speed; speed = 5f; // Start()メソッドはここまでなので、ここで変数 speed は破棄される } void Update() { // ここでは speed 変数は存在しない } }
privateって何?PaddleScript.cspublic class PaddleScript : MonoBehaviour { private float speed;クラスの持つインスタンス変数は、そのクラスの外側からアクセス可能にするか、そのクラス内でのみアクセスできるようにするかを設定できます。変数宣言の前に private と付けるとその変数にはクラス内からしかアクセスできないようになります。private の代わりに public とすると、その変数には外部からアクセスできるようになり、クラスの外部(他のクラスやUnityのInspectorなど)から値を参照したり変更したりすることができるようになります。
8. プレハブ(Prefab)って何? (#16)
ドットインストールではさらっと登場して使われているプレハブについて、とても重要な概念なのでもう少し説明しておきます。
プレハブは、同じような GameObject をたくさん配置して、それぞれを個別に動かしたいような場合のテンプレートのような機能です。スクリプトで使うクラスと似た概念です。
Ballのプレハブをドラッグ&ドロップでシーンにたくさん配置するとヒエラルキービューでは青く表示されます。これはこのオブジェクトがプレハブから作られたことを意味します。
これらの球体にまとめて変更を加えたければ、元のプレハブのScaleを変えればシーンに配置した全てのオブジェクトに反映されます。また、色を変えたければ、元のプレハブにマテリアルを適用することで全てのオブジェクトに反映されます。
ただ、これらのオブジェクトは個別にそれぞれの設定をすることも出来て、個別のオブジェクトのプロパティをプレハブのデフォルトの値から変更すると、それらは元のプレハブの設定を上書きした個別の値を持つことができます。
また、ドットインストールでやっているようにスクリプトからプレハブを元にオブジェクトをシーンに追加することもできます。
Unity公式マニュアル - プレハブ
https://docs.unity3d.com/ja/2018.1/Manual/Prefabs.html9. なぜ空の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 にさまざまな挙動をさせることができます。
Unity公式マニュアル - RigidBody
https://docs.unity3d.com/ja/current/Manual/class-Rigidbody.html11.
OnCollisionEnterって何?(#19)BallScript.csprivate 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にある以下の素晴らしい入門記事を読むと良いと思います。
- 投稿日:2019-08-22T13:57:08+09:00
[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も極力抑えられるはずです
- 投稿日:2019-08-22T13:21:05+09:00
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つの部屋に区切られた環境で、以下の手順で報酬を得ます
- エージェントは赤色のボタンを探索する
- エージェントがボタンに接触すると赤色のボタンが緑色に変化。同時にゴールがランダムな位置に出現する
- エージェントはゴールを探索し、衝突によりゴールを突き崩す。
- エージェントがゴール上部の黄色のキューブに接触すると報酬をGET。
- 終了
報酬獲得までの流れをみると、よくある強化学習用のサンプル課題に比べて手順が多く、報酬が得るまでの道のりが長いようにみえます。
このような環境では、模倣学習を活用するのが有効そうです。模倣学習のために、まずはデモンストレーション用のデータを用意します。デモンストレーション用データの作成
UnityのProjectタブから、Assets > ML-Agents > Examples > Pyramids > Scenes > PyramidsIL を開きます。
PyramidsILシーンでは、模倣学習用に予め教師用のエージェントを動かすための環境と、人間が操作するためのPlayerBrainが用意されているで、今回はこれを利用してデモンストレーション用データを作成します。準備
PyramidsILシーンには、TeacherAreaPBとStudentAreaPBという2つのエージェント実行環境がありますが、今回はTeacherAreaPBのみを使用するので、StudentAreaPBはDisableにしておきましょう。
また、エージェントから見た視点をもとにしたデモンストレーション用データを生成したいので、OverviewCameraもDisableにし、代わりにTeacherAreaPBの中のAgentの子として新規にカメラを追加しておきます(位置・角度とかはエージェントから見た視点っぽくなっていればOKだと思います)。次に、TeacherAreaPBの中のAgentに、Demonstration Recorderをアタッチし、Recordにチェックをいれ、Demonstration Nameには適当な文字列をいれておきます(ここでいれた文字列が生成されるデータのファイル名になります。今回はPyramidTeacherDemonstrationにしておきます。)
最後に、Academyに登録されたBrainのうち、PyramidsLearningのControllをチェックを外しておきます。
これでデモンストレーション用データ作成の準備は完了です。
実際にプレイしてデモンストレーション用データを作成する
Playボタンを押すとゲームが始まります。Play中はデモンストレーションが記録されます。
今回は約30回の課題を行いました。再度Playボタンを押し、ゲームを止めるとデモンストレーションデータがAssets > Demonstrationsに生成されます。生成されるデータのファイル名はDemonstration RecorderのDemonstration Nameに入力した文字列に対応しています。ですので今回はPyramidTeacherDemonstration.demoが生成されます。
28回のデモンストレーションを行い。平均で1.7程度の報酬を得ています。学習する
次に、生成されたデモンストレーション用データを使ってエージェントの学習を行います。
学習用のシーンとして、既に用意されているPyramidsシーンを利用します(Assets > ML-Agents > Examples > Pyramids > Scenes > Pyramids)準備
Pyramidsシーンを開いたら、Academyの設定を行います。BrainのPyramidsLearningのControllにチェックを入れておきましょう。Time Scaleなどの設定も適宜変更しておきます(デフォルトのままでも問題ないです)
次に、先ほど生成した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なし)で学習させたときの結果を並べています。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を適用します。
これでPyramidsLearning Brainをもつエージェントは、学習済みモデルで定義された振る舞いをとるようになります。最後に、AcademyのControllのチェックを外します。
これで準備は完了です。
確認する
ここまでの設定ができたら、Playボタンを押して動作を確認しましょう。
ML-AgentsのPyramidサンプルを強化学習+模倣学習で学習(50000 steps) pic.twitter.com/zO58rknh2n
— らうじぃスタァライトXV4DJ:漆黒のヴィランズ Grace note (@Rauziii) August 22, 2019ちゃんとオブジェクトを探索したり、ブロックを突き崩す動作をしていますね。
もう少し学習stepを増やせばよりスマートな挙動になるかもしれません。まとめ
強化学習だけでうまくいかない場合でも模倣学習を組み合わせて報酬までたどり着く見本を教えてやれば、早く学習してくれることがわかりました。
今まで強化学習だけで開発していた独自のプロジェクトで利用する場合も基本的に設定用のyamlを記述して設定するだけでよいので、試してみてはどうでしょうか
参考




























