はじめに
本記事では、VRMフォーマットで作成されたキャラクターを、キーボード操作で動かせるようになるまでに必要なことなどをつらつらっとメモっています。
さらに、NavMesh
使って、自動巡回できるようにもしたい。
前提とする環境
- Windows 環境:Windows 10/11
- Linux 環境:Ubuntu 20.04 LTS
- Mac 環境
- Intel
- macOS Catalina ver.10.15.7 以降
- Applesilicon
- Intel
キャラクタの3Dモデル
3Dモデル(VRM ファイル)は自作するか、入手しておく必要があります。
入手先としては、この辺りが参考になるかも。
※VRoid Hubは、3Dキャラクターのためのプラットフォームです。
ステップ
- Unityを起動する
- UniVRMをダウンロードする
- UnityにUniVRM unitypackageを Import する
- VRMファイルをインポートして、変換する
- キャラクタ操作スクリプトの設計をする
- AnimationControllerを作成する
- Unityスクリプトを作成する
- キャラクタに諸々アタッチして動作チェック
VRMファイルをインポートして、変換する
Unity の Scenes 直下に、Model
フォルダを作成して、VRMファイルを D&D します。
(上図はmitone.vrmというファイルを配置したもの)
miton
をクリックして、Inspectorビューを開き、Migrate To Vm1 をチェックし、Apply
をクリック。
成功すると、キャラクタが見えるようになる!
キャラクタ操作スクリプトの設計をする
キャラクタはキーボードで操作します。
操作方法は、以下です。
- 上下キー
- 前進、後進する
- 左右キー
- 方向を変更する
- スペースキー
- ジャンプする
プログラムの処理フローは以下の流れです。
キーボード入力は、Unityスクリプトがモニタリングします。そして、入力されたキー内容に応じて AnimationControllerの状態遷移とキャラクタの動き(Motion)を切り替えます。
speed_v、speed_h、jumpはそれぞれ、Animation Controllerへの入力パラメータです。
- speed_v(-1〜1の値)
- 上下キー操作の結果:下は負の値、上は正の値
- speed_h(-1〜1の値)
- 左右キー操作の結果:左は負の値、右は正の値
- jump(true/false)
- スペースキー操作の結果:trueはジャンプし、falseはジャンプ解除する
AnimationControllerを作成する
Unityの「Animation Controller」は、キャラクターやオブジェクトのアニメーション状態を管理し、遷移を制御するためのツールです。これを使うと、異なるアニメーションクリップ間で滑らかに遷移するための条件やルールを設定できます。
作り方は、プロジェクトビューで、コンテキストメニューから、Create
-> Animation Controller
をクリックです(下図)。
状態設計と実装
ダブルクリックすると、下図のように状態が3つ見えます。
今回は、下図のような状態遷移にします。
初期状態は、Locomotion
で、ここで上下・前後移動の動きを実現する状態です。Jump
は、キャラクタをジャンプさせる状態です。
Locomotionは、下図のようにFrom New Blend Tree
で作成します。
Jumpは、下図のようにEmpty
で作成します。
次に、入力パラメータを設計内容に従って、それぞれ追加します。
そして、状態遷移トリガを追加します。
Locomotion
:jumpがtrueの場合に、Jump
に遷移
Has Exit Time
のチェックを外す必要があります。(理由はまだわかっていません...)
Jump
:jumpがfalseの場合に、Locomotion
に遷移
モーション設定
今回利用するモーションとしては、ユニティちゃんを利用します。
最新のものをダウンロードして、Import します。
Jump のモーション設定
Jumpを選択して、Motion
をクリックして、JUMP0
を選択します。
Locomotion のモーション設定
Locomotionの状態では、上下・前後の移動のモーションを入力パラメータに応じて切り替えます。
そのための設定が、下図のものです。
設定方法の詳細はこちらが参考になります。
Unityスクリプトを作成する
今回作成したUnityスクリプトは以下のものです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterOperation : MonoBehaviour
{
// Animator コンポーネント
private Animator animator;
// 設定したフラグの名前
private const string key_isJump = "jump";
private Rigidbody my_body;
// Start is called before the first frame update
void Start()
{
this.animator = GetComponent<Animator>();
my_body = GetComponent<Rigidbody>();
}
float speed = 2.0f;
void Update()
{
var h = Input.GetAxis("Horizontal");
var v = Input.GetAxis("Vertical");
//Debug.Log("h=" + h + " v=" + v);
if (Mathf.Abs(h) > 0.01f || Mathf.Abs(v) > 0.01f)
{
if (v < 0)
{
v = v / 1.5f;
}
var localVelocity = new Vector3(0, 0, v);
var worldVelocity = transform.TransformDirection(localVelocity);
my_body.velocity = worldVelocity * speed;
//向き操作
if (h > 0)
{
transform.Rotate(0, 0.1f, 0); // 右に0.1度回転
}
else if (h < 0)
{
transform.Rotate(0, -0.1f, 0); // 左に0.1度回転
}
//アニメーション操作
//Debug.Log("x=" + my_body.velocity.x + " z=" + my_body.velocity.z);
animator.SetFloat("speed_v", v);
animator.SetFloat("speed_h", h);
}
if (Input.GetKey(KeyCode.Space))
{
this.animator.SetBool(key_isJump, true);
}
else
{
this.animator.SetBool(key_isJump, false);
}
}
}
キャラクタに諸々アタッチして動作チェック
まずは、キャラクタをシーンビューに D&D します。
次に、物理オブジェクト RigidBody を割り当てます。この時、ContraintsのFreeze ROtation のXとZにチェックを入れます。(これやっとかないと移動する時に倒れちゃうので)
さらに、コライダー(Capsule Collider)を割り当てます。この時、キャラクタのサイズに合わせてパラメータ調整します。
次に、先ほど作成したスクリプトをアタッチします。
最後に、AnimationControllerを割り当て終了です。
デモ
こんな感じで動きます。
自動巡回させるためのコード
Unity の NavMesh を使うと簡単に実現できます。
インストール
Unityのパッケージマネージャで NavMesh をインストールする必要があります。
方法
- 移動領域に、NavMeshSurfaceアタッチして Bake する
- 障害物を置きたい場合は、NavMeshObstacleをアタッチして Curve にチェックをいれる
- 移動させたいオブジェクトに NavMeshAgentをアタッチ。Speedで移動速度をコントロールできる
プログラム
using System.Collections;
using System.Collections.Generic;
using UnityEngine.AI;
using UnityEngine;
public class CharacterOperation : MonoBehaviour
{
// Animator コンポーネント
private Animator animator;
public Transform[] goals;
private NavMeshAgent agent;
// 設定したフラグの名前
private const string key_isJump = "jump";
private Rigidbody my_body;
private int curr_goal_inx = 0;
// Start is called before the first frame update
void Start()
{
this.animator = GetComponent<Animator>();
my_body = GetComponent<Rigidbody>();
agent = this.GetComponent<NavMeshAgent>();
if (agent == null)
{
throw new System.Exception("can not find Agent");
}
agent.destination = goals[0].position;
}
private void Update()
{
Vector3 velocity = agent.velocity;
Vector3 localVelocity = transform.InverseTransformDirection(velocity);
// ローカル速度のz成分とx成分をアニメーターに設定
animator.SetFloat("speed_v", localVelocity.z);
animator.SetFloat("speed_h", localVelocity.x);
if (agent.remainingDistance < 0.5f)
{
curr_goal_inx++;
if (curr_goal_inx >= this.goals.Length)
{
curr_goal_inx = 0;
}
agent.destination = goals[curr_goal_inx].position;
}
}
}
最後に
ここで利用させていただきましたVRMファイルは、株式会社プラスプラス様からご提供いただいたものを使用しております。ご協力いただき誠にありがとうございます。