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

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

Unityでのゲーム開発 〜Rigidbodyとコライダーを適用したScript〜

Scriptを変更する前に


前回の記事でCharacterControllerかRigidbodyとコライダーを適用した事に関して触れたと思いますが、今の状態だと階段も登れない子になってしまうので_:(´ཀ`」 ∠):
そこを解消していきたいと思います。


現在のPlayerの状態⇩
f:id:mochisakucoco:20210314142821g:plain

見事に見えない壁にぶち当たってますねw



なぜ登れないのか?

f:id:mochisakucoco:20210314143555p:plain
なぜなのかと言うと今回取り付けているCapsuleColliderは👆の様な形状になっています。
このカプセル状態の物体が現実世界で階段を登っていたら恐怖でしかないです・・・足が生えていれば別ですが

Avatarとして外見は完全に人型をしていますが実際はカプセル状に判定があるので階段を登れていないとゆう事になります。FPSやTPSでも部位によって当たり判定やダメージが変化すると思いますが、今の状態だと何処に当ててもヘッドショット扱いになってしまう様な物です。
ダメージ判定などを分けるには各部位によってコライダーをつける必要があります。今は必要ない為、軽く覚えておいて貰えれば大丈夫です。




階段を登らせるには?

f:id:mochisakucoco:20210310054121p:plain
前回説明したCharacterControllerのStep Offsetを作れば解決出来ると思います。

プレイヤーの足元からRey(光線とゆう意味)を飛ばして登れる坂や階段なのかを調べる様にしたいと思います。
*銃の射線管理にも使えるraycastとゆうオブジェクトを検知するのに役に立つ機能もあります(後々使うと思います)

空のオブジェクトを作成して名前を[StepRayPoint]にしてPlayerの子オブジェクトとして設定しておきます。
positionなどは⇩の様に設定してます
f:id:mochisakucoco:20210314170727p:plain




Scriptの変更

今回の記事からmoveSpeedをwalkSpeedに変更しています(歩きと走りを分けたい為)
スクリプトは適宜変更する為ご了承ください:(;゙゚'ω゚'):

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

public class Player : MonoBehaviour
{

    [SerializeField] private GameObject aimCamera;          //エイムカメラを紐づける
    [SerializeField] private GameObject thirdPersonCamera;  //第三者視点カメラを紐づける


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

    private Animator animator;                              //animator
    private Vector3 velocity;                               //移動速度
    private Vector3 input;                                  //入力値
    private Rigidbody rd;                                   //rigidbody
    int layerMask;                                          //Playerレイヤー以外のレイヤーマスク
    [SerializeField] private bool isGrounded;               //地面に接地しているかどうか
    [SerializeField] private float walkSpeed = 1.5f;        //歩く速さ
    [SerializeField] private float jumpPower = 5f;          //ジャンプ力
    [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;      //昇れる段差の位置から飛ばすレイの距離


    void Start()
    {
        animator = GetComponent<Animator>();    //Animatorコンポーネントの取得
        rd = GetComponent<Rigidbody>();         //Rigidbodyコンポーネントの取得
        layerMask = ~(1 << LayerMask.NameToLayer("Player"));
    }
 
    void Update()
    {

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

        if (Input.GetKeyDown(KeyCode.B))  //対応したキーを押されたらカメラを切り換えるプログラムの呼び出し
            {
                aimCamera.SetActive(!aimCamera.activeInHierarchy);
                thirdPersonCamera.SetActive(!thirdPersonCamera.activeInHierarchy);
            }

        //キャラクターが接地している場合
        if (isGrounded)
            {
            //接地したので移動速度を0にする
            velocity = Vector3.zero;
            input = new Vector3(Horizontal, 0f, Vertical);
 
            //方向キーが押されている時
            if (input.magnitude > 0f)
                {
                animator.SetFloat("Speed", input.magnitude);

                //昇れる段差を表示
                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 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);

        // キャラクターの向きを進行方向にする(キャラクターの向きの設定は"ThirdPeasonCamera"が有効の場合のみ)
        if (moveForward != Vector3.zero && thirdPersonCamera.activeInHierarchy)
        {
            transform.rotation = Quaternion.LookRotation(moveForward);
        }
    }

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





スクリプト内容確認

layerMask = ~(1 << LayerMask.NameToLayer("Player"));

LayerMaskにPlayerレイヤーを設定、それ以外はビット演算子で新規でレイヤーを作成している。
http://www.ibe.kagoshima-u.ac.jp/edu/gengo0/p6.html⇦ビット演算子に関して

レイヤー - Unity マニュアル

 

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

f:id:mochisakucoco:20210318114059p:plain




//進行方向の地面の角度が指定以下、または昇れる段差より下だった場合の移動処理
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"))))

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"))))・・・Rayがレイヤーを検知している場合


上記の条件下で下記の移動方法を行える⇩⇩

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));

Quaternion.FromToRotation(Vector3.up, stepHit.normal)で上方向とRayの接触面の間の角度を計算しそれにtransform.forwardをかける事で坂の角度方向へのベクトルを計算しています。


Physics-Linecast - Unity スクリプトリファレンス






 
最終的な物がこちら⇩
f:id:mochisakucoco:20210317074456g:plain


今回はAnimatorに関して何も触れていませんが、通常はモーションは何も設定されていない為次回はAnimatorの設定を詳しくいじって行こうと思います。



今回参考にさせて頂いた記事⇩
キャラ操作をCharacterControllerからRigidbody+コライダに変更する | Unityを使った3Dゲームの作り方(かめくめ)