武器を持ったVRoidをAnimancerでアニメーションさせる(Unity)

久しぶりに投稿します。

ちょっと前にVRoidというサービスを知り、3Dモデルが非常に身近になったのを感じてました。その際は男性用モデルがほぼなく、作りたいゲームに合わないと判断、一度スルーしたのですが、

最近になって再度調べていると、男性モデルをアップしてくれている方がいらっしゃるじゃないですか!

これはゲームに使用できる可能性が出てきた…ということで、簡単に3Dモデルを使用するシーンを作ることにしました。

 

【想定するシーン】

・VRoidより出力したキャラクターで3Dモデルを用意

・キャラクターはUnityで扱う。キャラクターのアニメーション(モーション)はUnityのAssetで賄う

・Animancerというアセットを使い、スクリプトからアニメーション切替制御を行う。

・キャラクターは画面奥に向かって永遠に走り続けることができる

・走っている最中に持っている剣を振ることができる(マウスクリックで振る)

 

 

モデルの用意

使用モデル

 

スナック☆アツコ資材館さん、素敵なモデルの提供感謝!

Boothより.vroidファイルをダウンロードしたらVRoidStudioより.vrmファイルを出力し、unityのシーンにドラッグアンドドロップするだけで3Dモデルが読み込まれる…という手軽さ!

少し前では考えられないくらい楽になったのを実感しますね!

 

モーションの用意

UnityはAssetStoreでいろんなモーションが販売されています。

今回は何かのバンドルセールで買ったときに入っていた以下モーションを使用します。

Elevate your workflow with the Melee Warrior Animations asse…

いつもの通りインポートし、フォルダ内のファイルを使用します。なお、アニメーションは、△矢印を展開して見る必要があるものもある様。

 

Animancerの使用例

(2023/1/21 外部サイトリンク切れにより加筆修正)

具体的手順は以下。

①UniVrm(0.x系)をインストールし、vrmファイルからPrefabに変換する。ドラッグアンドドロップ操作のみ。

※注記 2023/1/21時点でUniVrmは1.x系もダウンロード可となっているが、vrmの形式違いによりこちらではvrmからモデルの生成ができない。

②URPの場合など、正常に表示されないなどあればテクスチャを修正する。テクスチャ類はPrefabが作られるときに同一階層のフォルダのどれかにある。

③Prefabをシーンにドラッグアンドドロップしインスタンス化する。

④Animancerをパッケージマネージャからインストールし、インスタンスにAddComponentから「NamedAnimancerComponent」をアタッチする。

⑤インスタンスにPlayerModelController(下記)をAddComponent(AddScript)する。

⑥PlayerModelControllerコンポーネントのAnimancer部分に、インスタンスをドラッグアンドドロップして設定する。

⑦PlayerModelControllerのIdle,Action部分に、適当なモーションファイルをドラッグアンドドロップする。

⑧別のスクリプトからDoActionを呼ぶ。動作するはず。

 

フェードの設定を付加する場合は下記公式を見るとスムーズ。

参考(但し古いのか、そのままではコードがエラーを吐く)

Qiita

#1. 前提Unity アセット真夏のアドベントカレンダー 2019 のために書かれた記事です。この記事は、Unity上…

PlayerModelController

using Animancer;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class PlayerModelController : MonoBehaviour
{
    [SerializeField]
    private AnimancerComponent _Animancer;

    [SerializeField]
    private AnimationClip _Idle;

    [SerializeField]
    private AnimationClip _Action;

    private void OnEnable()
    {
        _Animancer.Play(_Idle);
    }

    private bool isActioning;
    public async UniTask DoAction()
    {
        var state = _Animancer.Play(_Action,0.25f);
        state.Time = 0;
        isActioning = true;
        state.Events.OnEnd = OnActionEnd;

        while (isActioning) await UniTask.DelayFrame(1);
        state = _Animancer.Play(_Idle,0.25f);
    }

    private void OnActionEnd()
    {
        isActioning = false;
    }
}

 

 

 

3Dモデルに武器を持たせる

以下の手順で行う。対して難しくはない。

①空のゲームオブジェクトを作り、その子に武器のPrefabを設置する。

②キャラクターの手の部分の子として①で作ったゲームオブジェクトを位置調整の上配置する。

③適当に再生したりして微妙な位置調整する

 

背景画像を永遠に生成する

はじめはTerrain等の地形生成を検討したが、最終的にモバイル向けを考えるとどうしても描画処理関係が気になる。

そこで、軽量Prefabを使いまわしてリソース節約する方法とした。

建物としては以下を使用した。これもいつかのアセットバンドルセールでいつの間にか入手していたもの。

Elevate your workflow with the POLYGON Western - Low Poly 3D…

 

方法は以下の通り

①複数個建物を並べたものを1つのPrefabとする。

②①をPrefabの幅に合わせて等間隔でいくつか配置する。

③②をキャラクターの移動量に合わせて画面奥から手前に動かす

④建物を手前に動かすのをキャラクターの移動モーション中のみに制限する

要は、キャラクターは動かさずその場で足踏みし、建物を手前に動かすことで前方に移動しているように見せる、

まあ古くからある方法ですね。

以下を空のゲームオブジェクトにaddComponentし再生すれば画面奥に移動する感じが表現できました。

 


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

public class BattleSceneController : MonoBehaviour
{
    [SerializeField]
    private List<Transform> LoadSideTransformlist;
    [SerializeField]
    private int LoadSideBuildingWidth;
    [SerializeField]
    private int LoadSideSpacing;


    [SerializeField]
    private AnimancerComponent _Animancer;

    [SerializeField]
    private ClipTransition _Idle;

    [SerializeField]
    private ClipTransition _Action;

    private bool idling;

    private void OnEnable()
    {
        _Action.Events.OnEnd = (() =>
        {
            _Animancer.Play(_Idle);
            idling = true;
        });
        _Animancer.Play(_Idle);
        idling = true;
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            _Animancer.Play(_Action);

            idling = false;
        }
        else if(idling)
        {
            var deltaTime = Time.deltaTime;
            var distance = 10.0f * deltaTime; // 10m/s×delta_t
            LoadSideTransformlist.ForEach(x =>
            {
                Vector3 xyz;
                if(x.position.z < -LoadSideBuildingWidth)
                {
                    xyz = new Vector3(x.position.x, x.position.y, x.position.z - distance
                        + LoadSideTransformlist.Count * (LoadSideBuildingWidth + LoadSideSpacing));
                }
                else
                {
                    xyz = new Vector3(x.position.x, x.position.y, x.position.z - distance);
                }
                x.position = xyz;
            } );
        }
    }
}

 

以上

久しぶりにUnity触った気がする。