Unityを使ったゲーム開発記録

Unityのゲーム開発などを中心に投稿していきます。何か参考になる様な記事ではなく自分用の備忘録になる為、生暖かい目で見て下さい。

Unityでのゲーム開発 〜エイムモーション編〜

f:id:mochisakucoco:20210503091741p:plain

武器構え時のモーション変更の実装


今回はエイム時のモーション設定を設定したいと思います。




まずは下準備として前の記事で武器をUpper Chestの子要素から一旦Root外まで戻します。
f:id:mochisakucoco:20210418045253p:plain






武器の角度調整が可能になる様に新しくPivotPointを設定します。

武器のPivotPointの設定

  • 武器内に新しいGameObjectを作成し「WeaponPivot」として位置を調整する
  • Toggle Tool Handle Positionのギズモの位置をCenterからPivotに変更しておく
  • WeaponPivotを武器の子要素から外し
  • 武器をWeaponPivot内の子要素に変更する


ハンドル、ギズモなどの詳細⇩
ゲームオブジェクトの配置 - Unity マニュアル

処理をまとめた物はこちら⇩
f:id:mochisakucoco:20210418045331g:plain

f:id:mochisakucoco:20210418045434g:plain

f:id:mochisakucoco:20210418045534g:plain





RigLayer_WeaponPoseの作成・調整


新しくRigLayerを追加していくやり方


  • 新しいRigLayerを作成するにあたり、現在作成しているRigLayerと区別する為に「RigLayer_HandIK」に名前を変更する
  • Playerにカーソルを合わせて新しくGameObjectを作成し「RigLayer_WeaponPose」にする
  • RigLayer_WeaponPoseのAdd Componentから「Rig」を取り付ける
  • PlayerのRigBuilderの+ボタンで新規の項目を追加
  • RigLayer_WeaponPoseを適用させて「RigLayer_HandIK」との位置を入れ替える

f:id:mochisakucoco:20210418060317g:plain


  • RigLayer_WeaponPoseにカーソルを合わせて新しくGameObjectを作成し「WeaponPose」に名前を変更する
  • WeaponPoseのAdd Componentから「Multi-Parent Constraint」を取り付ける
  • 取り付けたMulti-Parent Constraintの「Constrained Object」にWeaponPivot 「Source Objects」にWeaponPoseを適用する
  • WeaponPivotのTransformをコピーしてWeaponPoseのTransformに貼り付ける(*微調整が必要な場合はプレイモードで確認しながら調整を行う)
  • WeaponPoseのAdd Componentから「Multi-Position Constraint」を取り付ける
  • 取り付けたMulti-Position Constraintの「Constrained Object」にWeaponPose 「Source Objects」に『胸(上部)、武器、右肩、右上腕、右前腕、左肩、左上腕、左前腕』を適用する
  • Multi-Position ConstraintのSettingsの項目を開きMaintain Positionのチェックを外す
  • プレイモードで確認しながらOffsetの位置調整を行う


 

Multi-Parent Constraintのプロパティー

f:id:mochisakucoco:20210430122236p:plain
Multi-Parent Constraintは、階層ウィンドウ内の別のGameObjectの子であるかのように、GameObjectを移動および回転させる物

Weight・・・0に設定すると、GameObjectに影響を与えませんが、1に設定すると、GameObjectに影響を与えます。
Constrained Object・・・制約の影響を受けるGameObject
Source Objects・・・制約されたGameObjectの位置と方向に影響を与えるGameObjectのリスト
Maintain Offset・・・Noneに設定されていない場合は制約されたGameObjectからソースGameObjectへのオフセット(Position、Rotation、またはその両方)を維持するものになります
Constrained Position Axes・・・X、Y、Zをチェックして、コンストレイントが対応する位置軸を制御できるようにします
Constrained Rotation Axes・・・X、Y、Zをチェックして、コンストレイントが対応する回転軸を制御できるようにします





 

Multi-Position Constraintのプロパティー

f:id:mochisakucoco:20210430115702p:plain
位置制約コンポーネントは、GameObjectをそのソースGameObjectに従うように移動する様にできます

Weight・・・0に設定すると、GameObjectに影響を与えませんが、1に設定すると、GameObjectに影響を与えます。
Constrained Object・・・制約の影響を受けるGameObject
Constrained Axes・・・X、Y、Zをチェックすると、コンストレイントが対応する位置軸を制御できるようにできます
Source Objects・・・制約されたGameObjectの位置に影響を与えるGameObjectのリスト
Maintain Position Offset・・・制約されたGameObjectからSource Objectsへの現在の位置オフセットを維持するものになります
Offset・・・Constrained Objectに新しくポスト位置をオフセットする項目 *今回の記事で例えると武器をどの位置に持っていくかの数値になります






RigLayer_WeaponAimingの作成・調整


  • RigLayer_WeaponPoseにカーソルを合わせてコピー(⌘+C)して、貼り付け(⌘+D)でRigLayerを複製する Mac用の操作
  • 複製したものを「RigLayer_WeaponAiming」に名前を変更する
  • PlayerのRigBuilderの項目を増やしRigLayer_WeaponAimingを適用する
  • この際にRigBuilderの並び順を上から[RigLayer_WeaponPose]⇨[RigLayer_WeaponAiming]⇨[RigLayer_HandIK]に並び替える
  • コピーしたRigLayer_WeaponAiming内のWeaponPose(Multi-Position Constraint)のOffset数値を変更(数値などは画面見ながら調整が必要)
  • Multi-Position Constraintの「Source Objects」を『胸(上部)Upper Chest』のみ残して他は削除する


内容が問題なければ下記の様にRigLayer_WeaponAimingの「Rig」コンポーネントを調整して動きの確認が出来ると思います
f:id:mochisakucoco:20210422101313g:plain






 

Multi-Aim Constraintの取り付けと設定

  • 新しくAdd Componentから「Multi-Aim Constraint」を取り付けて位置を入れ替える

f:id:mochisakucoco:20210425155447g:plain

  • 取り付けたMulti-Aim Constraintの「Constrained Object」にWeaponPoseを適用
  • Aim AxisをZからXに変更
  • Main Camera にカーソルを合わせて新しくGameObjectを作成し「AimLookAt」に名前を変更
  • AimLookAtを Multi-Aim Constraintの「Source Objects」に適用して数値を調整する




    

Multi-Aim Constraintのプロパティー

f:id:mochisakucoco:20210502020345p:plain
Multi-Aim Constraintは、GameObjectを回転させてSource Objectsに向けます。これは他のGameObjectを追跡するために使用したりします。目的のベクトル(X、-X、Y、-Y、Z、-Z)に照準軸を指定することで、照準方向を決める事ができます。オプションで、ワールドアップ方向を指定して、Constrained Objectに適用したGameObjectが上向きの位置を維持できるようにすることも可能です

Weight・・・0に設定すると、GameObjectに影響を与えませんが、1に設定すると、GameObjectに影響を与えます。
Constrained Object・・・制約の影響を受けるGameObject
Aim Axis・・・前方方向をSource Objectsに向けるために使用する、Constrained Objectのローカル照準軸を指定する項目
Up Axis・・・上方向をSource Objectsに向けるために使用する、Constrained Objectsのローカル上軸を指定する項目
World Up Type・・・制約されたGameObjectの上方向を維持するために使用するモードを指定できます

  • None・・・World Up Typeを使用しない
  • Scene Up・・・sceneのY軸に適用する
  • Object Up・・・World Up Objectに適用したGameObjectのY軸
  • Object Up Rotation・・・World Up Objectに適用したGameObjectのローカル空間を基準としてWorld Up Vectorを定義する
  • Vector・・・World Up Vectorを使用して、ユーザーが指定したベクトルとして定義する

Source Objects・・・制約されたGameObjectの位置に影響を与えるGameObjectのリスト
Maintain Rotation Offset・・・制約されたGameObjectからSource Objectsへの現在の回転するオフセットを維持する状態にする
Offset・・・Constrained Objectに新しく回転後のオフセットを適用する項目
Constrained Axis・・・X、Y、Zをチェックして、対応する軸を制御できるようにする
Min Limit・・・GameObjectが回転する軸の最小値
Max Limit・・・GameObjectが回転する軸の最大値





RigLayer_BodyAimの作成・調整

  • Playerにカーソルを合わせて新しくGameObjectを作成し「RigLayer_BodyAim」にする
  • RigLayer_BodyAimのAdd Componentから「Rig」を取り付ける
  • PlayerのRigBuilderの+ボタンで新規の項目を追加
  • RigLayer_BodyAimを適用させて「RigLayer_BodyAim」の位置を一番上に替える
  • RigLayer_BodyAimにカーソルを合わせて新しくGameObjectを作成し「AimShoulder_L」にする
  • AimShoulder_LにAdd Componentから「Multi-Aim Constraint」を取り付けるて下記の内容を設定する
  1. Constrained Objectに[LeftShoulder]を適用
  2. Aim Axisを[-Y]に設定
  3. Source Objectsに[AimLookAt]を適用

*上記の設定完了後同じ様な設定を4つ作りたい為、AimShoulder_Lをコピーして4つ複製します、作成したGameObjectを「左上腕、左前腕、左手、頭」の順にConstrained Objectを変更する
f:id:mochisakucoco:20210504163134p:plain

*頭(Head)のSettingsのOffsetの数値をいじっています。


最終的な感じがこちら⇩
f:id:mochisakucoco:20210503084450g:plain



色々なConstraintを使用して動きに制約を加える事が出来るのは理解できたんですが文章にするとややこしくなる:(;゙゚'ω゚'):

Animatorだけでは実装出来ない動きも再現出来たりするので上手く使えばもっと綺麗なモーションに仕上げることも出来ると思います

Unityでのゲーム開発 〜Cinemachine編〜

f:id:mochisakucoco:20210429124041p:plain

カメラをもっと使いやすく

今回はメインカメラにしていたThirdPersonCameraをCinemachineに変更したいと思います。
前の記事でカメラオブジェクトとScriptを適用してキャラクターを追従していましたがエイム機能などを実装するにあたり、こちらの機能の方が使いやすい部分が多いと思い変更していきたいと思います。(´-`).。oO(最初からそっちで実装しろって言うのはなしで


前回やっていたのはこんな感じの物です⇩
mochisakucoco.hatenablog.com
mochisakucoco.hatenablog.com



では早速準備の方からやっていきます。



Cinemachineの準備

f:id:mochisakucoco:20210429045203p:plain

  • PackageManagerのUnity Registryを選択
  • 右の検索欄からCinemachineを検索 *「cin」などで検索範囲は絞れるので全部入力しなくても大丈夫です
  • 右下のInstallからDLすればPC上のメニューバーに表示されると思います *画像ではDL済みな為「Remove」になっています

 

  • ThirdPersonCameraは今回わかりやすくする為に名前を「Main Camera」に変更しました。


 


今回はCinemachineから「Create Virtual Camera」で追従するカメラを作っていきます。
f:id:mochisakucoco:20210429055629p:plain




Create Virtual Cameraを使用すると「CM vcam1」とゆう物が作成されていると思います。
プロパティー内容を見ていきたいと思います
f:id:mochisakucoco:20210429060757p:plain

Status:Live・・・バーチャルカメラを一時的にライブ、非ライブ状態に切り替えることが出来ます
Game Window Guides・・・ゲームビューの構図用のガイドの表示、非表示の切り替えが出来ます(Gameビューの赤い枠や青い枠の事です)
Save During Play・・・Unityの仕様上プレイモードを使用すると変更が保存されないがここにチェックを入れるとプレイモードで変更を加えた場合も保存される様になります。
Priority・・・バーチャルカメラの重要度を示してます。値が高いほど優先度が高くなります。現在表示されているカメラがアクティブから非アクティブになった場合に次の優先度が高いカメラに切り替わります。このプロパティーは、バーチャルカメラを Timeline と併用している場合には無効になります。
Follow・・・Virtual Camera が追跡するターゲットのゲームオブジェクトです。
Look At・・・カメラが照準を合わせるターゲットとなるゲームオブジェクトです。
Lens

  • Field Of View・・・カメラ視野の垂直方向の角度
  • Near Clip Plane・・・カメラから最も近い描画が可能な位置
  • Far Clip Plane・・・描画が行われる、カメラから最も遠い位置
  • Dutch・・・ダッチアングル。カメラを Z 軸方向に傾ける角度

Extensions・・・バーチャルカメラ に追加を加えるコンポーネント



色々と機能がありますが基本的にいじるのは「Follow」「Look At」が多いと思います。
今回はFollowに「Player」に適用します、Look Atは今回は設定しないので空です。





Body(Framing Transposer)の設定

Bodyの項目を「Framing Transposer」を指定します
f:id:mochisakucoco:20210429085717p:plain
Tracked Object Offset・・・ゲームビューでも確認出来る黄色い点の部分の位置調整に使用します
Lookahead Time・・・ターゲットのモーションに応じてFollowターゲットのカメラオフセットを調整します、ノイズが増幅して意図せずにカメラのジッター(揺れ)が発生する場合があり、ターゲットが動いている時に許容範囲を超えるレベルでカメラのジッターが発生する場合は、このプロパティーの設定値を低くしたりして、ターゲットの動きをより滑らかに調整する事ができます
Lookahead Smoothing・・・値を大きくすると、予測によって発生するジッターが緩和され、予測のずれが大きくなります。
Lookahead Ignore Y・・・有効にすると、Lookahead の計算で Y 軸方向の動きを無視します *Ignore(無視とゆう意味)
Damping(X,Y,Z)・・・カメラがどれだけすばやく [X,Y,Z] 軸のオフセットを維持しようと反応するか(追てくるか)、値が小さいほどカメラの反応が素早くなり、値が大きいほど反応が遅くなります。軸ごとに異なる設定を用いることで、幅広いカメラ動作を作り出せます
Target Movement Only・・・これを有効にすると、遅延 (Damping) はターゲットのモーションにのみ適用されます。カメラの回転の変更は遅延 (Damping) を無視されます *カメラもDampさせると画面酔いになる場合もあるのでゲームの種類によって使い分けた方が良いと思います、今回のTPSゲームなどには向いていないのでオンにしておくのをお勧めします
Screen X,Y・・・画面上の水平及び垂直方向位置です。カメラが追従するオブジェクトをこの範囲内に納めこのカメラワークを基準に動きます
Camera Distance・・・カメラとFollowターゲットとの間に保たれるカメラの距離
Dead Zone Width・・・ターゲットがこの範囲内にある場合には、カメラの水平方向の移動が行われません(赤枠の部分)
Dead Zone Height・・・ターゲットがこの範囲内にある場合には、カメラの垂直方向の移動が行われません(赤枠の部分)
Dead Zone Depth・・・Follow ターゲットが、指定した Camera Distance (カメラ距離) の範囲内にある場合には、カメラの Z 軸に沿った移動が行われません。
Unlimited Soft Zone・・・有効にすると、Soft zone のサイズが無限大になります(青枠の部分)
Soft Zone Width・・・ターゲットがこの領域内にある場合、カメラが水平方向に動いてターゲットを Dead zone 内に収めます。Damping プロパティー各種が、カメラの動きの素早さに影響を及ぼします
Soft Zone Height・・・ターゲットがこの領域内にある場合、カメラが垂直方向に動いてターゲットを Dead zone 内に収めます。Damping プロパティー各種が、カメラの動きの素早さに影響を及ぼします
Bias X・・・ターゲットの位置を、Soft zone の中心から離れた方向へ、水平方向に動かします
Bias Y・・・ターゲットの位置を、Soft zone の中心から離れた方向へ、垂直方向に動かします





Aimの(POV)の設定

Aimの項目を「POV」を指定します
f:id:mochisakucoco:20210429085827p:plain
Vertical Axis(縦軸)

  • Value・・・カメラを向ける現在の軸の値 (単位: 度)。設定可能な値の範囲は-90から90
  • Value Range・・・バーチャルカメラの垂直方向の軸の最小値と最大値
  • Wrap・・・これを有効にすると、軸が Value Range の値の周りを輪状に囲みます *数値的に間が補完出来ない場合はワープした様な感じになります
  • Speed・・・軸が入力にどのように応答するか。MaxSpeed (デフォルト) は入力に関係なく、軸が変化できる最高速度を固定します。Input Value Gainは、入力値にSpeedを乗算します
  • Accel Time・・・指定した軸の最大値で、Max Speed まで加速するのに費やす時間 (単位: 秒)
  • Decel Time・・・指定した軸がニュートラル値の場合に、軸が 0 まで減速するのに費やす時間 (単位: 秒)
  • Input Axis Name・・・Unity の Input Manager で設定した軸の名前です。この軸の自動更新を無効化するには、このプロパティーを空の文字列にしてください
  • Input Axis Value・・・入力軸の値。値が 0 の場合、入力が無い状態を意味します。これはカスタムの入力システムから直接操作可能です。あるいは、Input Axis Name を設定し、値を Unity の Input Manager によって操作することもできます
  • Invert・・・これを有効にすると、入力軸の Raw 値を使用前に反転させます

 

Vertical Recentering・・・プレイヤーの入力が検知されない場合に、自動再センタリングされる機能

  • Enabled・・・自動再センタリングを有効にしたい場合は、チェックを入れます
  • Wait Time・・・垂直方向の軸にユーザー入力が検知されなくなった場合に、カメラが再センタリングを実行するまでの待機時間 (単位: 秒)
  • Recentering Time・・・再センタリングの角速度の最大値。この値から加速し、この値まで減速します


Horizontal Axis(横軸)・・・内容はVerticalと同じで軸の違い
Horizontal Recentering・・・内容はVerticalと同じです




カメラを変更した為Scriptを変更する

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

public class Player : MonoBehaviour
{

    private float Horizontal;                               // ←・→、Aキー・Dキーの移動キー
    private float Vertical;                                 // ↑・↓、Wキー・Sキーの移動キー

    private Animator animator;                              // animatorの取得
    private Rigidbody rd;                                   // rigidbodyの取得

    private Vector3 velocity;                               // 移動速度
    private Vector3 input;                                  // Vector3の入力値を格納する
    private Vector3 moveForward;                            // 移動方向の単位ベクトル
    [SerializeField] private float walkSpeed = 2f;          // 歩く速さ
    [SerializeField] private float dashSpeed = 3.5f;         // 走る速さ

    [SerializeField] private bool isGrounded;               // 地面に接地しているかどうか
    [SerializeField] private Transform landRay;             // 地面との距離を測るレイを飛ばす位置
    [SerializeField] public float groundDistance = 1f;      // 地面から離れたと検知する距離
    [SerializeField] public float landDistance = 1f;        // 着地モーションに移行する距離
    [SerializeField] private Transform stepRay;             // 段差を昇る為のレイを飛ばす位置
    [SerializeField] private float stepDistance = 0.5f;     // レイを飛ばす距離
    [SerializeField] private float stepOffset = 0.3f;       // 昇れる段差
    [SerializeField] private float slopeLimit = 65f;        // 昇れる角度
    [SerializeField] private float slopeDistance = 1f;      //昇れる段差の位置から飛ばすレイの距離
    int layerMask;                                          // Playerレイヤー以外のレイヤーマスク

    Quaternion targetRotation;


    /// <summary> 
    /// 開始時に呼び出される物
    /// </summary>
    void Start()
    {
        animator = GetComponent<Animator>();    // Animatorコンポーネントの取得
        rd = GetComponent<Rigidbody>();         // Rigidbodyコンポーネントの取得
        layerMask = ~(1 << LayerMask.NameToLayer("Player"));

        targetRotation = transform.rotation;
    }


    /// <summary>
    /// 毎フレーム呼び出される物
    /// </summary>
    void Update()
    {

        var horizontal = Input.GetAxisRaw("Horizontal");
        var vertical   = Input.GetAxisRaw("Vertical");
        var horizontalRotation = Quaternion.AngleAxis(Camera.main.transform.eulerAngles.y, Vector3.up);
        var input = horizontalRotation * new Vector3(horizontal, 0f, vertical).normalized;
        var speed = Input.GetKey(KeyCode.LeftShift) ? 2: 1;
        var rotationSpeed = 600 * Time.deltaTime;


        /// <summary> キャラクターが接地している場合の処理 </summary>
        if (isGrounded)
        {
            // 接地したので移動速度を0にする
            animator.SetBool ("Fall", false);
            velocity = Vector3.zero;

            /// <remarks> 方向キーが押されている時の移動方向・移動処理 </remarks>
            if (input.magnitude > 0.5f)
            {
                targetRotation = Quaternion.LookRotation(input, Vector3.up);
            }
                animator.SetFloat("Speed", input.magnitude * speed, 0.1f, Time.deltaTime);

                transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotationSpeed);

            /// <remarks> 段差を昇る時の処理 </remarks>
            if (input.magnitude > 0f)
            {

                /// <summary> ステップ用のレイが地面に接触しているか検知して処理を行う </summary>

                // コンソールに昇れる段差を表示
                Debug.DrawLine(transform.position + new Vector3(0f, stepOffset, 0f), transform.position + new Vector3(0f, stepOffset, 0f) + transform.forward * slopeDistance, Color.green);

                /// <remarks> 進行方向の地面の角度が指定以下、または昇れる段差より下だった場合の移動処理 </remarks>
                if (Physics.Linecast(stepRay.position, stepRay.position + stepRay.forward * stepDistance, out var stepHit, LayerMask.GetMask("Field", "Block")))
                    {
                        if (Vector3.Angle(transform.up, stepHit.normal) <= slopeLimit || (Vector3.Angle(transform.up, stepHit.normal) > slopeLimit 
                        && !Physics.Linecast(transform.position + new Vector3(0f, stepOffset, 0f), transform.position + new Vector3(0f, stepOffset, 0f) + transform.forward * slopeDistance, LayerMask.GetMask("Field", "Block"))))
                    {
                        velocity = new Vector3(0f, (Quaternion.FromToRotation(Vector3.up, stepHit.normal) * transform.forward * walkSpeed).y, 0f) + transform.forward * walkSpeed;
                        Debug.Log(Vector3.Angle(transform.up, stepHit.normal));
                    } else {
                        velocity += transform.forward * walkSpeed;
                    }
                    Debug.Log(Vector3.Angle(Vector3.up, stepHit.normal));

                /// <remarks> 地面に接している状態でのaimCamera(active、非active)での移動 </remarks>
                } else {
                    velocity += transform.forward * walkSpeed;
                }

                /// <remarks> キーを押していない場合は移動しない </remarks>
                } else {
                    animator.SetFloat("Speed", 0f);
                }
        }
    }

    /// <summary>
    /// 固定フレーム呼び出される物
    /// </summary>
    void FixedUpdate()
    {
        /// <remarks> 通常時のキャラクターを移動させる処理 </remarks>
        rd.MovePosition(transform.position + velocity * walkSpeed * Time.fixedDeltaTime);

        /// <remarks> シフトを押された時のキャラクターを移動させる処理 </remarks>
        if (Input.GetKey(KeyCode.LeftShift))
        {
            rd.MovePosition(transform.position + velocity * dashSpeed * Time.fixedDeltaTime);
        }
    }



    private void OnCollisionEnter(Collision collision)
    {
        /// <summary>
        /// 地面に着地した時の処理
        /// </summary>
        Debug.DrawLine(landRay.position, landRay.position + Vector3.down * landDistance, Color.blue);
        if (Physics.Raycast (transform.position, transform.position + Vector3.down * landDistance, LayerMask.GetMask ("Field","Block")))
        {
            isGrounded = true;
            // animator.SetBool ("Jump", false);
            velocity.y = 0f;
            animator.SetBool ("Landing", true);
            animator.SetBool ("Fall", false);
        }
    }

    private void OnCollisionExit(Collision collision)
    {
        /// <summary>
        /// 地面から降りた時の処理
        /// </summary>
        if (1 << collision.gameObject.layer != layerMask)    // Fieldレイヤーのゲームオブジェクトから離れた時
        {
            Debug.DrawLine(landRay.position, landRay.position + Vector3.down * groundDistance, Color.red);
            /// <remarks>下向きにレイヤーを飛ばしFieldレイヤーと接触しなければ地面から離れたと判定する </remarks>
            if (!Physics.Linecast(transform.position + Vector3.up * 0.2f, transform.position + Vector3.down * 0.1f, LayerMask.GetMask("Field", "Block")))
            {
                isGrounded = false;
                animator.SetBool ("Fall", true);
                animator.SetBool ("Landing", false);
            }
        }
    }
}







これでカメラワークの準備が出来たのでエイム調整もしやすくなたっと思います
f:id:mochisakucoco:20210429121921g:plain

Unityでのゲーム開発 〜UI編〜

今回はUI(ユーザーインターフェース)機能を実装してみようと思います。
Unityではよくあるゲーム開始時の[New Game]や[Load Game],[Setting]などの他にもCanvasとゆう内に配置された物をUI要素として呼ばれています。
docs.unity3d.com


今回の実装はシーン内に画像などを配置した演出効果的なものをいじってみようと思います。
ゲームスタートなどのシーンUIは別で記事にしたいと思っています。




そもそもUIってなんなの?⇩
ユーザインタフェース - Wikipedia


Canvasの設置

  • Hierarchy内で右クリックをしてUIを選択
  • UI内部のCanvasを選択

これで完了です

f:id:mochisakucoco:20210414053846p:plain

Scene内に作成していない場合にUI項目の中からCanvas以外の物を選択してもCanvasは自動生成されます。結果的にCanvasの子要素でないとUIが機能しない為だと思います。
面倒臭い時はText、Imageなどを先に生成して勝手にCanvasを生成してもらった方が手っ取り早いでしょうね(*´ω`*)





Imageを利用して画面内に画像表示させる


今回使用する物はImageで武器のアイコンやダメージの演出を表示したいと思います。

今回使用する物は一応フリー素材として検索した画像や加工した画像になります。
それでは早速準備と配置していきましょう。

下準備

*和テイストが好きなので『墨』とゆうワードで探しました

  • 下準備として画像を透明化処理をする

画像をそのまま配置したら周りの白い部分まで一緒に表示されてしまうので先に処理が必要になります
IllustratorPhotoshopを使える方はすぐ出来るかと思いますが、あまりそういったツールを使った事がない人でも、無料で透明化処理が出来るサイトなどもあるのでそれを活用すると良いと思います。

無料透明化サイトのリンク⇩
WEBブラウザ上で簡単に透過PNG画像を作成できるツール | 無料で画像を加工できるサイト PEKO STEP
画像背景 透過・透明(一部、部分的に透明にできます) | 無料オンラインフリーソフト



処理が終わったらProjectに組み込んでScene内に配置出来る様にします。

画像のSprite化

Imageを実装する前に加工した画像をSprite化してUIとして表示させる様に変更したいと思います。

  1. Projectタブ内に新規フォルダを作成して「UI Image」や「Image」などわかりやすい様しておく
  2. フォルダが完成したらPC内に保存している画像をフォルダ内にドラック&ドロップする
  3. Texture Typeを「Sprite(2D and UI)」に変更
  4. Applyして変更内容を適用する

f:id:mochisakucoco:20210414065150p:plain

これでUIのImageとして配置出来る様になります。




Image配置とサイズ変更

上記の工程で画像の処理が終わったので実際にSceneに配置してサイズ感を調整したいと思います。

  • Canvasを作成した時と同じくUIの項目からImageを選択
  • Source Imageに画像をドラック&ドロップする
  • サイズの変更が必要な場合はRect Transformの「Width」「Height」を変更でサイズ変更可能

*色も変更可能なので好きな色に変更しても良いと思います、α(アルファ)値を下げれば透明度も上がるので項目によってカスタマイズも可能。

サイズ感などは使用したイメージによっても変わるのでScene内の配置とPlayモードを繰り返して配置すると良いと思います。
f:id:mochisakucoco:20210414072514p:plain




ダメージ演出と照準の配置

今回は体力ゲージを実装しないで画面にダメージを受けた時の演出として画像で代用できたらと思っています。
*注)最終的な実装は未定なので現状の案です

下記のサイトより血飛沫の画像をお借りしました。⇩
commons.nicovideo.jp
使用には会員登録(無料)が必要になります、当記事では使用フリーなものを選んでおります。
*サイト内は全て使用許可が不要ではない為、利用する際は必ず投稿者様の注意書きを読んでいただく様お願いいたします。


今回は以下の6作品をお借りしました。_:(´ཀ`」 ∠):
f:id:mochisakucoco:20210414074521p:plain

お借りした画像は透明化処理もされていてすぐ使えるのでImageフォルダに取り込んでSpriteをすれば配置できます。



実装イメージは⇩の様な感じで段階的に表示される様にしたいと思っています、ダメージに伴ってアクションも変更出来たらと思ってます・・・出来るとは言っていないw

ダメージ1段階(軽症)
f:id:mochisakucoco:20210414075615p:plain
ダメージ2段階(重症)
f:id:mochisakucoco:20210414075636p:plain
ダメージ3段階(瀕死)
f:id:mochisakucoco:20210414075657p:plain




演出の際に原色だと画面が見えなくなってしまう為、α値を変更して見えやすくしてます
色の設定は⇩
f:id:mochisakucoco:20210414080002p:plain





照準の配置

これは特にやる事がない為さっくり説明します。

  • 上記のやり方でImageを新規作成
  • Source Imageの横にある◎から「Konb」を選択
  • Rect Transformの「Width」「Height」を両方とも7にサイズ変更
  • Positionは移動してもズレない様にデフォルトの0のまま変更しない

*カスタムしたい場合はカーソルの画像とかを透明化して実装しても良いと思います。
f:id:mochisakucoco:20210414081227p:plain



武器のアイコン化

最後に武器のアイコン化です。

f:id:mochisakucoco:20210414082316g:plain

  • 上記の様に武器の3Dモデルのプレビューを表示して画像を横向きに変更
  • 画面のSS(MACの場合:⌘+⇧+3)を撮って、当記事の一番上のサイトなどで画像を加工して透明化させる
  • Sprite化
  • Imageとして配置



今回はここまで実際に画像だけ追加するだけでも少しぽくはなるかなと思います。

Unityでのゲーム開発 〜キャラクターに武器を持たせる編〜

今回は武器持たせてみようと思います。

今回使用するAssetはこちら⇩
assetstore.unity.com
無料Assetなのにこのクオリティはヤバいです(´-`).。oO(語彙力)


早速ですが下準備から初めて行こうと思います。

  • まず使用したい武器をAssetStoreからDL
  • Unityの標準で用意されている「Animation Rigging」をInstall

f:id:mochisakucoco:20210411081416p:plain


  • DLした武器をScene内に配置

f:id:mochisakucoco:20210411081734p:plain

デカすぎ!

*Assetによって大きさが違うと思いますので適切な大きさに変更した方が良いですね
今回はScale値を『X:0.1Y0.1Z0.1』に設定しています。

ゲーム画面を確認するとこんな感じです⇩
f:id:mochisakucoco:20210411082857p:plain

適宜変更していくと良いと思います。





 
これをPlayerの親子関係にすれば武器を持たせるのは完了です。
f:id:mochisakucoco:20210411084004g:plain

スミマセン嘘です:(;゙゚'ω゚'):
これで下準備は完了です。



Animation Riggingを使って武器を持たせる

作業工程1

  1. PlayerにAdd Componentから「Rig Builder」を取り付ける
  2. Playerの子要素として空のGameObjectを作成して「RigLayer」に名前を変更する
  3. 先ほど作成したRigLayerにAdd Componentから「Rig」を取り付ける
  4. Playerの「Rig Builder」に子要素のRigLayerを適用する
  5. RigLayerの子要素として空のGameObjectを作成して「RightHandIK」に名前を変更する
  6. 先ほど作成したRightHandIKにAdd Componentから「Two Bone IK Constraint」を取り付ける
  7. RightHandIKのTwo Bone IK Constraintコンポーネント内の[Root]に右肩、[Mid]に右肘、[Tip]に右手、Source ObjectsのTargetに武器を取り付ける

*Player⇨root⇨Hips⇨Spineの順に展開していけば見つかると思います、使用しているAssetキャラクターによって違う事が殆どの為展開して確認が◯です。


f:id:mochisakucoco:20210411085109p:plain


❷~❸

f:id:mochisakucoco:20210411091151g:plain

 

f:id:mochisakucoco:20210411091253p:plain

 

❺~❻

f:id:mochisakucoco:20210411092150g:plain

f:id:mochisakucoco:20210411100948p:plain


工程1での出来上がりはこんな感じ⇩
f:id:mochisakucoco:20210411101629g:plain

しっかりと武器が持てていないので変ですね_:(´ཀ`」 ∠):
武器を持てる様に微調整します。


作業工程2

  1. 武器の子要素として空のGameObjectを作成して「RightHandGrip」に名前を変更する
  2. 先程のRightHandIKのTwo Bone IK Constraint⇨Source ObjectsのTargetに「RightHandGrip」を取り付ける
  3. 武器のAdd Componentから「Rig Transform」を取り付ける
  4. RightHandIKにカーソルを合わせて⌘+Cでオブジェクトをコピーしてそのまま⌘+Dで貼り付ける「RightHandIK(1)」とゆう名前で複製されるハズなので「LeftHandIK」に変更する
  5. LeftHandIKのTwo Bone IK Constraintコンポーネント内の[Root]に左肩、[Mid]に左肘、[Tip]に左手を取り付ける
  6. 武器の子要素として作成した「RightHandGrip」にカーソルを合わせて⌘+Cでオブジェクトをコピーしてそのまま⌘+Dで貼り付ける「RightHandGrip(1)」とゆう名前で複製されるハズなので「LeftHandGrip」に変更する
  7. LeftHandIKのTwo Bone IK Constraintコンポーネント内Source ObjectsのTargetに「LeftHandGrip」を取り付ける
  8. Playerの子要素にしていた武器を「Upper Chest」の子要素に変更する
  9. LeftLowerArmの子要素として空のGameObjectを作成して「HintL」に名前を変更する
  10. RightLowerArmの子要素として空のGameObjectを作成して「HintR」に名前を変更する
  11. それぞれのHintをLeft用、Right用 Two Bone IK Constraintコンポーネント内Source ObjectsのHintに取り付ける


それぞれのTransformの数値などは下記の画像を貼ります、キャラクターAssetによって微調整必須です

RightHandIK

f:id:mochisakucoco:20210411133544p:plain

LeftHandIK

f:id:mochisakucoco:20210411133603p:plain

RightHandGrip

f:id:mochisakucoco:20210411133626p:plain

LeftHandGrip

f:id:mochisakucoco:20210411133643p:plain

HintR

f:id:mochisakucoco:20210411133704p:plain

HintL

f:id:mochisakucoco:20210411133716p:plain

武器のTransform

f:id:mochisakucoco:20210411133731p:plain



出来上がりはこちら⇩
f:id:mochisakucoco:20210411140918g:plain


これを武器種によって変更するともっとややこしくなりそう:(;゙゚'ω゚'):
Assetを提供している人とか本当に尊敬します!!



次回はチラチラ画面に写っているUIの設定をいじれたらと思います。

Unityでのゲーム開発 〜落下処理、着地処理とおまけ編〜

今回の実装は前回落下時のMotion中に空中を歩いてしまう状態だった為、そちらの変更と着地時のMotionを設定したいと思います。

落下モーションの設定

早速Animatorを設定していきます。
f:id:mochisakucoco:20210407235143p:plain

  • FallのStateを新規で作成
  1. Locomotion⇨FallにMake Transitionで繋ぐ
  2. FallStateのMotion(Jump_Up_A_Loop)に設定
  3. Parametersの設定にBool型を作成して名前を「Fall」に変更
  4. Transitionの設定にHas Exit Timeのチェックを外す
  5. Intrruption Sourceを「Next State」に変更
  6. ConditionsをFallを選択してtrueに変更

 

着地モーションの設定

f:id:mochisakucoco:20210408003236p:plain

  • LandingのStateを新規で作成
  1. Fall⇨LandingにMake Transitionで繋ぐ
  2. LandingStateのMotion(Land_Spawn_Wait)に設定
  3. Foot IKにチェックを入れる
  4. Parametersの設定にBool型を作成して名前を「Landing」に変更
  5. Transitionの設定にHas Exit Timeのチェックを外す
  6. Intrruption Sourceを「Next State」に変更
  7. ConditionsをLandingを選択してtrueに変更


 

  1. Landing⇨ExitにMake Transitionで繋ぐ
  2. Transitionの設定にHas Exit Timeのチェックをしたままにする
  3. Intrruption Sourceを「Next State」に変更
  4. ConditionsをLandingを選択してfalseに変更
  5. Landingスクリプトを取り付ける(下に記述する物)




Landing用のビヘイビアを作成


StateMachineのビヘイビアを作成してLandingに貼り付ける。
これをつける理由としては着地アニメーションをある程度再生してからLocomotionに派生させたいからです

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

public class Landing : StateMachineBehaviour
{
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        // 動かしていない時はある程度再生してからLandingをfalseにする
        if (animator.GetCurrentAnimatorStateInfo (0).normalizedTime >= 0.9f)
        {
            animator.SetBool ("Landing", false);
        }
    }
}


normalizedTime >= 0.9f・・・normalizedTimeはアニメーション開始時を0・再生後を1とするようにアニメーションの長さを正規化したものな為、今回の設定ではアニメーションが9割再生されたらLandingを終了させる様な設定になっています。

⇩参考記事
Unity5 MecanimのStateMachineBehaviourと戯れる - テラシュールブログ


Stateビヘイビアの適用

f:id:mochisakucoco:20210408225011p:plain
Stateの適用の仕方は上記の様に付けたいStateを選択して[Add Behaviour]からScriptを選択すれば適用可能です。





スクリプトの変更

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

public class Player : MonoBehaviour
{

    [SerializeField] private GameObject aimCamera;          // GameObjectのエイムカメラを紐づけるField
    [SerializeField] private GameObject slideCamera;        // GameObjectのスライドカメラを紐づけるField
    [SerializeField] private GameObject thirdPersonCamera;  // GameObject三人称視点カメラを紐づけるField

    private float Horizontal;                               // ←・→、Aキー・Dキーの移動キー
    private float Vertical;                                 // ↑・↓、Wキー・Sキーの移動キー

    private Animator animator;                              // animatorの取得
    private Rigidbody rd;                                   // rigidbodyの取得

    private Vector3 velocity;                               // 移動速度
    private Vector3 input;                                  // 入力値を格納する
    private Vector3 moveForward;                            // 移動方向の単位ベクトル
    [SerializeField] private float walkSpeed = 4f;          // 歩く速さ
    [SerializeField] private float jumpPower = 5f;          // ジャンプ力

    [SerializeField] private bool isGrounded;               // 地面に接地しているかどうか
    [SerializeField] private Transform landRay;             // 地面との距離を測るレイを飛ばす位置
    [SerializeField] public float groundDistance = 1f;      // 地面から離れたと検知する距離
    [SerializeField] public float landDistance = 1f;        // 着地モーションに移行する距離
    [SerializeField] private Transform stepRay;             // 段差を昇る為のレイを飛ばす位置
    [SerializeField] private float stepDistance = 0.5f;     // レイを飛ばす距離
    [SerializeField] private float stepOffset = 0.3f;       // 昇れる段差
    [SerializeField] private float slopeLimit = 65f;        // 昇れる角度
    [SerializeField] private float slopeDistance = 1f;      //昇れる段差の位置から飛ばすレイの距離
    int layerMask;                                          // Playerレイヤー以外のレイヤーマスク


    /// <summary> 
    /// 開始時に呼び出される物
    /// </summary>
    void Start()
    {
        animator = GetComponent<Animator>();    // Animatorコンポーネントの取得
        rd = GetComponent<Rigidbody>();         // Rigidbodyコンポーネントの取得
        layerMask = ~(1 << LayerMask.NameToLayer("Player"));
    }


    /// <summary>
    /// 毎フレーム呼び出される物
    /// </summary>
    void Update()
    {


        Horizontal = Input.GetAxisRaw("Horizontal");
        Vertical   = Input.GetAxisRaw("Vertical");


        /// <summary> キー入力でカメラ切り替え </summary>
        if (Input.GetKeyDown(KeyCode.B))
        {
            aimCamera.SetActive(!aimCamera.activeInHierarchy);
            thirdPersonCamera.SetActive(!thirdPersonCamera.activeInHierarchy);
        }


        /// <summary> キャラクターが接地している場合の処理 </summary>
        if (isGrounded)
        {
            // 接地したので移動速度を0にする
            animator.SetBool ("Fall", false);
            velocity = Vector3.zero;
            input = new Vector3(Horizontal, 0f, Vertical);


            /// <remarks> 方向キーが押されている時 </remarks>
            if (input.magnitude > 0f)
            {

                // キーを押されている間のAnimator(parameter)の速度の変動によってのMotion変化
                var speed = Mathf.Abs(input.magnitude * 2.5f);
                animator.SetFloat("Speed", speed, 2f, Time.deltaTime);


            /// <summary> キャラクターがスライディングした時の処理 </summary>
            if (Input.GetKeyDown(KeyCode.C))
            {
                slideCamera.SetActive(!slideCamera.activeInHierarchy);
                thirdPersonCamera.SetActive(!thirdPersonCamera.activeInHierarchy);
                /// <remarks> Cキーが押された時にカメラを切り替えてanimatorを起動する </remarks>
                if(slideCamera.activeInHierarchy)
                {
                    animator.SetBool("Slide", true);
                } else if(!slideCamera.activeInHierarchy){
                    animator.SetBool("Slide", false);
                }
            }


                /// <summary> ステップ用のレイが地面に接触しているか検知して処理を行う </summary>

                // コンソールに昇れる段差を表示
                Debug.DrawLine(transform.position + new Vector3(0f, stepOffset, 0f), transform.position + new Vector3(0f, stepOffset, 0f) + transform.forward * slopeDistance, Color.green);

                /// <remarks> 進行方向の地面の角度が指定以下、または昇れる段差より下だった場合の移動処理 </remarks>
                if (Physics.Linecast(stepRay.position, stepRay.position + stepRay.forward * stepDistance, out var stepHit, LayerMask.GetMask("Field", "Block")))
                    {
                        if (Vector3.Angle(transform.up, stepHit.normal) <= slopeLimit || (Vector3.Angle(transform.up, stepHit.normal) > slopeLimit 
                        && !Physics.Linecast(transform.position + new Vector3(0f, stepOffset, 0f), transform.position + new Vector3(0f, stepOffset, 0f) + transform.forward * slopeDistance, LayerMask.GetMask("Field", "Block"))))
                    {
                        velocity = new Vector3(0f, (Quaternion.FromToRotation(Vector3.up, stepHit.normal) * transform.forward * walkSpeed).y, 0f) + transform.forward * walkSpeed;
                        Debug.Log(Vector3.Angle(transform.up, stepHit.normal));
                    } else {
                        velocity += transform.forward * walkSpeed;
                    }
                    Debug.Log(Vector3.Angle(Vector3.up, stepHit.normal));

                /// <remarks> 地面に接している状態でのaimCamera(active、非active)での移動 </remarks>
                } else if (!aimCamera.activeInHierarchy){
                    velocity += transform.forward * walkSpeed;
                } else if (aimCamera.activeInHierarchy){
                    Vector3 lookForwad = aimCamera.transform.forward;
                    lookForwad.y = 0f;
                    transform.rotation = Quaternion.LookRotation(moveForward);
                    velocity += input * 2f;
                }

                /// <remarks> キーを押していない場合は移動しない </remarks>
                } else {
                    animator.SetFloat("Speed", 0f);
                }

            /// <summary> ジャンプの処理 </summary>
            if (Input.GetButtonDown("Jump")
                && !animator.GetCurrentAnimatorStateInfo(0).IsName("Jump")
                && !animator.IsInTransition(0))      // 遷移途中にジャンプさせない条件
            {
                animator.SetBool("Jump", true);
                isGrounded = false;                  // ジャンプしたら接地していない状態にする
                velocity.y += jumpPower;
            }
        }
    }

    /// <summary>
    /// 固定フレーム呼び出される物
    /// </summary>
    void FixedUpdate()
    {
        // キャラクターを移動させる処理
        rd.MovePosition(transform.position + velocity * Time.fixedDeltaTime);

        // カメラの方向から、X-Z平面の単位ベクトルを取得
        Vector3 cameraForward = Vector3.Scale(thirdPersonCamera.transform.forward, new Vector3(1, 0, 1)).normalized;

        // 方向キーの入力値とカメラの向きから、移動方向を決定
        Vector3 moveForward = cameraForward * Vertical + thirdPersonCamera.transform.right * Horizontal;

        // 移動方向にスピードを掛ける。ジャンプや落下がある場合は、別途Y軸方向の速度ベクトルを足す。
        velocity = moveForward * walkSpeed + new Vector3(0, velocity.y, 0);

        // キャラクターの向きを進行方向にする
        if (moveForward != Vector3.zero)
        {
            transform.rotation = Quaternion.LookRotation(moveForward);
        }
    }



    private void OnCollisionEnter(Collision collision)
    {
        /// <summary>
        /// 地面に着地した時の処理
        /// </summary>
        Debug.DrawLine(landRay.position, landRay.position + Vector3.down * landDistance, Color.blue);
        if (Physics.Raycast (landRay.position, landRay.position + Vector3.down * landDistance, LayerMask.GetMask ("Field","Block")))
        {
            isGrounded = true;
            animator.SetBool ("Jump", false);
            velocity.y = 0f;
            animator.SetBool ("Landing", true);
            animator.SetBool ("Fall", false);
        }
    }

    private void OnCollisionExit(Collision collision)
    {
        /// <summary>
        /// 地面から降りた時の処理
        /// </summary>
        if (1 << collision.gameObject.layer != layerMask)    // Fieldレイヤーのゲームオブジェクトから離れた時
        {
            Debug.DrawLine(landRay.position, landRay.position + Vector3.down * groundDistance, Color.red);
            /// <remarks>下向きにレイヤーを飛ばしFieldレイヤーと接触しなければ地面から離れたと判定する </remarks>
            if (!Physics.Linecast(transform.position + Vector3.up * 0.2f, transform.position + Vector3.down * 0.1f, LayerMask.GetMask("Field", "Block")))
            {
                isGrounded = false;
                animator.SetBool ("Fall", true);
                animator.SetBool ("Landing", false);
            }
        }
    }
}


追記部分

[SerializeField] private Transform landRay;             // 地面との距離を測るレイを飛ばす位置
[SerializeField] public float groundDistance = 1f;      // 地面から離れたと検知する距離
[SerializeField] public float landDistance = 1f;        // 着地モーションに移行する距離

これは必須ではないのですが「Debug.DrawLine」で可視化する際に使用しています。
landRayは前の記事で作成したstepRayのポイントをコピーして名前を変えただけになります。





地面から離れた時の処理

private void OnCollisionExit(Collision collision)
    {
        /// <summary>
        /// 地面から降りた時の処理
        /// </summary>
        if (1 << collision.gameObject.layer != layerMask)    // Fieldレイヤーのゲームオブジェクトから離れた時
        {
            Debug.DrawLine(landRay.position, landRay.position + Vector3.down * groundDistance, Color.red);
            /// <remarks>下向きにレイヤーを飛ばしFieldレイヤーと接触しなければ地面から離れたと判定する </remarks>
            if (!Physics.Linecast(transform.position + Vector3.up * 0.2f, transform.position + Vector3.down * 0.1f, LayerMask.GetMask("Field", "Block")))
            {
                isGrounded = false;
                animator.SetBool ("Fall", true);
                animator.SetBool ("Landing", false);
            }
        }
    }
  • 地面と離れたらisGroundedをオフにする
  • AnimatorのFallをオンにしてアニメーションを再生させる
  • AnimatorのLandingをオフにする(着地時にオンにさせる為)


  

地面に着地した時の処理

private void OnCollisionEnter(Collision collision)
    {
        /// <summary>
        /// 地面に着地した時の処理
        /// </summary>
        Debug.DrawLine(landRay.position, landRay.position + Vector3.down * landDistance, Color.blue);
        if (Physics.Raycast (landRay.position, landRay.position + Vector3.down * landDistance, LayerMask.GetMask ("Field","Block")))
        {
            isGrounded = true;
            animator.SetBool ("Jump", false);
            velocity.y = 0f;
            animator.SetBool ("Landing", true);
            animator.SetBool ("Fall", false);
        }
    }
  • 地面に接触したらisGroundedをオンにする
  • AnimatorのLandingをオンにしてアニメーションを再生させる
  • AnimatorのFallをオフにする


出来上がったのがこちら⇩⇩⇩
f:id:mochisakucoco:20210408225632g:plainf:id:mochisakucoco:20210408225759g:plain

ん?キャラが変わってるって(´-`).。oO気のせいです
キャラメイク画面とか作った時に使えそうだったので購入しました。


assetstore.unity.com
私が購入した時は9$くらいだったんですがセールの対象とかになってたのかな?
AssetStoreも定期的にチェックした方が良いですね:(;゙゚'ω゚'):




 

おまけ

f:id:mochisakucoco:20210409063945g:plain
スライディングを実装してみました・・・上のScriptでも記載はしていますがCキーを押してON・OFFの切り替えと共にスライディング用のカメラに切り換える処理をしているのですが、これは後々の実装で使うかどうか迷っているので本格的に必要になったらいじろうと思います。




 
カメラのTransformはこちら⇩
f:id:mochisakucoco:20210409064834p:plain

Unityでのゲーム開発 〜Blend Tree編〜

Blend Tree

Blend Treeは前回やったAnimatorのモーションとモーションをBlend(混ぜ合わせて)してモーションの近い動き同士で繋ぎ合わせて滑らかな動きを実現できる機能です。

なら前回やった奴も全部Blend Treeで良いじゃんってなりそうですが・・・:(;゙゚'ω゚'):
Transition(遷移)とBlend Treeは区別して実装するのがベターとなっています。

docs.unity3d.com




Blend Treeの作成

作成方法はNewStateと同じく

  • [Create State]⇨[From New Blend Tree]でBlend Treeが作成完了です
  • Blend Treeの作成完了したらダブルクリックをすると編集可能になります

f:id:mochisakucoco:20210324224724p:plain





Blend Treeの編集

f:id:mochisakucoco:20210324225134p:plain

  • ❶はBlend Typeになっていて1Dと2Dの選択が出来ます、今回は1Dタイプを選択しています

⇩⇩ブレンドタイプは下記のリファレンスで確認できます⇩⇩
1D ブレンディング - Unity マニュアル
2D ブレンディング - Unity マニュアル
 

  • ❷はMotionもしくはBlendTreeのフィールドを追加する際に使用します

*BlendTreeの中にさらにBlendTreeを作成する事も可能です
 

*上の画像ではMotionの横の項目の所ですね、チェックボックスにチェックが入っていると灰色になって編集できない様になっているのですぐわかるかと思います。

閾値(しきい値)って何?:(;゙゚'ω゚'):・・・簡単に言うとギリギリの境界線みたいな感じだと思って貰えれば良いと思います。

https://wa3.i-3-i.info/word15001.html閾値の説明はこちらがわかりやすいとおもいます

上の図で説明するのであれば「Idle状態は0~0.4999...まで」、「Walk状態は0.5~1.9999...まで」、「Dash状態は2~」で遷移するモーションの切り変わる境界線とゆうことになります。



今回は上記の内容でMotionとThresholds設定を行いました・・・画像のみで説明とか...楽するな(´-`).。oO


Animatorの初期StateをBlend Treeに変更

今回作成したBlend Treeを初期Stateに変更するやり方を説明します。

  • まずBase Layerで前の画面に戻り
  • Blend Treeの名前をLocomotion[移動とゆう意味]に変更します(分かりやすくする為なので必須ではありません)
  • Locomotionにカーソルを合わせて右クリックで[Set as Layer Default State]にすると初期の状態をLocomotionに変更可能になります。

f:id:mochisakucoco:20210325022128g:plain




Thresholds(閾値)を可変させる為のScript変更

    void Update()
    {

        Horizontal = Input.GetAxisRaw("Horizontal");
        Vertical   = Input.GetAxisRaw("Vertical");

        /// <summary>
        /// 対応したキーを押されたらカメラを切り換える処理
        /// </summary>
        if (Input.GetKeyDown(KeyCode.B))
            {
                aimCamera.SetActive(!aimCamera.activeInHierarchy);
                thirdPersonCamera.SetActive(!thirdPersonCamera.activeInHierarchy);
            }

        /// <summary>
        /// キャラクターが接地している場合の処理
        /// </summary>
        if (isGrounded)
            {
            // 接地したので移動速度を0にする
            velocity = Vector3.zero;
            input = new Vector3(Horizontal, 0f, Vertical);


            // 方向キーが押されている時
            if (input.magnitude > 0f)
                {

                // キーを押されている間の速度(Speed)の変動によってのMotion変化
                var speed = Mathf.Abs(input.magnitude * 2.5f);
                animator.SetFloat("Speed", speed, 2f, Time.deltaTime);

                // 昇れる段差を表示
                Debug.DrawLine(transform.position + new Vector3(0f, stepOffset, 0f), transform.position + new Vector3(0f, stepOffset, 0f) + transform.forward * slopeDistance, Color.green);

                // ステップ用のレイが地面に接触しているかどうか
                if (Physics.Linecast(stepRay.position, stepRay.position + stepRay.forward * stepDistance, out var stepHit, LayerMask.GetMask("Field", "Block")))
                    {
                    // 進行方向の地面の角度が指定以下、または昇れる段差より下だった場合の移動処理
                    if (Vector3.Angle(transform.up, stepHit.normal) <= slopeLimit 
                        || (Vector3.Angle(transform.up, stepHit.normal) > slopeLimit 
                        && !Physics.Linecast(transform.position + new Vector3(0f, stepOffset, 0f), transform.position + new Vector3(0f, stepOffset, 0f) + transform.forward * slopeDistance, LayerMask.GetMask("Field", "Block"))))
                    {
                        velocity = new Vector3(0f, (Quaternion.FromToRotation(Vector3.up, stepHit.normal) * transform.forward * walkSpeed).y, 0f) + transform.forward * walkSpeed;
                        Debug.Log(Vector3.Angle(transform.up, stepHit.normal));
 
                    } else {
                        velocity += transform.forward * walkSpeed;
                    }
 
                    Debug.Log(Vector3.Angle(Vector3.up, stepHit.normal));
 
                // 地面に接触していない場合
                } else {
                    velocity += transform.forward * walkSpeed;
                }
            // キーを押していない場合は移動しない
            } else {
                animator.SetFloat("Speed", 0f);
            }

            // ジャンプの処理
            if (Input.GetButtonDown("Jump")
                && !animator.GetCurrentAnimatorStateInfo(0).IsName("Jump")
                && !animator.IsInTransition(0))      // 遷移途中にジャンプさせない条件
            {
                animator.SetBool("Jump", true);
                // ジャンプしたら接地していない状態にする
                isGrounded = false;
                velocity.y += jumpPower;
            }
        }
    }

  • void Update()部分に変更を加えています
  • 前回のAnimator編で説明していませんでしたが[animator.Set〇〇("〇〇");]と付けるとAnimatorを呼び出せます・・・oi

*Setの後ろの〇〇はAnimatorで設定したparameter値[Float,Bool,Int,Trigger]を入れます[ " " ]で囲っている〇〇の中はparameterの名前です



void Update()
    {

      〜〜

     // キーを押されている間の速度(Speed)の変動によってのMotion変化
     var speed = Mathf.Abs(input.magnitude * 2.5f);
     animator.SetFloat("Speed", speed, 2f, Time.deltaTime);

      〜〜
  • Mathf.Abs(input.magnitude * 2.5f);でキーが押されている間2.5fになるまでの絶対値を指定して
  • animator.SetFloat("Speed", speed, 2f, Time.deltaTime);でanimatorの呼びだし条件に上記の内容を2fごとに呼び出す様にしています。

*ここの2fの所を0に近づける事に変動率が早くなる為、遷移させたいMotionごとに設定で調整するとよく見えると思います。

BlendしたMotionがこちら⇩⇩

f:id:mochisakucoco:20210325143306g:plain
分かりずらいですが、歩くMotionからDashまでの繋ぎが滑らかになっています。


Motionの可変をUPにした物

f:id:mochisakucoco:20210325145558g:plain






今回はここまで




f:id:mochisakucoco:20210325162029g:plain
あら・・・あなた空中走ってません_:(´ཀ`」 ∠):
そんな訳で次回は細かいAnimatorの設定を終わらせたいと思います

Unityでのゲーム開発 〜Animator編〜

Animator

今回はAnimatorの設定をしていきたいと思います、本来であれば今まで作成していた記事ではキャラクターにモーションを設定していないので、本来はT字ポーズ(カカシの様な状態)のまま動いているハズです。
記事内容を見やすくする為に動きを付けていましたが本格的にイジってみたいと思います。

Animatorはキャラクターに動きを加える重要な役割がある為、ゲーム開発において重要な役割があります、これが付くだけでよりゲーム『っぽく』なります٩( ᐛ )و





Animator Controllerの作成

  • まずProject内に[Animator]のフォルダを作っておきます。
  • フォルダを作ったらAnimatorフォルダにカーソルを合わせて右クリック⇨[Create]⇨[Animator Controller]の順にクリックします。

今回は[ThirdPersonAnimatorController]とゆう名前にしておきます。・・・ながっ
f:id:mochisakucoco:20210321123947p:plain



モーションState(状態)の作成

Animatorのタブ内で右クリック⇨[Create State]⇨[Empty]で空のState(状態)を作ります。
⇩⇩⇩⇩⇩⇩
f:id:mochisakucoco:20210321140917g:plain

基本的にはこの状態を複数作成し、条件によってモーションを遷移出来る様にして行動1つ1つにモーションを加えていく作業になります。


まずは待機(Idle)状態を作る

どのゲームでも静止状態で止まっている場合でもモーションがあると思います。
動いてない時に静止画の様な状態で止まってたら不自然なので
*Unityでは何もモーションが設定されていない場合はTポーズになる場合が多いと思います。



今回はこの記事の一番最初にアセットストアからDLしたアセット内のモーションから選びたいと思います。
mochisakucoco.hatenablog.com

作業工程

  • 先程作成した[New State]をクリックしてMotion「Idle_Wait_A」を選択
  • Motion選択後にNew Stateの名前を[Idle]に変更(わかりやすくする為)
  • 完了したら[Player]のAnimatorコンポーネントに今回作成した[ThirdPersonAnimatorController]取り付ける

f:id:mochisakucoco:20210321153257g:plain

f:id:mochisakucoco:20210321154038p:plain

f:id:mochisakucoco:20210321154730p:plain


上の設定で出来上がったのがこちら⇩
f:id:mochisakucoco:20210321155522g:plain
この様に簡単に出来ます(*´ω`*)
まだ遷移状態がIdleしか作っていない為、動いた時は待機状態のまま動いてしまいますが、Animatorのタブを見るとIdleがしっかり起動しているのがわかると思います。


状態を遷移させて歩かせる(Walk)

基本的な状態設定が出来る様になったので(細かい設定は後ほど)、状態の遷移をしてモーションを繋げたいと思います。
状態の作り方はIdleの時と同じ方法でもう一つの新しい状態を作ります。

作業工程❶

  • New StateのMotionを「Mvm_Walk」を選択
  • New Stateの名前を[Walk]に変更
  • Make Transition(遷移を作る)⇦直訳しすぎ………
  1. Idleを選択中に右クリックでMake TransitionをクリックしてWalk側に持っていき繋げる
  2. Walkを選択中に右クリックでMake TransitionをクリックしてIdle側に持っていき繋げる

遷移状態を相互関係にした理由は一方通行にしてしまうとモーションが行ったきりで永遠に繰り返してしまうからです。
f:id:mochisakucoco:20210321163312g:plain


*遷移先を間違えてしまった時の対処法
Animatorタブ内ではDeleteが使える場所と使えない場所があります、今回の遷移状態は矢印を右クリックしてもDelete項目は出てきません。
私は使い方が分からない時はSteteを消して作り直すとゆう苦行をおかしてました_:(´ཀ`」 ∠):
そんな訳で消し方を覚えてもらった方が、今後の為にもなるので載せておきます。
f:id:mochisakucoco:20210321172925g:plain

項目の「ー」ボタンを押す!コレだけ!!






作業工程❷

  • Conditionsに設定が必要なParametersの設定
  • Float型を指定して名前を「Speed」に設定

アニメーションパラメーター - Unity マニュアル
f:id:mochisakucoco:20210322150550g:plain





作業工程❸

  • Make Transitionで作った遷移状態の設定を行う
  • 遷移状態の矢印をクリックしてInspectorで編集可能にする

⇩⇩Inspector内の細かい説明は下記の公式リファレンスから確認してください。⇩⇩
アニメーション遷移 - Unity マニュアル
f:id:mochisakucoco:20210321165323p:plain

  • Idle⇨Walkの遷移の設定
  1. Has Exit Timeのチェックボックスを外す
  2. Interruption SourceをNext Stateに変更
  3. ConditionsにSpeedを指定してGreaterを選択して数値を0.1にする

*parameter値が0.1より大きくなったら(Greater)遷移する条件付けしています。

 

  • Walk⇨Idleの遷移の設定
  1. Has Exit Timeのチェックボックスを外す
  2. Interruption SourceをNext Stateに変更
  3. ConditionsにSpeedを指定してLessを選択して数値を0.1にする

*parameter値が0.1より小さくなったら(Less)遷移する条件付けしています。


*Has Exit Timeはモーションを最後まで再生されるかどうかなのでIdleから遷移させるにはチェックを外しています。攻撃モーションなどは最後まで再生させる為にチェックを入れる事が多いです。




状態を遷移させてジャンプさせる(Jump)

 

作業工程❶

  • New Stateを作成
  • New StateのMotionを「Jmp_Base_A」を選択
  • New Stateの名前を[Jump]に変更
  • Make Transitionで遷移先を作る
  1. Idleを選択中に右クリックでMake TransitionをクリックしてJump側に持っていき繋げる
  2. Jumpを選択中に右クリックでMake TransitionをクリックしてIdle側に持っていき繋げる
  3. Walkを選択中に右クリックでMake TransitionをクリックしてJump側に持っていき繋げる

 

作業工程❷

  • Conditionsに設定が必要なParametersの設定
  • Bool型を指定して名前を「Jump」に設定

 

作業工程❸

  • Make Transitionで作った遷移状態の設定を行う
  • 遷移状態の矢印をクリックしてInspectorで編集可能にする

 

  • Idle⇨Jumpの遷移の設定
  1. Has Exit Timeのチェックボックスを外す
  2. Interruption SourceをNext Stateに変更
  3. ConditionsにBoolを指定してtrueを選択する

 

  • Jump⇨Idleの遷移の設定
  1. Has Exit Timeのチェックボックスを外す
  2. Interruption SourceをNext Stateに変更
  3. ConditionsにBoolを指定してfalseを選択する

 

  • Walk⇨Jumpの遷移の設定
  1. Has Exit Timeのチェックボックスを外す
  2. Interruption SourceをNext Stateに変更
  3. ConditionsにBoolを指定してtrueを選択する


最終的な出来上がりが⇩になります。
f:id:mochisakucoco:20210322165624g:plain


今回は、Animatorの基本的な操作を行いましたが次回はBlend Treeでモーションを滑らかに動作する様に変更していきたいと思います。