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

SkyBoxのフェード切り替え

はじめに

SkyBoxを別のテクスチャにフェードで切り替えたい。
しかし、ビルトインシェーダー内に、2つのCubeMapをブレンドして表示するようなシェーダーはなかったため、もともと入っているシェーダーを改造して作ることにする

やりかた

Unityのあらかじめ入っているシェーダのソースコードは、Unity ダウンロード アーカイブから指定バージョンのダウンロードボタンを押すとできます。「ビルトインシェーダー」を押して、ダウンロードします。今回は例として、2019.4.6を落とします
image.png

さっそく改造しようと思ったら、なんか初めからできそうなのがはいってる。。
image.png

これを使えば解決しました笑
マテリアルにCubeMapを2つ指定して、Valueを動かすとブレンドされます
image.png

その他のアプローチ

カメラを2つ使って、RenderTextureでフェードするのも手

参考

UnityのStandardシェーダーのソースコードを取得する
https://bluebirdofoz.hatenablog.com/entry/2019/07/25/093410
ビルトインシェーダガイド
http://www.mikame.net/sample/unity_documentation/Components/Built-in%20Shader%20Guide.html

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

UnityのMLAgentsでPython使って機械学習 2021年最新版

はじめに

こんにちは。陰キャ大学生です。

前置きはさておき、Pythonを使って機械学習していると、「物理エンジン使ってやってみたいなぁ」なんて思う時があります。ありません
そこで今回は、Unityのために作られた機械学習ライブラリML Agents を使って物理エンジンで機械学習してみました。
実はすでにQiita上には多くのMLAgentsの記事が上がっています。

【ML-Agents】UnityとPythonのTensorFlowをつかって機械学習させてみた(v0.11β対応)

しかし、このMLAgents、バージョンアップが頻繁なうえに、バージョン変わると関数やメソッド名も変わるというなんともQiitaライター泣かせな仕様となっています。

そこで今回は、現在(2021年2月25日)最新であるRelease13 について記事を書きます。
おそらく数か月後には古くて使い物にならない記事になると思いますが、今この瞬間にMLAgentsを使いたいんだ!!という人に届いてほしいと思います。

MLAgentsについて

こちらの記事が大変わかりやすく参考になりますので、ぜひご覧ください

Unityで体感する強化学習【ML-Agents】

環境

MLAgentsの推奨環境

  • Unity (2018.4以降)
  • Python (3.6.1以降)

今回私が使用した環境

  • Unity (2019.4.15f1)
  • Python (3.7.9) Anaconda

推奨環境を満たしていれば大丈夫だと思いますが、Unityはバージョン等によりUIが変わる可能性があるのでご容赦ください。

前準備

MLAgentsのダウンロード

こちらのサイトから、Release13をダウンロードしてください。
ダウンロードされたZIPファイルを任意の場所に展開してください。

PythonとUnityのダウンロード&インストール

参考記事がわかりやすくまとめてくださったので引用します。参照されてください。

【ML-Agents】UnityとPythonのTensorFlowをつかって機械学習させてみた(v0.11β対応)

準備

Pythonの環境構築

Pytorchのインストール

ターミナルで以下を実行します

pip install torch~=1.7.1 -f https://download.pytorch.org/whl/torch_stable.html

image.png
完了

関連ライブラリのインストール

展開したディレクトリに移動して、以下を実行

pip install -e ./ml-agents-envs
pip install -e ./ml-agents

実際、pip install mlagentsでもインストールできますが、この際は常に最新版がインストールされますので、バージョンの整合性をとるためにもダウンロードしたSetup.pyから参照することをお勧めします。

0.24.0がインストールされたことを確認してください

Unitでプロジェクト作成

Unity Hubを開いて、プロジェクトの作成をします
image.png

上のメニューから、Window>Package Managerの順に進んで、左上の+マークから、Add Package from disk...を選択して、<展開したフォルダ>/com.unity.ml-agents/package.jsonを選択します。
image.png

インポートが始まり、In ProjectML Agents 1.8.0があれば大丈夫です。
image.png

今回は、箱をボールが追いかけるAIを作成します。(詳しくは記事の最後を見てね)
Unityの詳しい説明等はここでは省略します。

ステージの作成

GameObject>3D Object>Planeを選択し、Stageに改名、Positionを(0, 0, 0)Scaleを(1, 1, 1)に設定
image.png

箱の設定

GameObject>3D Object>Cubeを選択し、Targetに改名、Positionを(3, 0.5, 3)Scaleを(1, 1, 1)に設定
image.png

ボールの設定

GameObject>3D Object>Sphereを選択し、RollerAgentに改名、Positionを(0, 0.5, 0)Scaleを(1, 1, 1)に設定

Inspector下部のAdd Componentから、Physics>Rigidbodyを追加 ?これめちゃ大事です!!!
image.png

後で複製できるように、グループ化しておきます。
GameObject>Create Emptyを選択し、Stage、Target、RollerAgentをドラッグしてグループ化します名前は何でもいいです。
image.png

ボールのスクリプト作成

UnityのProject内にScriptsフォルダを作り、その中にRollerAgent.csを作成します
image.png

中身をお好きなエディタで以下のように書き換えてください。
解説は適宜コメントで挿入してあるので見てみてくださいね。

RollerAgent.cs
// 使用ライブラリのインポート
using System.Collections.Generic;
using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Sensors;
using Unity.MLAgents.Actuators;

// Agentクラスを継承し、必要なところだけ書き換えていきます
public class RollerAgent:Agent
{
    Rigidbody rBody;
    // スタートしたときに呼び出される関数
    void Start () {
        // ボールの物体を変数に格納
        rBody = GetComponent<Rigidbody>();
    }

    // Targetをグローバルに宣言
    public Transform Target;
    // エピソード(学習のステップ)が始まった時に呼び出される関数
    public override void OnEpisodeBegin()
    {
        // ボールのY座標が0=落下したとき
        if (this.transform.localPosition.y < 0)
        {
            // 初期化
            this.rBody.angularVelocity = Vector3.zero;
            this.rBody.velocity = Vector3.zero;
            this.transform.localPosition = new Vector3( 0, 0.5f, 0);
        }

        // ターゲットをランダムな位置に
        Target.localPosition = new Vector3(Random.value * 8 - 4,
                                            0.5f,
                                            Random.value * 8 - 4);
    }

    // 観測データ(この場合で言うと学習に必要な数値)の取得
    public override void CollectObservations(VectorSensor sensor)
    {
        // ボールと箱の座標(x, y, z) x 2
        sensor.AddObservation(Target.localPosition);
        sensor.AddObservation(this.transform.localPosition);

        // ボールのスピード(x, z)
        sensor.AddObservation(rBody.velocity.x);
        sensor.AddObservation(rBody.velocity.z);

        // トータルで入力次元は8次元になる
    }

    // 力を加えるときの乗数
    public float forceMultiplier = 10;
    // アクションが起きたときに呼び出される関数
    public override void OnActionReceived(ActionBuffers actionBuffers)
    {
        // XとZ軸の入力に合わせてボールに力を加える
        Vector3 controlSignal = Vector3.zero;
        controlSignal.x = actionBuffers.ContinuousActions[0];
        controlSignal.z = actionBuffers.ContinuousActions[1];
        rBody.AddForce(controlSignal * forceMultiplier);

        // 箱とボールの距離
        float distanceToTarget = Vector3.Distance(this.transform.localPosition, Target.localPosition);

        // 箱にボールが到達したとき
        if (distanceToTarget < 1.42f)
        {
            // 報酬を1に設定
            SetReward(1.0f);
            // エピソードを終了
            EndEpisode();
        }

        // ボールが落下したとき
        else if (this.transform.localPosition.y < 0)
        {
            // エピソードを終了
            EndEpisode();
        }
    }

    // 手で動かす際の設定
    public override void Heuristic(in ActionBuffers actionsOut)
    {
        var continuousActionsOut = actionsOut.ContinuousActions;
        continuousActionsOut[0] = Input.GetAxis("Horizontal");
        continuousActionsOut[1] = Input.GetAxis("Vertical");

    }
}

ファイルのアタッチ

作成したスクリプトをRollerAgentにドラッグします。

RollerAgent (Script)Targetに、Target(ゲームオブジェクト)をドラッグ

Add Componentから、ML Agents>Behavior Parametersを追加
Vector Observation>Space Size8 に変更
Actions>Continuous Actions2 に変更
Behavior nameRollerBall に変更

Add Componentから、ML Agents>Decision Requesterを追加
Decision Period10 に変更

最後はこんな感じになります
image.png

テスト

Behavior ParametersBehavior TypeHeuristic Onlyに変更

中央上部のプレイボタンを押して、矢印キーで遊べるか確認してみてください
test.gif

学習

<展開したディレクトリ>/config/rollerball.yamlを作成して、以下を書き込みます

rollerball.yaml
behaviors:
  RollerBall:
    trainer_type: ppo
    hyperparameters:
      batch_size: 10
      buffer_size: 100
      learning_rate: 3.0e-4
      beta: 5.0e-4
      epsilon: 0.2
      lambd: 0.99
      num_epoch: 3
      learning_rate_schedule: linear
    network_settings:
      normalize: false
      hidden_units: 128
      num_layers: 2
    reward_signals:
      extrinsic:
        gamma: 0.99
        strength: 1.0
    max_steps: 500000
    time_horizon: 64
    summary_freq: 1000

Behavior ParametersBehavior TypeDefaultに変更

展開したディレクトリに移動して、以下を実行

mlagents-learn config/rollerball.yaml --run-id=RollerBall

image.png

Listening on port 5004. Start training by pressing the Play button in the Unity Editor.

この文言がでたら、Unityに戻って、プレイボタンを押すと、学習が始まります。
最初のうちはボールがオロオロ動くのがなかなかに可愛いです笑

学習並列化

物理エンジンのいいところは、モデルを複製して同時に学習を進められるところです。
先ほどグループ化したものをプレハブ化し、複製してみましょう。(特にコード等変える必要はありません)
image.png

さて、改めて学習してみましょう。
test2.gif

ログはこのようになります。Mean Rewardが1に近づけばうまく学習が行われています。
終了したいときは、CTRL+Cで止められます。その時のモデルデータを自動的に保存してくれます。

image.png

また、バックボーンはTensorFlowなので、別ターミナルでコマンドを実行することで、TensorBoardも確認することができます。

tensorboard --logdir results --port 6006

image.png

学習モデルでテスト

<展開したディレクトリ>/results/RollerBall/RollerBall.onnxを、Unityプロジェクト内のAssetsにドロップしたのちに、RollerAgentのInspector内ののBehavior Parameters>Modelにドロップ。

プレイボタンを押すと、学習されたモデルでボールが動く様子を見ることができます。
床から落ちることなく箱を一生懸命追っている姿が確認できますね。可愛い。

test3.gif

最後に

たびたびバージョンの変わってしまうMLAgentsですが、使い方によっては機械学習の世界が広がると思います。
僕も早くコマンドラインで学習できるようにならないかなぁ

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

Live2DモデルをAnimatorを使わずにスクリプトでアニメーションさせる

Animatorを使わずにスクリプトでアニメーションさせる方法です。
まずLive2D.Cubism.Coreをインポートします

追加インポート
using Live2D.Cubism.Core;

次にアニメーションを管理するClassを宣言します。

actionclip
    public class actionclip
    {
        string ease = "Linear";
        public float startValue = 0;
        public float endValue = 0;
        public float duration = 0;
        public float delay = 0;

        private float currentDurationTime = 0;
        private float currentDelayTime = 0;

        public actionclip(float StartValue, float EndValue, float Duration, float NextDelay = 0, string Ease = "Linear")
        {
            startValue = StartValue;
            endValue = EndValue;
            duration = Duration < 0 ? 0 : Duration;
            delay = NextDelay < 0 ? 0 : NextDelay;
            ease = Ease;

            currentDurationTime = currentDelayTime = 0;
        }

        public void Reflesh()
        {
            currentDurationTime = currentDelayTime = 0;
        }

        public float Value
        {
            get
            {
                float p = endValue;
                if (currentDurationTime <= duration)
                {
                    currentDurationTime +=  Time.deltaTime;
                    float t = 1.0f;
                    if (duration > 0)
                    {
                        t = currentDurationTime / duration;
                    }
                    else
                    {
                        t = 1.0f;
                    }
                    t = t > 1.0f ? 1.0f : t;
                    switch (ease.ToUpper())
                    {
                        case "LINEAR":
                            break;
                        case "EASEIN":
                            t = Calc.EasyEaseIn(t);
                            break;
                        case "EASEOUT":
                            t = Calc.EasyEaseOut(t);
                            break;
                        case "EASEINOUT":
                            t = Calc.EasyEaseInOut(t);
                            break;
                    }
                    p = Mathf.Lerp(startValue, endValue, t);

                }
                else
                {
                    currentDelayTime += Time.deltaTime;
                }
                return p;
            }
        }
        public bool isAnimationEnd
        {
            get
            {
                return (Value == endValue && currentDelayTime > delay);
            }
        }
    }

Valueを呼び出すごとに開始値から終了値までの遷移値を取り出し、終了値になったらDelay時間待つだけのスクリプトです。前回の簡単イージングはここで使っています。
https://qiita.com/RYUMAGE/items/0351af94eff77dc32187

そしてLive2Dのパラメータを管理するクラスを宣言します

param
    public class param
    {
        public CubismParameter dat;
        //Animation
        private actionclip[] animations;
        public bool isRepeat = false;

        private int currentAnimCnt = 0;
        private float startValue = 0;

        public string ID
        {
            get
            {
                return dat.Id;
            }
        }

        public void SetAnimation(actionclip[] Animations, bool Repeat = false)
        {
            currentAnimCnt = 0;
            animations = Animations;
            isRepeat = Repeat;

            startValue = dat.Value;
        }
        public void StopAnimation()
        {
            isRepeat = false;
            animations = null;
        }

        public bool isRunning
        {
            get
            {
                return animations != null;
            }
        }

        public void Update()
        {
            if (animations != null)
            {
                dat.Value = animations[currentAnimCnt].Value;
                if (animations[currentAnimCnt].isAnimationEnd)
                {
                    animations[currentAnimCnt].Reflesh();
                    currentAnimCnt++;
                    if (currentAnimCnt >= animations.Length)
                    {
                        if (isRepeat)
                        {
                            currentAnimCnt = 0;
                        }
                        else
                        {
                            StopAnimation();
                        }
                    }
                }
            }
        }
    }

最後にLive2Dモデルデータのパラメーターをこのクラス内のdatに参照させます。
さらにLateUpdate()内でparamをUpdateさせます。

LateUpdate
    private param[] Live2DParams;
    private CubismModel Model;

    public void InitializeParameters(CubismModel model)
    {
        Model = model;
        Live2DParams = new param[model.Parameters.Length];

        for (int i = 0; i < model.Parameters.Length; i++)
        {
            Live2DParams[i] = new param();
            Live2DParams[i].dat = model.Parameters[i];
        }
    }

    void LateUpdate()
    {
        for (int i = 0; i < Live2DParams.Length; i++)
        {
            Live2DParams[i].Update();
        }
    }

こうやってMistyLink TouchableではAnimatorに寄らないスクリプトによるリアルタイムアニメーションをやっていました。

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

簡単イージング

なぜかUnityにはイージングを制御する関数がありません。
なので自分用にイージングを極力簡略化したスクリプトを組みました。

本来イージングには開始値、終了値、経過時間、トータル時間の4つが必要ですが、実際イージングは比率しか求めないので開始位置と終了位置は無視して経過時間/トータル時間のみを渡して計算してます。

このスクリプトは0~1までの値を渡して、その値にイージング処理した値を返すものです。

たとえばLerpのt値にこれらの処理を加えることでイージング効果がでます。

Calc.cs
    public static float EasyEaseIn(float t)
    {
        return   t * t;
    }
    public static float EasyEaseOut(float t)
    {
        return -1 * t * (t - 2.0f);
    }
    public static float EasyEaseInOut(float t)
    {
        t = t *0.5f;
        if (t < 1) { return 0.5f * t * t; }
        t = t - 1;
        return -0.5f * (t * (t - 2.0f) - 1) ;
    }

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

突然Unity Recorderで録画できなくなった時の対処法

設定を変更していないのに録画できなくなった

Unity Recorderの設定をまったく変えてないのに、
突然録画されなくなったのです。
※もしかしたら何か手が触れて設定が変わっちゃったのかもしれない

出力されたアニメーションGIFが真っ白の状態。

タイミング的な何かが重なって奇跡的に
今まで動いていたという可能性もあります。


?設定はコチラ?

複数カメラを使った録画

gif_animation_065.gif
?このようにUIと3Dオブジェクトを同時に表示させる必要がありましたので、
UIと3Dの2つのカメラを使った録画でした。


?上の通りCamera設定をActiveCameraに設定しています。

結論: Include UIのチェックを外す


今回の解決方法は「Include UIのチェックを外す」でした。

Include UIとは?

Include UIについて調査できておりません。

To include UI GameObjects in the recording.

録画時にuGUIを表示する際は必須なのかと
思っていましたがそうでもなさそうです。
(チェックしなくても録画できるため)

最後に

gif_animation_168.gif

現在執筆中の「DOTweenの教科書」が佳境で時間がなく
本質的な原因は突き止めておりません。

Include UIについては後日調査予定。

開発環境

  • Unity2019.4.15f1
  • Unity Recorder 2.5.2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む