Raspberlyのブログ

Raspberlyのブログ

Unityネタをメインとした技術系ブログです。にゃんこ大戦争や日常なども。そろそろブログタイトル決めたい

Unity Package ManagerのGit URLからパッケージをインストールしようとしたら出るエラーの解決方法

個人的な忘備録。
Unity Package Manager(UPM)のGit URLからパッケージをインストールしようとしたらエラーが出て困ったときの解決方法

 

Unityのバージョンは2021.3.12f1(多分関係ないはず)
OSはWindows10です。

 

 

解決方法

先に解決方法から載せると、
システムの環境変数にGitのパスを設定すれば解決しました

 

 

 

状況とエラー内容

Unity Package ManagerでUniTaskをインストールしようとしたらエラーが出てインストールできない。

 

エラー内容は以下の通り

[Package Manager Window] Cannot perform upm operation: Unable to add package https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask:
  No 'git' executable was found. Please install Git on your system then restart Unity and Unity Hub [NotFound].
UnityEditor.EditorApplication:Internal_CallUpdateFunctions ()

 

[Package Manager Window] Error adding package: https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask.
UnityEditor.EditorApplication:Internal_CallUpdateFunctions ()

 

「Gitの実行ファイルが見つからないから、GitをインストールしてUnityとUnity Hubを再起動してね」
と言われています。Gitはインストール済なのに。

 

 

 

 

解決方法

Unity Forumとこちらの動画の方法で解決

forum.unity.com

youtu.be

 

この動画の通りに進めてみます。

 

git.exeがあるフォルダのパスを確認

git.exeが置いてあるフォルダを調べ、そのパスを控えておきます。


git.exeは複数ありますがcmdフォルダ*1にあるものを使います。
私の場合は「C:\Program Files\Git\cmd」でした。このパスをコピーしておきます。

 

 

環境変数に設定

Windowsの検索窓にPathを入力。
検索結果に「システム環境変数の編集」が出てくるので開きます。

 

システムのプロパティというウインドウが開かれるので、環境変数を押します。

 

するとこんな画面が出ると思います。表示されている内容は人によって異なりますが。

 

ここでシステム環境変数からPathを選択し、編集ボタンを押します。

 

環境変数名の編集画面が開きます。

 

右上の新規ボタンを押します。

 

新しい環境編のパスを入力する欄ができるので、そこに先ほどのgit.exeのあるcmdフォルダのパスを入力

最後にOKを押します。

 

 

これで完了です。
UnityとUnity Hubを全て開きなおすとUPMからパッケージをインストールできました。

 

 

 

 

参考になりそうな記事

www.hanachiru-blog.com

 

baba-s.hatenablog.com

 

 

*1:binフォルダでもよさそう

【アセット紹介】Play Mode Saver でPlay Mode中に変更したパラメータを保持する【Unity】

今回はアセットの紹介をしていきます。
紹介するのはPlay Mode Saver
Play Mode中に変更したパラメータを終了後も保持するアセットです。

screenshot

どんなアセット?

Play Mode中に変更したパラメータ(Transformの値など)を、Play Modeが終了した後も保持するアセットです。

 

通常、Play Mode中に変更したパラメータはPlay Modeを終了すると元通りになります。
どうしても保存したい場合、InspectorビューでCopy ComponentしてPlay Modeが終わった後Pasteするというやり方がよく使われます。
このアセットを使えば、それらの保存を簡単に行えます。

 

※レビューによると、Unity 2022.2ベータ版以降では動作しない可能性があります
1年以上更新されていないのでそこは注意

 

 

実行環境

Unity 2021.3.12f1

Play Mode Saver ver1.3.5

 

 

インポートの確認

アセットインポート完了時はこんな感じ。

初期設定とかは必要なし。すぐに実行できます。

 

 

実際に試してみる

基本的な使い方

Unity Editorのメニューから、Window/Play Mode Saverを押して、Play Mode Saverウィンドウを開きます。

 

このウインドウはPlay Mode中にのみ機能します。

 

Play Mode中にScene内のGameObjectをドラッグ&ドロップすると、保持対象として登録され、
Play Modeが終了した時、その瞬間のパラメータが全て保持されます
保存するタイミングは後述のSave Modeにより異なります。

 

ちなみにInspectorビューからドラッグ&ドロップして、特定のコンポーネントだけ対象にすることもできます。
GameObjectを丸ごと登録すると影響範囲が大きいため基本はコンポーネント単位でやったほうがいいかも。

 

ちなみにドラッグ&ドロップ以外にも、GameObjectやコンポーネントを右クリックして登録することもできます。
ドラッグ&ドロップと違い、後述するSave Modeを直接指定できます

この時、ウインドウが開いていない場合は自動的に開かれます。

 

 

Save Mode(保存形式)の違い

Save Mode(保存する形式)は2種類あります。
Save Modeの違いにより保存されるタイミングが違います。

Save on Exit

Play Modeが終了した時のパラメータを保持するモード。

所感ですが、終了時にパラメータがどういう風になるか予想できない場合は使わない方がいいです。

 

Snapshot

Save Snapshotが押されたタイミングのパラメータを保持するモード。

Save on Exitよりは安全。
Play Mode中に一時停止して、きちんと想定した値になっているかを確認してから保存した方がよい
Save Snapshotした後に変更したパラメータは再度Save Snapshotしない限り無視されます。

 

Save Modeの切り替え

モードの切り替えは、同じくPlay Mode Saverウインドウ上でできます。
Save Mode部分のボタンを押すだけ。

ドラッグ&ドロップで登録した場合は、デフォルトでSave on Exitになります。

 

登録の解除方法

Play Mode Saverウインドウから、右側にある×ボタンを押すと解除されます

Unity Personalだとほとんど見えない!

 

 

あるいは、登録したGameObjectかコンポーネントを右クリックして、Forget Play Mode Changesを選択しても解除できます。

 

 

変更したパラメータを元に戻す

Play Mode Saverで変更した値は、Ctrl + Zで元に戻せます
また、Ctrl + Yで元に戻した変更を再適用できます

 

 

 

注意点

親を保持対象にすると子も保持される

親子関係にあるGameObjectで、親を保持対象とした場合、子の変更も保持されます
子オブジェクトが増えたり減ったりした場合も全て保存されます。
複雑な親子関係になっているGameObjectを扱う場合は注意しましょう

 

※子だけ保持対象にすることもでき、その場合親の変更は無視されます。

※登録したGameObjectやそれにアタッチされているコンポーネントをが削除された場合、自動的に登録も解除されます。

 

動的生成されたGameObjectを対象にした場合はそのままSceneに残る

Play Mode後、何らかの方法でSceneに動的生成されたGameObjectを保持対象に登録した場合、
そのGameObjectはPlay Mode終了後もSceneに残り続けます
コンポーネントだけ選んでもGameObjectごと残ります。

Prefabを保持対象とした場合、元のPrefab側は変化しないのでそこは安心。

 

 

 

まとめ

Play Mode Saverを使うと、Play Mode中に変更したGameObjectのパラメータが終了後も保持される
・モードは2つあり、Play Mode終了時のパラメータを保持するモードと、任意のタイミングでパラメータを保持するモードがある
・保持したパラメータを元に戻せるし、再適用も簡単にできる

 

Play Modeを終了し忘れた時だけでなく、プレイヤーの移動速度など実際にゲームを動かしながらパラメータの調整をしたい時にも便利なアセットですね。

 

 

以上です。

 

 

 

他のアセットの紹介記事はこちら↓

raspberly.hateblo.jp

 

 

 

※本記事にはAssetStoreアフィリエイトリンクが含まれています。

他、間違っている箇所、わかりにくい所がありましたらコメントにお願いします。

【エディター拡張】Projectビューの指定のフォルダにあるPrefabをListに格納する【Unity】

今回はUnityのエディター拡張をしていきます。
最近エディター拡張を書くことが多いので、自分用の忘備録です。

 

やること

Projectビューの指定のフォルダにあるPrefabを検索し、それをScriptのListに格納する

 

 

開発環境

Unity 2021.3.12f1

 

 

事前準備

先に格納したいPrefabを作成します。
Assets/Prefabsフォルダを作成し、適当にGameObjectをPrefab化しておきます。

 

 

 

Scriptの作成

以下のScriptを作成します。

ObjectList.cs

GameObject型のListがあるScript。
Listを設定する関数と空にする関数を持つ。

using System.Collections.Generic;
using UnityEngine;

public class ObjectList : MonoBehaviour
{
    [SerializeField] List<GameObject> m_List = new List<GameObject>();

    /// <summary>
    /// GameObjectのListを受け取りm_Listに格納する
    /// </summary>
    /// <param name="objects"></param>
    public void SetObjects(List<GameObject> objects)
    {
        m_List = objects;
    }

    /// <summary>
    /// m_Listを空にする
    /// </summary>
    public void Clear()
    {
        m_List.Clear();
    }
}

 

 

ObjectListEditor.cs

ObjectList.csのエディター拡張Script
プレハブをロードしてListに格納するボタンと、Listを空にするボタンを表示する。

using System.Linq;
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(ObjectList))]
public class ObjectListEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        var objectList = target as ObjectList;

        if(GUILayout.Button("プレハブロード"))
        {
            LoadPrefabs(objectList);
            EditorUtility.SetDirty(objectList);
        }

        if(GUILayout.Button("クリア"))
        {
            objectList.Clear();
            EditorUtility.SetDirty(objectList);
        }
    }


    /// <summary>
    /// プレハブのロード
    /// </summary>
    static public void LoadPrefabs(ObjectList objectList)
    {
        // ロード先のディレクトリパス
        var path = "Assets/Prefabs";

        var guids = AssetDatabase.FindAssets("t:GameObject", new string[] { path });
        var paths = guids.Select(guid => AssetDatabase.GUIDToAssetPath(guid)).ToArray();
        var list = paths.Select(_ => AssetDatabase.LoadAssetAtPath<GameObject>(_)).ToList();

        if(list.Count == 0)
        {
            Debug.LogWarning("プレハブがない\n" + "検索パス[" + path + "]");
            return;
        }

        Debug.Log("プレハブを検索:" + list.Count + "つ格納\n" + "検索パス[" + path + "]");
        objectList.SetObjects(list);
    }

}

これで完成

 

実行

空のGameObjectを作成し、ObjectListをアタッチするとボタンが表示されます。
ロードボタンを押すと、指定のフォルダにあるPrefabをListに格納します



 

 

調整

以下細かい調整

読み込むフォルダの変更

ObjectListEditor.csのLoadPrefabsにある

var path = "Assets/Prefabs";

ディレクトリパスを指定しているのでここを書き換えればよし。
ここも指定できるようにしていいかも。

 

 

特定のコンポーネントがアタッチされているPrefabのみ格納する

Prefabを検索する時、特定コンポーネントがあるかWhere句を挟むとよい。
例えば、RigidbodyがアタッチされているPrefabだけ格納したいならこうする。

        // 特定のコンポーネントがあるものだけ格納
        var list = paths.Select(_ => AssetDatabase.LoadAssetAtPath<GameObject>(_))
            .Where(_ => _.TryGetComponent(out Rigidbody rigidbody))
            .ToList();

 

    /// <summary>
    /// プレハブのロード
    /// </summary>
    static public void LoadPrefabs(ObjectList objectList)
    {
        // ロード先のディレクトリパス
        var path = "Assets/Prefabs";

        var guids = AssetDatabase.FindAssets("t:GameObject", new string[] { path });
        var paths = guids.Select(guid => AssetDatabase.GUIDToAssetPath(guid)).ToArray();

        // 特定のコンポーネントがあるものだけ格納
        var list = paths.Select(_ => AssetDatabase.LoadAssetAtPath<GameObject>(_))
            .Where(_ => _.TryGetComponent(out Rigidbody rigidbody))
            .ToList();

        if(list.Count == 0)
        {
            Debug.LogWarning("プレハブがない\n" + "検索パス[" + path + "]");
            return;
        }

        Debug.Log("プレハブを検索:" + list.Count + "つ格納\n" + "検索パス[" + path + "]");
        objectList.SetObjects(list);
    }

 

AssetDatabase.FindAssets()のフィルタを使えばもっと簡単にできそうだけど、
なぜかうまくいかないのでLINQで対応しています。

 

 

 

まとめ

指定のフォルダにあるPrefabを検索し、ScriptのListに格納するエディター拡張を作りました。
Inspectorを拡張し、格納するボタンを配置しています。
Scriptを編集することで、対象のフォルダや格納するPrefabの条件を設定できます。

 

以上です。

 

 

参考資料

www.urablog.xyz

 

www.hanachiru-blog.com

 

 

 

【アセット紹介】SensorToolkit 2 でオブジェクトを検知する【Unity】

今回はアセットの紹介をしていきます。
紹介するのはSensorToolkit 2
オブジェクトを検知するセンサー機能を提供するアセットです。

 

 

セール情報

現在アセットストアではメガバンドルセールを開催中!

assetstore.unity.com

超お得に多くのアセットを入手できるチャンス!
今回紹介する「SensorToolkit 2」も含まれています。

 

どんなアセット?

オブジェクトの検知に特化したアセットです。

SensorToolkit は、Raycasts、Overlaps、Trigger Colliders などのビルトイン Unity 関数を強力に抽象化したものです。スタンドアロンのセンサー コンポーネントをゲーム オブジェクトに追加し、必要に応じて構成できます。センサーにクエリを実行して、検出対象を特定し、ターゲットの可視性や形状などの追加情報を提供できます。

皆さんがステルスゲームなどで、敵がプレイヤーを検知する仕組みを作りたい場合どうしますか?
二点間のTransformを計算したりColliderを使って近くにPlayerがいるか確認し、その後にRayを飛ばして間に障害物がなければPlayerを発見するといった処理を書いたりするのではないでしょうか。
SensorToolkit 2を使えばそれらの処理を簡単に実装できます

 

 

旧バージョン

過去に前バージョンの紹介記事を投稿しています。
メジャーアップデートにより古いバージョンは使用できなくなったのでご注意ください

raspberly.hateblo.jp

 

拡張性の高い設計への変更といくつかの新機能が追加されています。
ドキュメントはこちら

micosmo.com

 

 

 

開発環境

Unity2021.3.12f1

SensorToolkit 2 ver2.4.1

 

 

 

デモシーンの確認

アセットインポート完了時はこんな感じ。

 

サンプルシーンがいくつか用意されています。


Fundamentalsシーンではアセットにある全てのセンサーが紹介されているので、
インポート後最初に見ることをオススメします。

 

 

センサーとコンポーネントの紹介

Fundamentalsシーンを元に各センサーを簡単に紹介します。
ドキュメントからも詳細が確認できます。

 

Ray Sensor

Rayを飛ばして衝突したオブジェクトを検出するセンサーです。UnityのRayに近い。
レイを完全に遮るレイヤーを設定したり、遮断される斜面の角度を指定できます。

形状をRayから変更するとボリュームのある衝突判定を行うことができます。

最も良い使い方は、Obstructed By Layersに衝突させたいオブジェクトのレイヤーを指定することです。

 

Arc Sensor

Ray Sensorの曲線バージョンです。曲線であること以外はほとんど同じ。

セグメントに対して複数のRay Sensorを呼び出すということをしているので、セグメントをなるべく少なくするのがポイント。
使いどころが難しいですが、公式ドキュメントではキャラクターがジャンプできるかどうかの判定で使用していました。

 

 

Range Sensor

範囲内のオブジェクトを検出するセンサー。

形状はSphere、Box、Capsuleが用意されています。

 

 

Trigger Sensor

OnTriggerEnterOnTriggerExitでオブジェクトを検出するセンサー
センサーがアタッチされたオブジェクトにはColliderと、IsKinematicが有効になったRigidbodyが必要です。
センサーの範囲などはColliderの大きさで調整します。

Range Sensorと似たような役割を持ちますが、こちらの方が多機能で少し複雑です。
UnityのTriggerとColliderによる衝突判定に少し慣れていないと難しいかも。

 

Range Sensorは使える形状が3種類と制限がありましたが、
OnTriggerEnterOnTriggerExitが呼ばれるColliderであればどんなものでも機能します
SensorToolkit 2には円錐状のFOV Colliderが含まれているため、視界内にいるかどうかの判定にぴったりです。

 

 

慣れている方なら推察できる通り、OnTriggerEnterOnTriggerExitを使って検出していることから、検出中のオブジェクトが非アクティブになると判定が取れなくなります。


SensorToolkit 2では、この問題の対処方*1としてSafeModeが用意されています。
このSafeModeが有効だとOnTriggerStayも併用して判定します。

 

SafeModeを使いたくない場合は、非アクティブ前にオブジェクトを遠くまで移動(OnTriggerExitを呼ばせるため)させるというひと手間を加える必要があります。

 

 

Signal Proxy

オブジェクトがセンサーに検出された時、別のオブジェクトを検出対象としてSensorに指示するコンポーネント
センサーではなく、検出される側のオブジェクトにアタッチして使用する。
これは複数のColliderを持つオブジェクトに対して使う。

 

例えば、手足のColliderがSensorに検出された時、手足ではなくルートオブジェクトをSensorに伝えたりなど。

 

 

LOS Sensor(Line of Sight Sensor)

オブジェクトを視認できるを判定し検出するセンサーです。
オブジェクトに複数のRayを飛ばし、それがどれくらい遮られているかを計算して、視認の有無を決定します。
これは他のSensorを併用して判定します。併用しているSensorに検出されたオブジェクトにのみRayを飛ばして視認性を計算します。

 

UserSignalsを使って、Sensorを使わず特定のオブジェクトだけ最初から紐づけて視認性をチェックしたり。

 

最大距離と視野角に制約をつけ、視野角ギリギリや遠くのオブジェクトの視認性にマイナス補正をかけたりできる。

 

 

Boolean Sensor

複数のセンサーをマージするセンサー
複数のセンサーを取得し、OR判定、AND判定でオブジェクトを検出したりできます。

 

 

他にSteering SensorとNavMesh Sensorもある

 

実際に試してみる (NPCの視界を作る)

実際にSensorToolkit 2 を使って、「Playerを視認したら追跡するNPC」を作ってみます。

 

ステージの作成

Navigationで移動させるので、NavMeshをBakeしておきます。
視認の有無がわかりやすいよう、Cubeを変形させて壁として配置しています。

 

 

プレイヤーの作成

適当なプレイヤーを作成します。今回はユニティちゃん(URP)を使用します。

raspberly.hateblo.jp

プレイヤーオブジェクトのレイヤーはPlayerにしておきます。

 

 

エネミーの作成

プレイヤーを追跡するエネミーを作成します。


Enemyオブジェクトを作成し、その子にLOS SensorRangeSensorCapsuleを作成します。

 

RangeSensorにはRangeSensorコンポーネントをアタッチ。
DetectsOnLayersをPlayerにします。


LOS SensorにはLOSSensorコンポーネントをアタッチ。
InputSensorに先ほど作ったRangeSensorを紐づけます。

 

 

Enemyには下記のEnemyAIコンポーネントNavMeshAgentをアタッチします。
EnemyAIのSightにはLOS Sensorを紐づけます。


EnemyAIはこちら
プレイヤーを視認したら追跡し、見失ったら即座に停止します。

using UnityEngine;
using UnityEngine.AI;
// SensorToolkitを使う場合はこれが必要
using Micosmo.SensorToolkit;

public class EnemyAI : MonoBehaviour
{
    [SerializeField] Sensor sight;
    NavMeshAgent agent;
    GameObject target;

    void Start ()
    {
        agent = GetComponent<NavMeshAgent>();
        sight.OnDetected.AddListener(OnDetect);
        sight.OnLostDetection.AddListener(OnLost);
    }

    /// <summary>
    /// 見つけたときに呼ばれる関数
    /// </summary>
    public void OnDetect(GameObject detection, Sensor sensor)
    {
        var detectObjs = sensor.GetDetections();
        // 見つけたら追跡開始
        // 今の所Playerは1体だけなのでこれでやる
        target = detectObjs[0];
        agent.isStopped = false;
    }

    /// <summary>
    /// 見失ったときに呼ばれる関数
    /// </summary>
    public void OnLost(GameObject detection, Sensor sensor)
    {
        target = null;
        agent.isStopped = true;
    }

    void Update ()
    {
        if(target)
        {
            agent.SetDestination(target.transform.position);
        }
    }

}

LOS Sensorにイベントを紐づけて追跡状態を切り替えます。
AddListenerせず、var detectObjs = sight.GetDetections(); で Sensorで検出しているオブジェクトを取得しいろいろやってもいいですね。(最初はそうやってた)

 

 

これで完成、視界が黄色いギズモで表示されていますね。

 

動かしてみると、視界に入っている時だけプレイヤーを追跡しているのがよくわかりますね。

見失ったら即座に停止しているので、数秒は追いかけるという風に書き換えてもいいかも。

 

 

CapsuleをSDユニティちゃんに置き換えたバージョン

 

 

まとめ

SensorToolkit 2はオブジェクトを検出するセンサー機能を提供するアセットです。
各センサーの概要、実際にオブジェクトを検出する方法を紹介しました。
NPCの視界を実装するのに便利!

 

 

 

他のアセットの紹介記事はこちら↓

raspberly.hateblo.jp

 

 

 

※本記事にはAssetStoreアフィリエイトリンクが含まれています。

他、間違っている箇所、わかりにくい所がありましたらコメントにお願いします。

*1:正しい解決方法はあるが複雑なので妥協案としてセーフモードが用意されている

【Unity】ダークソウルっぽいカメラワークを作る【Cinemachine + Input System】

この記事はUnity Advent Calendar 2022 その3 24日目の記事です。

qiita.com

 

今回は、ダークソウルのようなカメラワークをCinemachineで実装してみます。
記事内ではゲームパッドで操作することを前提としています。

ちょっと不具合があったり、機能を省いていますがご了承ください。

 

 

ダークソウルのようなカメラワークとは

ダークソウルとは3人称視点のアクションRPGです。
一般的な3人称視点のゲームと同様、カメラはプレイヤーの移動に合わせて追従し、
プレイヤーを中心に回転します。

 

ゲームパッドでは左スティックで移動、右スティックで視点移動をします。

 

 

今回はダークソウル3をベースとした移動とカメラワークをUnityで実装します。
具体的には

左スティックでの移動
右スティックでの視点移動
ジャンプ機能(これはSEKIROから)
R3(右スティック押し込み)でのロックオン

の4つを実装します。

 

 

開発環境

Unity 2021.3.12f1

Cinemachine ver2.8.9

Input System ver1.4.4

 

プロジェクトはURPですがBuilt-inでも問題ありません。
必須ではありませんが、ステージ用アセットとしてこちらも使用します。

 

 

 

プロジェクトの設定

パッケージのインストール

UPM(Unity Package Manager)からCinemachineInput Systemをインストールします

 


Input Systemをインストールすると、このようなダイアログを表示されるのでYesを押し再起動します



再起動後、ProjectSettings/Player/OtherSettings/Configration/ActiveInputValidation
Both、またはInputSystemPackage(New)になっていることを確認します。

なっていない(Input Managerのまま)場合は変更してください。

 

 

シーンの作成

インポートしたステージアセットに含まれている、SpeedTutor Test Scene.sceneをそのまま使います。
元のシーンを残しておきたい方は別名で保存するといいですね。

このシーン内に含まれているCameraは、TagがUntaggedになっているので、
TagをMainCameraに変更します。変更しないと後々エラーがでます。

また、ステージがそのままだとやや狭いので、DemoScene DecorationのScaleを全て2にしておきます。

 

 

 

上記のステージアセットを使わない場合は、Create Sceneでシーンを作成します。
Playerが動けるようPlaneだけ置いておきます。障害物などは必要ありません。

 

 

 

プレイヤーの作成

まずはプレイヤーを作成します。
Create/3D Object/CapsuleからCapsuleを作成名前をPlayerにし、Rigidbodyをアタッチします。
RigidbodyはConstraintsのFreezeRotationにチェックしておきます。

 

追加でオブジェクトを作成

Playerの方向を取得するオブジェクトと、接地判定用オブジェクトを作成します。

 

Playerの方向を取得するため、Playerの子オブジェクトとして空のGameObjectを作成。
名前はOrientationにします。位置や角度はそのまま。

 

同じように設置判定をするGameObjectを作成。
名前はGroundCheckにします。

このGroundCheckはPlayerの足元に配置します。

 

 

移動制御Scriptの作成

以下の、PlayerMove.csをPlayerにアタッチします。

 

PlayerTrnOrientationTrnGroundCheckTrnにそれぞれオブジェクトを紐づけ、
WhatIsGroundはDefaultを設定します

 

Input Systemの対応

Input Systemで入力を受け取りたいのでその対応をします。
PlayerにPlayer Inputコンポーネントをアタッチ。

 

Player Input内のCreate Actionsボタンを押して、Input Action Assetを作成。

 

作成後、Input Action Assetの設定をしていきます。

このままでも基本的な移動や視点移動はできますが、ジャンプとロックオン機能を実装したいため、新しいActionを追加します。

 

PlayerマップのActionsにJumpを追加、Action TypeはButtonを設定。

 

BindingのPathはGamepad/Button Southを設定、Use in control schemeのGamepadにチェックを入れます。

 

同じ手順でLockon Actionを追加、BindingはGamepad/Right Stick Pressを設定。

最後にウインドウ上部のSaveAssetで保存します。
保存したら、このウインドウは閉じても大丈夫です。

 

 

ここまでできたらゲームを実行してみましょう。
ゲームパッドの入力で移動とジャンプができると思います。

左スティックで移動、下のボタン(PS配置なら×ボタン)でジャンプします。

 

 

 

カメラの作成

次にCameraを動かせるようにします。
シーン内にCinemachine/Virtual Camera*1CM vcam1を作成

 

Inspectorビューから設定していきます。
FollowPlayerを紐づけます。

 

Bodyの設定

BodyはCameraの移動を制御する項目です。

BodyをFraming Transposerに設定

これはCameraがターゲットに対して(スクリーン空間上で)一定の距離を保ちながら追跡するアルゴリズムです。

パラメータはこのままにします。

 

Aimの設定

AimはCameraの回転を制御する項目です。

AimをPOVに設定

これはCameraをユーザーの入力に合わせて回転させるアルゴリズムです。

こちらのパラメータもそのままにします。

 

 

Input Systemの対応

初期設定では旧Input Managerによる入力を使用してしまうので、Input Systemを使うよう変更します

CinemachineVirtualCameraコンポーネントの上部にある、Add Input Providerボタンを押します。

 

ボタンを押すと、CM vcam1にPlayer Inputコンポーネントが自動的にアタッチされます。
これはCinemachineでInput Systemの入力を受け取り処理できるようにするコンポーネントです。

XY Axisに最初からActionが紐づけられています。
ここには先ほど作ったInput Action AssetのActionではなく、デフォルトで用意されているActionが使われていますが、機能的には同じなのでこのままでも問題ありません。

 

 

この状態でゲームを実行してみましょう。
右スティックの入力に合わせてカメラが回転します。

 

これでキャラクターの移動とカメラの視点移動を実装することができました。
ここから先はおまけです。

 

 

Cinemachineの詳細設定

Cameraの更新設定(カクつきの対策)

Playerの移動をFixed Updateで行っている時限定

 

このまま動かすと、Playerが移動するたびCameraがガクガクします。
Playerの移動はFixed Updateで行っているのでCameraの更新タイミングを変更すると解決します。

Main CameraにアタッチされているCinemachineBrainのUpdate MethodをFixed Updateに変更

(CinemachineBrainはVirtualCameraをシーンに作成すると自動でアタッチされます)

 

 

Cameraが地形を貫通しないようにする

カメラが壁や地面を貫通しないようにします。
CinemachineVirtualCameraコンポーネントExtensions/Add ExtensionからCinemachineCollierを選択。

 

CinemachineColliderコンポーネントがアタッチされるので、StrategyをPullCameraForwardに設定。
これはカメラがどうやって視線を確保するかを決める設定です。

これでカメラが地形を貫通しないようになりました

カメラが少しめり込んで裏世界が見えてしまう場合、CinemachineColliderのCameraRadiusを大きくすると改善します。

 

今回は省略してますが、敵やプレイヤーのColliderは無視してほしいので、レイヤーを分け壁や地面とだけ衝突するよう設定しておくのがいいですね。

 

 

よりダークソウルっぽいカメラ設定

カメラのパラメータはほとんど初期状態なので、よりダークソウルに近い設定をしてみます。

カメラの中心点

ダークソウルのプレイ動画を見ると、画面の中心はプレイヤーキャラクターの顔~首周辺です。
今の設定だとキャラクターのお腹あたりが中心点になってしまうのでここを変えます。
Playerの子供に空のGameObjectを作成、名前をGazeにします。

Gazeはキャラクターの頭当たりに配置します。

カメラの中心になるよう、CinemachineVirtualCameraコンポーネントのFollowに設定します。

距離と角度制限

カメラの距離は結構近めなためCameraDistanceは4
角度については、上方向に対し下方向はそこそこ制限があるのでこんな感じ。
(ちなみにPlayerMove.csのバグで、カメラが真上付近になるとプレイヤーの移動に支障がでちゃいます)

また、入力の反転はInvertで切り替えます。

 

これだけでダークソウルにちょっと近づきましたね。
追従速度など細かいところまでやりだすとキリがないのでこの辺りまで。

 

 

 

 

カメラのデーモン対策

カメラのデーモンとは

カメラのデーモンはダークソウルシリーズでよく用いられる単語で、
狭い場所や壁際でカメラが激しく動く
障害物によりカメラの動きが制限される、または勝手に動く
敵にカメラを遮られて何も見えなくなる
など、カメラワークによってゲームプレイが阻害される問題全般を指します。

 

SEKIROでは井戸底の孤影衆が有名で、場所がかなり狭く壁際に押し込まれると何も見えなくなることもあり、忍法クソカメラの術と呼ばれたりします。

以下Youtubeからの引用です(特に0:10~0:15あたり)

youtu.be

大型の敵に関しては、見上げるような視点で地形の把握ができなくなったり、
敵のモーションが見えにくい問題が発生します。
そのため、ロックオンを使わず戦うのが推奨されることもあります。

youtu.be

対策方法

対策方法として、モンスターハンターが参考になるので取り上げます。
モンスターハンターもカメラの問題に悩まされてきましたが*2、ワールドから大きく改善されました。

 

一部はSEKIRO以降のフロム作品でもたびたび見られます

オブジェクトを透過(ディザ抜き)する

カメラとプレイヤーの間に挟まれたオブジェクトを透過させる手法。

透過とは書きましたが、実際にはディザ抜きのケースが多いようです。

 

 

プレイヤーが壁と密着しないようにする

プレイヤーの移動可能範囲と壁の間にカメラ用スペースを確保し、プレイヤーが壁と密着できないようにする。

プラスして透過オブジェクトを間に挟む手法が、モンスターハンターワールドでは広く使われています。

 

図にするとこんな感じ↓

密着できる場所もあるにはありますが、ほとんど気にならないレベル。
まあダークソウルではわざと壁に接触し、装備やコーデを眺めて楽しむといった遊び方があったりしますが。

 

モンスターと重なった時プレイヤーを透過する

アイスボーンから追加された機能で、プレイヤーがモンスターと重なった時にプレイヤーのシルエットを表示する手法です。
ライズではアウトライン表示になりました。

 

 

 

今回はモンスターハンターから3つの手法を取り上げました。
これらを使うことで快適(当社比)なカメラワークを実現できると思います。

 

試しに、プレイヤーと壁の間にオブジェクトを挟んでみるとこんな感じ。
透過処理はシルエットで代用。

Playerと接触するがCinemachineCollierは無視するレイヤーのColliderを配置すれば簡単に実装できます。
アウトラインはアセットを使いました。

 

ダークソウルでは、カメラが荒ぶる時ロックオンを強制的に解除するという措置が取られたりもします。
ゲームごとにさまざまな工夫が凝らされているので、いろいろ観察してみるのもいいですね。

 

 

 

ロックオン機能の実装(仮)

おまけで、ダークソウルシリーズで使われるロックオン機能を実装してみます。
手順としては、ロックオン時にVirtualCameraを切り替えて対応します。
※プログラム的な問題点が多いです。ご了承ください。

 

ロックオン時のカメラを作成

ロックオン時にのみ使われるカメラを作成します。
新しくVirtual Cameraを作成。


Priorityは0FollowにはGazeBodyはFraming Transposerにします。
Bodyの値は最初に作ったCM vcam1と同じにしておいたほうがよさそう。

 

ダークソウルではターゲットとプレイヤーが重ならないように、画面中央よりやや上側にターゲットがくるようになっています。

 

これを再現するためDamping、Screen Y、Soft Zoneの大きさをそれぞれ調整します。

 

 

敵の作成

ロックオン対象となる敵を作成します。まずは判定用のEnemyレイヤーを定義。


Playerと同じ要領でCapsuleを作成し名前をEnemyに変更、LayerはEnemyに設定。

場所は適当な所でOK。

※プログラムのバグで、ロックオン対象との位置取りによっては挙動がおかしくなるかも

 

 

ロックオンカーソルを作成

ロックオンしてることがわかるUIを作成します。
Canvasとその子にImageを作成。

カーソル用に適当な画像を用意して使います。

最後に、このImageオブジェクトは必ず非アクティブにしてください

 

 

カメラを制御するScritpの作成

以下の、PlayerCamera.csをPlayerにアタッチします。

FreeLookCameraには通常時のVirtualCameraを、LockonCameraには先ほど作ったロックオン用VirtualCameraを紐づけます。

 

 

ロックオンを行うScriptの作成

以下の、PlayerLockon.csをPlayerにアタッチします。

PlayerCameraには先ほどのPlayerCameraコンポーネントを、OriginTrnにはPlayerを、TargetLayerNameにはEnemyを、LockonCursorには先ほど作ったカーソルをそれぞれ紐づけます。

 

 

VirtualCameraの切り替え速度

デフォルトだと切り替え速度が遅いので、MainCameraにあるCinemachineBrainコンポーネントDefaultBlendの値を0.5に変更します。

 

 

これでとりあえず完成。
カメラの正面方向にEnemyがいる状態で右スティックを押すとロックオン状態になり、再度押すとロックオンが解除されます。

正直あやしい挙動があったり、ダークソウルより動きが硬い気もしますがまあここまで。
ロックオン対象がいない場合はプレイヤーの向いている方向にカメラをリセットする機能が必要ですが今回はないです。

 

 

 

ロックオンカメラにCinemachineColliderをつける場合

割と致命的な問題として、ロックオン用カメラにCinemachineColliderがある状態で障害物をターゲットとの間に挟むとカメラが荒ぶる問題があります。

そのため、カメラとターゲットとの間にある障害物は、いい感じに衝突判定を無効化するなど工夫が必要です。
良い解決方法が思い浮かばなかったので対応していませんが、CinemachineColliderのIgnoreTagやTransparentLayersなどがヒントになりそう。

 

 

 

 

まとめ

今回はCinemachineを使ってダークソウルっぽいカメラワーク+移動処理を実装しました。
移動、視点移動、ジャンプ、ロックオン機能にCinemachineの調整でダークソウルっぽい見た目に仕上がったと思います。
3人称視点のゲームであればいろいろ応用できると思うのでぜひ遊び倒してください。

 

 

 

問題点

解決できていない問題点が複数あります

時間がなかったのでそのままです。ごめんなさい

・ジャンプ中に壁と接触すると浮遊する
・カメラが真上付近にあるとプレイヤーが正常に回転できなくなる
・ロックオン中に右スティックでターゲットを切り替える機能がない
・ロックオン対象がいない時にカメラをプレイヤーの向いている方向にリセットする機能がない
・ロックオン中、ターゲットとの距離が離れた時に解除する機能がない
・ロックオンする敵との位置関係によってはプレイヤーが正常に移動できなくなる
・ロックオン用カメラにCinemachineColliderがあると障害物を挟んだ時にカメラが荒ぶる
・ロックオン時に前回のロックオン時の角度を引き継いで一瞬揺れることがある。

 

 

改善点などがありましたらコメントなど気軽にお願いします!
まさかり大歓迎です!

 

 

 

参考資料

キャラクターコントローラー関係

移動に関する処理は↓の動画を参考にしました

youtu.be

 

 

Cinemachine関係

www.youtube.com

 

www.youtube.com

nekojara.city

 

ロックオン機能関係

最初はTargetGroupでやろうとしたがロックオン系には不向きみたいなのでやめました

forum.unity.com

forum.unity.com

www.youtube.com

 

youtu.be

 

ロックオン機能のScriptは↓2つの記事を参考にしました
ChatGPTの力も借りています

gomafrontier.com

qiita.com

 

 

その他

nekojara.city

 

 

 

過去のアドベントカレンダー記事

raspberly.hateblo.jp

raspberly.hateblo.jp

raspberly.hateblo.jp

raspberly.hateblo.jp

 

 

 

以上です。

*1:FreeLook Cameraでも同じようなことはできますが

*2:森丘9を代表に初期作品が顕著

【アセット紹介】ProPixelizerでオブジェクトをピクセル化する【Unity】

今回はアセットの紹介をしていきます。
紹介するのはProPixelizer
3Dオブジェクトをピクセル化するシェーダーアセットです。

 

Humble Bundleのアートバンドルに含まれていたアセットです

どんなアセット?

3Dモデルを2Dピクセル(ドット絵のような見た目)にリアルタイムでレンダリングするアセットです。
専用のシェーダーを使用します。
URP専用WebGLAndroidでも動作します。

特徴
  • オブジェクト単位でのピクセル化により、オブジェクトを低解像度のようにレンダリング
  • 移動するオブジェクトのピクセルクリープが発生しない。
  • オブジェクト単位でのアウトライン制御。
  • リアルタイムの光と影をサポート。
  • オブジェクト単位のカラーグレーディング
  • SRP と ShaderGraph をサポート。
  • 被写界深度、ブルームなどの URP 後処理エフェクトに対応。

 

過去にunity1week online共有会でも紹介されていました

raspberly.hateblo.jp

 

 

アセットの仕組みはブログで解説されています

medium.com

 

 

実行環境

Unity 2021.3.7f1

Universal RP 12.1.7

ProPixlizer ver1.7.1

 

 

インポートの確認

アセットインポート完了時はこんな感じです。

セットアップ

Project Settings/Graphicsから現在設定しているURP Assetを選択

 

Depth Textureを有効にします

 

RednderListにあるRenderer Assetを選択、

 

Renderer FeatureのAdd Renderer Featureを押し、Pixelisation Featureを追加します

 

Cameraの設定

シーン内にあるCameraの設定です。(後述のサンプルシーンとかは最初から設定されています)
Anti-aliasingをオフ、Depth TextureをUse Pipeline Settingsに設定

 

Camera Snap SRPコンポーネントをCameraにアタッチ

 

これで設定は完了。
ちなみに投影方式はどちらでも機能しますが、Orthographicの方がきれいになるようです。

 

 

サンプルシーン

ProPixelizer/ExampleAssetsにサンプルシーンが用意されています

 

Exampleシーンはピクセル化したオブジェクトとしていないオブジェクトが混合したシーン

 

 

Example_NoCreepシーン

 

CameraStackingシーンはURPのカメラスタッキング(いわゆる複数カメラの合成)を使ったシーン
適当にオブジェクトを置いてみるとわかりやすいかも

 

 

 

実際に試してみる

URPのサンプルシーンで試してみます。
今回はこのヘルメットをピクセル化します。

このヘルメットはSafety Hatという名前のオブジェクトで、HardHat_Matマテリアルを使用しています。

 

HardHat_Matマテリアルを編集していきます(念のためバックアップはとっておきましょう)
シェーダーをProPixelizer/SRP/PixelizedWithOutlineに変更、
テクスチャがはがれるので、Appearance/AlbedoとNormalMapにテクスチャを設定。

 

これでヘルメットがピクセル化されました。

 

マテリアルのパラメータPixel Sizeからピクセルの度合いを調整できます。

他にもアウトラインを設定できたりします。

 

 

まとめ

ProPixelizerを使って、3Dオブジェクトをオブジェクト単位でピクセル化しました。
URP専用アセットで、WebGLに対応しているのでUnity1Weekでも利用できます。

 

 

なんかおかしい時

オブジェクトがピクセルではなくディザ抜きされたような見た目になっている場合、
URP Assetのセットアップがうまくいっていない可能性があります

きちんとセットアップしてもこうなっている場合、他のアセットが干渉しているのかもしれません。
このアセットを試す時、最初はいろいろなアセットが入っているプロジェクトにインポートしましたが、どう調整してもうまく表示されなかったので新しいプロジェクトでやりました。(そっちだと問題なく動いた)
もし同じような状態になった場合、プロジェクトの方を疑った方がいいかも。

 

 

 

類似アセットとの比較

過去に画面全体をピクセル化できるStylizer - Extendedというアセットを紹介しました。

raspberly.hateblo.jp

 

Stylizerは画面全体の見栄えを調整するアセットでピクセル化は複数ある機能の一部です。
一方ProPixelizerはピクセル化に特化しており、オブジェクト単位でピクセル化できるという違いがあります。
手っ取り早く全体をピクセル化したいならStylizer、細かい設定がしたいならProPxelizerがいいですね。(所感)

 

 

 

他のアセットの紹介記事はこちら↓

raspberly.hateblo.jp

 

 

 

※本記事にはAssetStoreアフィリエイトリンクが含まれています。

他、間違っている箇所、わかりにくい所がありましたらコメントにお願いします。

【アセット紹介】Animancer Pro でアニメーションを制御する【Unity】

今回はアセットの紹介をしていきます。
紹介するのはAnimancer Pro
Scriptでアニメーションを制御できるようにするアセットです。

 

どんなアセット?

AnimatorControllerを使わず、Scriptで直接アニメーションを制御するアセットです

「Animancer」を使用すれば、追加のセットアップ段階なしで、オンデマンドでアニメーションを再生し、すべての詳細を制御できます。Animator Controllers の主な問題が修正されており、完全に置き換えることも、併用することもできます。これにより、基本的なプロトタイピングから複雑なシステムの維持およびデバッグまで、開発の全ての段階でアニメーション作業をはるかに簡単にします。

各種機能の比較についてはこちら
ちなみにPlayable APIで実装されています

 

過去に「ジスたんファイト」というゲームを開発しましたが、
ゲーム内のアニメーション管理を全てAnimancer Proで行っています

raspberly.hateblo.jp

この時初めて使いましたが、とても使いやすく簡単にゲームに組み込むことができたので、
その忘備録もかねてこの紹介記事を書いていきます

 

 

開発環境

Unity2021.3.7f1
Animancer Pro ver7.3

 

 

インポートの確認

アセットインポート完了時はこんな感じです
Pluginsフォルダの中にインポートされます

コンポーネント、サンプルシーン、アセットのソースコードが含まれています

 

サンプルシーンの確認

Animancer/Examplesの中にサンプルシーンが用意されています
たくさんあり、1つ1つドキュメントも完備しているのでかなり親切!

※URPで開く場合は、ArtフォルダにMaterialがあるので変換しておきましょう

多いのでここでは取り扱いません

 

 

実際に試してみる

AnimancerProを使って、実際にアニメーションをさせてみます
はっきり言って公式ドキュメントが優秀すぎるので詳しくはそっちを見た方がいいです
(どちらかというと未来の自分が読み返してわかるように書いておきます)

 

内容としてはExmaple01~02あたりまで

キャラクターの準備

今回はキャラクターモデルにユニティちゃんを使用します
これはAnimatorがあればなんでもOKです

シーンにUnityChanSSU_DynColを配置し、AnimancerComponentをアタッチします
Animatorが設定されていることを確認しましょう

 

 

AnimationClipの再生

最もシンプルにAnimationClipの再生をします

AnimancerTest.csを作成

using UnityEngine;
using Animancer;

public class AnimancerTest : MonoBehaviour
{
    [SerializeField] AnimancerComponent animancer;
    [SerializeField] AnimationClip animationClip;

    void Start ()
    {
        animancer.Play(animationClip);
    }
}

 

これをUnityChanSSU_DynColにアタッチし、AnimancerにAnimancerComponentを、
AnimationClipに適当な待機アニメーションを設定します

 

これで完成、シーンを再生すると設定した待機アニメーションが再生されます

 

 

入力に応じてAnimationClipを切り替える

プレイヤーの入力に合わせてAnimationClipを切り替えます

AnimancerWalkTest.csを作成

using UnityEngine;
using Animancer;

public class AnimancerWalkTest : MonoBehaviour
{
    [SerializeField] AnimancerComponent animancer;
    [SerializeField] AnimationClip idleClip;
    [SerializeField] AnimationClip walkClip;

    void Start ()
    {
        animancer.Play(idleClip);
    }

    void Update ()
    {
        if(Input.GetKey(KeyCode.W))
        {
            animancer.Play(walkClip);
        }
        else
        {
            animancer.Play(idleClip);
        }
    }
}

 

これをUnityChanSSU_DynColにアタッチし、
適当な待機アニメーションと歩行アニメーションを設定します

 

これで完成、シーンを再生すると待機アニメーションが再生され、Wキーを押している間は歩行アニメーションに切り替わります

 

上のソースコードを見て、Wキー押している間animancer.Playが呼ばれ続けるけど大丈夫?と思うかもしれませんがこの方法で問題ありません
(シンプルにするためこうしていますが、本来は適切なタイミングに1回だけ呼んだ方がいいです)

 

AnimationClipの切り替え時にクロスフェードさせる

animancer.Playを呼ぶことでアニメーションを切り替えられるようになりましたが、
これだとアニメーションが一瞬で切り替わるのでクロスフェードさせながらスムーズに切り替えてみます

 

やり方は簡単で、Playの第二引数にフェード時間を指定するだけです

if(Input.GetKey(KeyCode.W))
{
    animancer.Play(walkClip, 0.3f);
}
else
{
    animancer.Play(idleClip, 0.3f);
}

 

 

 

アニメーションが再生し終わったら別のアニメーションに切り替える

攻撃やジャンプなど、アニメーションが終了した後別のアニメーションに切り替えてみます

AnimancerJumpTest.csを作成

using UnityEngine;
using Animancer;

public class AnimancerJumpTest : MonoBehaviour
{
    [SerializeField] AnimancerComponent animancer;
    [SerializeField] AnimationClip idleClip;
    [SerializeField] AnimationClip jumpClip;

    void Start ()
    {
        animancer.Play(idleClip);
    }

    void Update ()
    {
        if(Input.GetKey(KeyCode.Space))
        {
            var state = animancer.Play(jumpClip, 0.1f);
            state.Events.OnEnd = () => animancer.Play(idleClip, 0.3f);
        }
    }
}

これをUnityChanSSU_DynColにアタッチし、
適当な待機アニメーションとジャンプアニメーションを設定します

 

これで完成、Spaceキーを押すとジャンプし、ジャンプアニメーションが終わったら待機アニメーションに切り替わります

 

 

おまけ 1つのAnimationClipだけを再生したい場合

1つのAnimationClipだけを再生したい(遷移させる必要がない)場合は、
SoloAnimationコンポーネントを使うと簡単です

 

SoloAnimationコンポーネントをアタッチして、
Animator(AnimancerComponentではない)と再生したいAnimationClipを設定するだけ

これでシーン再生時にAnimationClipが再生されます
Scriptを書く必要すらありません

手っ取り早く何か再生したい場合に便利!(ずっと同じ動きをする背景のギミックとかによさそう)
SoloAnimation.IsPlayingをfalseにすると一時停止もできる

 

 

まとめ

Animancer Proを使ってアニメーションの再生、切り替えまでやってみました
基本機能のみでしたが、他にもブレンドの設定、2Dの対応、アニメーション中のイベントの発行からレイヤーまでAnimatorControllerでできていたことはほぼ全て対応可能です。
なんならAnimatorControllerとの併用までできます

 

ジスたんファイトは今回紹介した基本機能だけで実装できています
公式ドキュメントがとても充実しているため、何ができるか知りたい方はそちらもチェックしてみてください

 

ちなみに無料版もあります、ビルドはできませんがエディター内であれば全ての機能を使用できます

 

以上です

 

 

参考資料

qiita.com

 

 

 

他のアセットの紹介記事はこちら↓

raspberly.hateblo.jp

 

 

 

※本記事にはAssetStoreアフィリエイトリンクが含まれています。

他、間違っている箇所、わかりにくい所がありましたらコメントにお願いします。