【ダークソウル風ゲームを作りたい(Unity)。その3】

今回はカメラとプレイヤーの間にオブジェクトがある場合、カメラをオブジェクトよりも内側に移動するようにします。
これにより、オブジェクトに遮られてプレイヤーがカメラに映らなくなることを防ぎます。

youtu.be

・下記スクリプトを追加します。

 public class CameraHandler : MonoBehaviour //+EP2 cameraHolderにアタッチ
    {
      (省略)
        private Vector3 cameraFollowVelocity = Vector3.zero; //+EP3

        private float targetPosition; //+EP3
        
        public float cameraSphereRadius = 0.2f; //+EP3 球体レイの半径
        public float cameraCollisionOffset = 0.2f; //+EP3 カメラ位置のオフセット
        public float minimumCollisionOffset = 0.2f; //+EP3 カメラとプレイヤーの最小距離

//+EP3 カメラとプレイヤーの間にオブジェクトがある場合、カメラをオブジェクトよりも内側に移動
        private void HandleCameraCollisions(float delta)
        {
            targetPosition = defaultPosition; //MainCameraLocalPosion.z
            RaycastHit hit;
            //プレイヤーからMainCameraへのベクトル
            Vector3 direction = cameraTransform.position - cameraPivotTransform.position;
            direction.Normalize();

            //Physics.SphereCast (Vector3 origin, float radius, Vector3 direction, out RaycastHit hitInfo,
            //float maxDistance= Mathf.Infinity, int layerMask= DefaultRaycastLayers,
            //球体のレイを飛ばし、オブジェクトコライダーの付いたオブジェクトがヒットするかを調べ、ヒットしたらtrueを返す
            //origin:球形が通過を開始する地点の中心 radius:球の半径 direction:球を通過させる方向
            //hitInfo:If true is returned, hitInfo will contain more information about where the collider was hit. (See Also: RaycastHit).
            //maxDistance:キャストの最大の長さ
            //layerMask:レイヤーマスク はレイキャストするときに選択的に衝突を無視するために使用します。

            //MainCameraとプレイヤーの間のオブジェクト有無を判定
            //CameraPivot位置(=プレイヤー位置)から半径cameraSphereRadiusの球をdirection方向(プレイヤーからMainCamera)へ
            //プレイヤー~MainCameraの距離飛ばす。
            if (Physics.SphereCast(cameraPivotTransform.position,cameraSphereRadius,direction,
                out hit,Mathf.Abs(targetPosition),ignoreLayers))
            {
                //オブジェクトがヒットした場合
                //変数disにcameraPivotTransform.positionとヒットオブジェクトの距離を格納
                float dis = Vector3.Distance(cameraPivotTransform.position, hit.point);

                //求めたdisからオフセット値を引く。おそらくカメラ位置をオブジェクトよりも内側にする為。
                targetPosition = -(dis - cameraCollisionOffset);
            }

            //targetPositionがminimumCollisionOffsetより小さい場合
            if (Mathf.Abs(targetPosition) < minimumCollisionOffset)
            {
                //カメラが近くなりすぎるので、一定の距離をとる
                targetPosition = -minimumCollisionOffset;
            }

            //MainCameraのz位置をtargetPositionに近づける
            cameraTransformPosition.z = Mathf.Lerp(cameraTransform.localPosition.z, targetPosition, delta / 0.2f);
            cameraTransform.localPosition = cameraTransformPosition;
        }
}

今回は以上です。

【メタルギアソリッド風ゲームを作りたい(UE4)。その13】

今回は画面中央に発砲する方法と銃の照準カーソルの追加です。
youtu.be

・画面中央への発砲を実装します。
BP_CharacterのBPに下記追加します。

・銃の照準(+)を作成します。

・エイミング時に照準カーソルを表示します。

インプットアクションAimingのBPに追加。

今回は以上です。

【ダークソウル風ゲームを作りたい(Unity)。その2】

今回はマウスに合わせたカメラの動作を実装します。
youtu.be

・空オブジェクトCameraHolderを作成します。positionはプレイヤーポジションと同じにします。
また、CameraHolderの直下にCameraPivotを作成し、CameraPivotの直下にMainCameraを移動する。

・カメラ制御を行うスクリプトを作成します。

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

namespace SG
{
    public class CameraHandler : MonoBehaviour //+EP2 cameraHolderにアタッチ
    {
        public Transform targetTransform;
        public Transform cameraTransform;
        public Transform cameraPivotTransform;
        private Transform myTransform;
        private Vector3 cameraTransformPosition;
        private LayerMask ignoreLayers;

       // public static CameraHandler singleton; //CameraHandlerクラスのシングルトン宣言

        public float lookspeed = 0.1f;
        public float followspeed = 0.1f;
        public float pivotSpeed = 0.03f;

        private float defaultPosition;
        private float lookAngle;
        private float pivotAngle;
        public float minimumPivot = -35;
        public float maximumPivot = 35;

       // public float cameraSmoothTime = 0.2f;

        private Vector3 cameraFollowVelocity = Vector3.zero; //+EP3

        private void Awake()
        {
           // singleton = this;
            myTransform = transform;
            defaultPosition = cameraTransform.localPosition.z;
            ignoreLayers = ~(1 << 8 | 1 << 9 | 1 << 10);
        }

        //カメラpositionを設定
        public void FollowTarget(float delta)
        {
            //cameraHolderとプレイヤーの位置を徐々に近づける。
             Vector3 targetPosition = Vector3.Lerp(myTransform.position, targetTransform.position, delta / followspeed);
            //Vector3 pos = Vector3.Lerp(a, b, t);t で a と b の間を補間.t=0のときpos=a,t=0.5のときpos=aとbの中間値、t=1のときpos=a
            //myTransform.position:カメラポジション targetTransform.position:プレイヤー位置 delta / followspeed:0.02/0.1=0.2

            myTransform.position = targetPosition;
        }

        //カメラRotationを設定
        public void HandCameraRotation(float delta,float mouseXInput,float mouseYInput)
        {
            lookAngle += (mouseXInput * lookspeed) / delta;
           // Debug.Log("lookAngle:" + lookAngle);

            pivotAngle -= (mouseYInput * pivotSpeed) / delta;
           // Debug.Log("pivotAngle:" + pivotAngle);

            //pivotAngleをminimum~maximumの範囲内の値で制限し、その値を返す
            pivotAngle = Mathf.Clamp(pivotAngle, minimumPivot, maximumPivot);

            //CameraHolderのRotationのy値を設定
            Vector3 rotation = Vector3.zero;
            rotation.y = lookAngle;
            Quaternion targetRotation = Quaternion.Euler(rotation);
            //myTransform.rotation = targetRotation;

            //CameraPivotのRotationのx値を設定
            rotation = Vector3.zero;
            rotation.x = pivotAngle;

            targetRotation = Quaternion.Euler(rotation);
            cameraPivotTransform.localRotation = targetRotation;
        }
    }
}
 public class InputHandler : MonoBehaviour //+EP1 playerにアタッチ
    {
(省略)
 //+EP2
        private void FixedUpdate()
        {
            //1フレームの時間幅:0.0167
            float delta = Time.fixedDeltaTime;
           // Debug.Log("delta:" + delta);

            if (cameraHandler != null)
            {
                cameraHandler.FollowTarget(delta);
                cameraHandler.HandCameraRotation(delta, mouseX, mouseY);
            }
        }
    }

今回は以上です。

【メタルギアソリッド風ゲームを作りたい(UE4)。その12】

今回はエイムオフセットを実装します。

youtu.be

上記または下記のUEページが参考になります。
docs.unrealengine.com

今回は以上です。

【メタルギアソリッド風ゲームを作りたい(UE4)。その11】

今回はFootIKを実装します。
これにより階段の高さに合わせた左右の足の位置にすることができます。

youtu.be

・プレイヤーメッシュのfoot_rとfoot_lの直下にソケットを作成します。

・BP_CharacterAnimのAnimGraphに下記追加します。

全体図

拡大図

・関数TraceGroundを作ります。

・関数FootIKを作ります。
左部

真中部

右部

・イベントグラフ内でFootIKを呼び出します。

今回は以上です。

【ダークソウル風ゲームを作りたい(Unity)。その1】

今回からダークソウル風ゲームを作っていきたいと思います。

今回はプレイヤーの移動を実装します。
youtu.be

・Planeを設置し、キャラクターを配置します。
私はmixamoからキャラクターをダウンロードしました。

www.mixamo.com

・Input systemをPackageManagerでインストールします。

・Input Actionsを作成(PlayerControls)を作成し、下記のように設定しました。

RightStick[GamePad]はProcessorにStickDeadZone(デッドゾーン:特定範囲の入力値を補正すること)を設定します。
詳細は下記参照。
nekojara.city

・プレイヤーにCapsuleColliderとRigidbodyをアタッチします。

・プレイヤーに下記スクリプトを追加します。

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


namespace SG
{
    public class InputHandler : MonoBehaviour //+EP1 playerにアタッチ
    {
        public float horizontal;
        public float vertical;
        public float moveAmount;
        public float mouseX;
        public float mouseY;

        PlayerControls inputActions;

        Vector2 movementInput;
        Vector2 cameraInput;

        public void OnEnable() //OnEnableはオブジェクトが有効になったときに呼び出されます.
        {
            if (inputActions == null)
            {
                inputActions = new PlayerControls();
                 //アクション実行中(performed)に、+=でデリゲート登録されたメソッドが実行される。
                inputActions.PlayerMovement.Movement.performed += inputActions => movementInput = inputActions.ReadValue<Vector2>();
                inputActions.PlayerMovement.Camera.performed += i => cameraInput = i.ReadValue<Vector2>();
            }
            //インプットアクションの有効化
            inputActions.Enable();
        }

        private void OnDisable()
        {
            //インプットアクションの無効化
            inputActions.Disable();
        }

        public void TickInput(float delta)
        {
            MoveInput(delta);
        }

        //キーボードやマウスの入力値を取得
        public void MoveInput(float delta)
        {
            //Actions:Movementのx方向入力値(キーボードA(-1) or D(+1))を変数horizontalに代入
            horizontal = movementInput.x;
            //Actions:Movementのy方向入力値(キーボードW(+1) or S(-1))を変数Verticalに代入
            vertical = movementInput.y;
           //Clamp値(0~1)を変数moveAmountに代入。WASDの押下により1が代入される。
            moveAmount = Mathf.Clamp01(Mathf.Abs(horizontal) + Mathf.Abs(vertical));

            //Actions:Cameraの入力値(マウスの場合、前フレーム位置からの差分)を変数mouseに代入
            //mouseXはx方向値(画面横方向のデルタ値)を代入。
            //mouseYはy方向値(画面縦方向のデルタ値)を代入。
            mouseX = cameraInput.x;
            mouseY = cameraInput.y;
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SG
{
    public class PlayerLocomotion : MonoBehaviour //+EP1 playerにアタッチ
    {
        Transform cameraObject;
        InputHandler inputHandler;
        Vector3 moveDirection;

        [HideInInspector]
        public Transform myTransform;
        [HideInInspector]
        public AnimatorHandler animatorHandler;

        public new Rigidbody rigidbody;
        public GameObject normalCamera;

        [Header("Stats")]
        [SerializeField]
        float movementSpeed = 5; //移動スピード
        [SerializeField]
        float rotationSpeed = 10; //歩行スピード

        private void Start()
        {
            rigidbody = GetComponent<Rigidbody>();
            inputHandler = GetComponent<InputHandler>();
            animatorHandler = GetComponentInChildren<AnimatorHandler>();
            //MainCameraのtransformを取得
            cameraObject = Camera.main.transform;
            myTransform = transform;
            animatorHandler.initialize();
        }

        public void Update()
        {
            //Time.deltaTime:前回のフレームからの経過時間
            float delta = Time.deltaTime;

            inputHandler.TickInput(delta);

            moveDirection = cameraObject.forward * inputHandler.vertical;
            //cameraObject.forward:world spaceに対するプレイヤーの前方位置(0.0, 0.0, -1.0)
            //inputHandler.vertical:キーボードW(+1) or S(-1)
            //moveDirection:キーボードW(0.0, 0.0, -1.0) or S(0.0, 0.0, 1.0)

            moveDirection += cameraObject.right * inputHandler.horizontal;
            //cameraObject.right:world spaceに対するプレイヤーの右位置(-1.0, 0.0, 0.0)
            //inputHandler.horizontal:キーボードA(-1) or D(+1)
            //cameraObject.right * inputHandler.horizontal:キーボードA(1.0, 0.0, 0.0) or D(-1.0, 0.0, 0.0)
            // moveDirection:キーボードW,A押下の場合(1.0, 0.0, -1.0) 

            //正規化:ベクトルの向きは変えずに大きさを1にする
            moveDirection.Normalize();

            float speed = movementSpeed;
            moveDirection *= speed;

            //ProjectOnPlane:斜面に沿ったベクトルを計算
            Vector3 projectedVelocity = Vector3.ProjectOnPlane(moveDirection, normalVector);
           
            //velocityによりプレイヤー移動
            rigidbody.velocity = projectedVelocity;

            //animatorの変数Verticalと変数Horizontalの値を設定
            animatorHandler.UpdateAnimatorValues(inputHandler.moveAmount, 0);

            if (animatorHandler.canRotate)
            {
                HandleRotaion(delta);
            }
        }

        #region Movement
        Vector3 normalVector;
        Vector3 targetPosition;

        //プレイヤーの回転方向を制御
        private void HandleRotaion(float delta)
        {
            Vector3 targetDir = Vector3.zero;
            float moveOverride = inputHandler.moveAmount;

            targetDir = cameraObject.forward * inputHandler.vertical;

            targetDir += cameraObject.right*inputHandler.horizontal;

            //正規化されたときベクトルは同じ方向は維持したままで長さが 1.0 のものが作成されます。
            targetDir.Normalize();
            targetDir.y = 0;

            //WASDを押してない場合
            if (targetDir == Vector3.zero)
                targetDir = myTransform.forward;

            float rs = rotationSpeed;

            Quaternion tr = Quaternion.LookRotation(targetDir); //指定された forward(targetDir) と upwards 方向に回転します。

            //public static Quaternion Slerp (Quaternion a, Quaternion b, float t): a と b の間を t で球状に補間します。
            //パラメーター t は、[0, 1] の範囲です。
            //回転がゆるやかになる。
              Quaternion targetRotation = Quaternion.Slerp(myTransform.rotation, tr, rs * delta);

              myTransform.rotation = targetRotation;
        }

        #endregion
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace SG
{
    public class AnimatorHandler : MonoBehaviour //+EP1 player直下のPaladinにアタッチ
    {
        public Animator anim;
        int vertical;
        int horizontal;
        public bool canRotate;

        public void initialize()
        {
            anim = GetComponent<Animator>();
            //animatorからstring名の値を取得
            vertical = Animator.StringToHash("Vertical");
            horizontal = Animator.StringToHash("Horizontal");
        }

        public void UpdateAnimatorValues(float verticalMovement,float horizontalMovement)
        {
            #region Vertical
            float v = 0;

            //引数により変数vを設定
            if(verticalMovement>0 && verticalMovement < 0.55f)
            {
                v = 0.5f;
            }
            else if (verticalMovement > 0.55f)
            {
                v = 1;
            }
            else if (verticalMovement < 0 && verticalMovement > -0.55f)
            {
                v = -0.5f;
            }
            else if (verticalMovement < -0.55f)
            {
                v = -1;
            }
            else
            {
                v = 0;
            }

            #endregion

            #region

            float h = 0;

            if(horizontalMovement>0 && horizontalMovement < 0.55f)
            {
                h = 0.5f;
            }
            else if (horizontalMovement > 0.55f)
            {
                h = 1;
            }
            else if (horizontalMovement < 0 && horizontalMovement > -0.55f)
            {
                h = -0.5f;
            }
            else if (horizontalMovement < -0.55f)
            {
                h = -1;
            }
            else
            {
                h = 0;
            }

            #endregion

            // anim.SetFloat(id, 値,値が変化する幅 , 1フレームの時間幅);
            anim.SetFloat(vertical, v, 0.1f, Time.deltaTime);
            anim.SetFloat(horizontal, h, 0.1f, Time.deltaTime);    
        }

        public void CanRotate()
        {
            canRotate = true;
        }

        public void StopRotation()
        {
            canRotate = false;
        }
    }
}


・Animatorコントローラーを作成し、BlendTree(Locomotion)を作ります。また、パラメータverticalとhorizontalを追加します。

・BlendTree(Locomotion)でIdle、walking、Runアニメーションを設定します。

アニメーションはmixamoからダウンロードしました。
アニメーションの設定です(Idle,Walking,Run全て同じ設定)。
Rig設定をHumanoidに変更。
LoopTimeとLoopPoseにチェック。
【Root Transform Rotation】
Bake Into Poseにチェック:チェックを入れるとオブジェクトの向きが、アニメ―ションによる回転に左右されなくなります。
Based UponをOriginalにする:Idle時にBody Orientation設定だと斜め前を見ているがBased UponをOriginalに設定すると正面を向くようになった為、idle以外にも適用。

アニメーション設定については下記参照。
xr-hub.com

今回は以上です。

【メタルギアソリッド風ゲームを作りたい(UE4)。その10】

今回は発砲時の薬莢を実装していきます。
youtu.be

・ライフルから実装します。BP_RifleAmmoというブループリントを作り、下図のようにします。
薬莢のスポーン時にプレイヤーから見て右側方向に力を加えています。

・ライフルメッシュのAmmoEject位置に薬莢をスポーンさせます。プレビューの追加で薬莢の位置と方向を調整します。

・WeaponStructureにAmmoClassとAmmoSocketName(初期値:AmmoEject)を追加します。

・WeaponDataTableでAmmoClassに先程作成したBP_RifleAmmoを追加します。

・BP_Characterの関数FireAnimationで下図のように設定します。

・薬莢の実装が出来ましたが、現状だと地面の薬莢とプレイヤーが衝突します。解消する為に、BP_RifleAmmoのAmmoMesh内のコリンジョンプリセットをカスタムにし、下図のように設定します。これで薬莢は地面とだけ衝突するようになります。

・ピストル、ショットガンも同様に設定します。

今回は以上です。