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

今回はHP表示とダメージを受けた場合の処理を実装します。

youtu.be

・追加したスクリプト

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

namespace SG
{
    public class HealthBar : MonoBehaviour //+EP9 
    {
        public Slider slider;

        private void Start()
        {
            slider = GetComponent<Slider>();
        }

        public void SetMaxHealth(int maxHealth)
        {
            slider.maxValue = maxHealth;
            slider.value = maxHealth;
        }

        public void SetCurrentHealh(int currentHealth)
        {
            slider.value = currentHealth;
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SG
{

    public class PlayerStats : MonoBehaviour //+EP9 
    {
        public int healthLevel = 10;
        public int maxHealth;
        public int currentHealth;

        public HealthBar healthBar;

        AnimatorHandler animatorHandler;

        private void Awake()
        {
            animatorHandler = GetComponentInChildren<AnimatorHandler>();
        }

        void Start()
        {
            maxHealth = setMaxHealthFromHealthLevel();
            currentHealth = maxHealth;
            healthBar.SetMaxHealth(maxHealth);
        }

        int setMaxHealthFromHealthLevel()
        {
            maxHealth = healthLevel * 10;
            return maxHealth;
        }

        public void TakeDamage(int damage)
        {

            if (currentHealth > 0)
            {
                currentHealth = currentHealth - damage;

                healthBar.SetCurrentHealh(currentHealth);

                animatorHandler.PlayTargetAnimation("Damage01", true);
            }

            if (currentHealth <= 0)
            {
                currentHealth = 0;
                animatorHandler.PlayTargetAnimation("Dead01", true);
                //HANDLE PLAYER DEATH
            }
         
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace SG
{


    public class DamagePlayer : MonoBehaviour //+EP9
    {
        public int damage = 25;

        private void OnTriggerEnter(Collider other)
        {
            PlayerStats playerStats = other.GetComponent<PlayerStats>();

            //タッチしたのがプレイヤーの場合、ダメージを与える
            if (playerStats != null)
            {
                playerStats.TakeDamage(damage);
            }
        }
    }
}

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

今回は攻撃アクションを実装します。

youtu.be

 public class PlayerLocomotion : MonoBehaviour //+EP1 playerにアタッチ
    {
  public void HandleMovement(float delta)
        {
            //+EP5 ローリング中の場合、以降なにもしない
            if (inputHandler.rollFlag)
                return;

            //+EP7 アニメーション実行中、以降なにもしない
            if (playerManager.isInteracting)
                return;

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

            moveDirection.y = 0; //+EP2

            float speed = movementSpeed;

            //+EP5 スプリントフラグがONになった場合の設定

            // if (inputHandler.sprintFlag)
            if (inputHandler.sprintFlag && inputHandler.moveAmount>0.5)
            {
                speed = sprintSpeed;
                playerManager.isSprinting = true;
                moveDirection *= speed;
            }
            else
            {
                moveDirection *= speed;
                playerManager.isSprinting = false; //+EP9
            }

            //ProjectOnPlane:斜面に沿ったベクトルを計算
            Vector3 projectedVelocity = Vector3.ProjectOnPlane(moveDirection, normalVector);

            //velocityによりプレイヤー移動
            rigidbody.velocity = projectedVelocity;

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

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

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

        public bool b_Input; //+EP4
        public bool rb_Input;// +EP9
        public bool rt_Input; //+EP9

        public bool rollFlag; //+EP4
        public float rollInputTimer; //+EP5 ローリングボタンを押下している時間を格納
        public bool sprintFlag; //+EP5 スプリントフラグがOnの場合、true

        PlayerControls inputActions;
        PlayerAttacker playerAttacker; //+EP9
        PlayerInventory playerInventory; //+EP9

        Vector2 movementInput;
        Vector2 cameraInput;

        //+EP9
        private void Awake()
        {
            playerAttacker = GetComponent<PlayerAttacker>();
            playerInventory = GetComponent<PlayerInventory>();
        }

        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);
            HandleRollInput(delta); //+EP4
            AttackInput(delta); //+EP9
        }

        //キーボードやマウスの入力値を取得
        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方向値(画面横方向のデルタ値)を代入。-1~1
            //mouseYはy方向値(画面縦方向のデルタ値)を代入。-1~1
            mouseX = cameraInput.x;
            mouseY = cameraInput.y;
        }

        //+EP4 ローリングをONした場合の設定
        private void HandleRollInput(float delta)
        {
            //ローリングボタン押下でb_input=true
            b_Input = inputActions.PlayerActions.Roll.phase == UnityEngine.InputSystem.InputActionPhase.Started;

            //ローリングボタンを押下中
            if (b_Input)
            {
                //+EP5
                rollInputTimer += delta;
                sprintFlag = true;
            }
            else //+EP5
            {
                //ローリングボタンを下記条件でON/OFFした場合、ローリングを行う
                if(rollInputTimer>0 && rollInputTimer < 0.5f)
                {
                    sprintFlag = false;
                    rollFlag = true;
                }

                rollInputTimer = 0;
            }

        }

        //+EP9
        private void AttackInput(float delta)
        {
            //InputActionのRB,RT押下をr*_inputに設定
            inputActions.PlayerActions.RB.performed += i => rb_Input = true;
            inputActions.PlayerActions.RT.performed += i => rt_Input = true;

            if (rb_Input)
            {
                playerAttacker.HandleLightAttack(playerInventory.rightWeapon);
            }

            if (rt_Input)
            {
                playerAttacker.HandleHeavyAttack(playerInventory.rightWeapon);
            }
        }
    }
public class ResetAnimatorBool : StateMachineBehaviour //+EP9 Animator Override Emptyにアタッチ
{
    public string targetBool;
    public bool status;

    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.SetBool(targetBool, status);
    }
}
[CreateAssetMenu(menuName="Items/Weapom Item")]
    public class WeaponItem : Item //+EP8
    {
        public GameObject modelPrefab;
        public bool isUnarmed;

        //+EP9
        [Header("On Handed Attack Animations")]
        public string OH_Light_Attack_1;
        public string OH_Heavy_Attack_1;
    }
 public class PlayerAttacker : MonoBehaviour //+EP9 プレイヤーにアタッチ
    {
        AnimatorHandler animatorHandler;

        private void Awake()
        {
            animatorHandler = GetComponentInChildren<AnimatorHandler>();
        }

        //ライトアタックアニメーションを実装
        public void HandleLightAttack(WeaponItem weapon)
        {
            animatorHandler.PlayTargetAnimation(weapon.OH_Light_Attack_1, true);
        }

        //ヘビーアタックアニメーションを実装
        public void HandleHeavyAttack(WeaponItem weapon)
        {
            animatorHandler.PlayTargetAnimation(weapon.OH_Heavy_Attack_1, true);
        }
    }

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

今回はプレイヤーに武器を持たせます。
youtu.be

今回追加するスクリプトです。

namespace SG
{
    public class Item : ScriptableObject //+EP8 ScriptableObject:共用するデータを格納する時に便利なクラス
    {
        [Header("Item Information")]
        public Sprite itemIcon;
        public string itenName;
    }
}
namespace SG
{
    [CreateAssetMenu(menuName="Items/Weapom Item")]
    public class WeaponItem : Item //+EP8
    {
        public GameObject modelPrefab;
        public bool isUnarmed;
    }
}
namespace SG
{
    public class WeaponHoldersSlot : MonoBehaviour //+EP8 プレイヤーの右手/左手にアタッチ
    {
        public Transform parentOverride;
        public bool isLeftHandsSlot;
        public bool isRightHandSlot;

        public GameObject currentWeaponModel;

        public void UnloadWeapon()
        {
            if (currentWeaponModel != null)
            {
                currentWeaponModel.SetActive(false);
            }
        }

        public void UnloadWeaponAndDestory()
        {
            if (currentWeaponModel != null)
            {
                Destroy(currentWeaponModel);
            }
        }

        public void LoadWeaponModel(WeaponItem weaponItem)
        {
            UnloadWeaponAndDestory();

            if (weaponItem == null)
            {
                UnloadWeapon();
                return;
            }

            //武器の生成
            GameObject model = Instantiate(weaponItem.modelPrefab) as GameObject;
            if (model != null)
            {
                //左手の武器の親を設定
                if (parentOverride != null)
                {

                    model.transform.parent = parentOverride;
                }
                //右手の武器の親を設定
                else
                {
                    model.transform.parent = transform;
                }

                model.transform.localPosition = Vector3.zero;
                //回転なし
                model.transform.localRotation = Quaternion.identity;
                model.transform.localScale = Vector3.one;
            }

            currentWeaponModel = model;
        }
    }
}
namespace SG
{
    public class WeaponSlotManager : MonoBehaviour //+EP8 プレイヤー直下のオブジェクトにアタッチ
    {
        WeaponHoldersSlot leftHandslot;
        WeaponHoldersSlot rightHandslot;

        private void Awake()
        {
            WeaponHoldersSlot[] weaponHoldersSlots = GetComponentsInChildren<WeaponHoldersSlot>();
            foreach(WeaponHoldersSlot weaponSlot in weaponHoldersSlots)
            {
                if (weaponSlot.isLeftHandsSlot)
                {
                    leftHandslot = weaponSlot;
                }
                else if (weaponSlot.isRightHandSlot)
                {
                    rightHandslot = weaponSlot;
                }
            }

        }

        public void LoadWeaponOnSlot(WeaponItem weaponItem,bool isLeft)
        {
            if (isLeft)
            {
                leftHandslot.LoadWeaponModel(weaponItem);
            }
            else
            {
                rightHandslot.LoadWeaponModel(weaponItem);
            }
        }
    }
}
namespace SG
{
    public class PlayerInventory : MonoBehaviour //+EP8 プレイヤーにアタッチ
    {
        WeaponSlotManager weaponSlotManager;

        public WeaponItem rightWeapon;
        public WeaponItem leftWeapon;

        private void Awake()
        {
            weaponSlotManager = GetComponentInChildren<WeaponSlotManager>();
        }

        private void Start()
        {
            //右手/左手に武器のリスポーンを実行
            weaponSlotManager.LoadWeaponOnSlot(rightWeapon, false);
            weaponSlotManager.LoadWeaponOnSlot(leftWeapon, true);
        }
    }
}

今回は以上です。

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

今回はAI Perceptionを使ってAIに視覚情報を与えるです。
youtu.be

・2つのインターフェイスを作ります。

・BP_EnemyAIに下記を追加します。

・BB_EnemyAIに変数TargetActor(Object)と変数HasLineOfSight(Boolean)を追加します。

・BP_EnemyAIConに下記追加します。

今回は以上です。

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

今回は滞空アニメーションと着地アニメーションを実装します。
youtu.be

・mixamoから着地アニメーションを取得します。

・着地アニメーションを編集して滞空中アニメーションを作成します。
mixamoのアニメーションはReadOnlyになっていて編集できないのでVeryAnimationを使用して編集可能にします。
knb-mayumi.com

・Animatorにアニメーションを設定します。

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

public class PlayerManager : MonoBehaviour //+EP4 playerにアタッチ
{
 private void LateUpdate()
        {
            inputHandler.rollFlag = false;
            inputHandler.sprintFlag = false;
            isSprinting = inputHandler.b_Input;

            //+EP7 滞空時間を変数inAirTimerに代入
            if (isInAir)
            {
                playerLocomotion.inAirTimer = playerLocomotion.inAirTimer + Time.deltaTime;
            }
        }
}
public class PlayerLocomotion : MonoBehaviour //+EP1 playerにアタッチ
{
        //+EP7
        [Header("Ground & Air Detection Stats")]
        [SerializeField]
        float groundDetectionRayStartPoint = 0.5f;
        [SerializeField]
        float minimumDistanceNeededToBeginFall = 1f;
        [SerializeField]
        float groundDirectionRayDistance = 0.2f;
        LayerMask ignoreForGroundCheck;
        public float inAirTimer;

        [Header("Movement Stats")]
        [SerializeField]
        float movementSpeed = 5; //移動スピード
        [SerializeField]
        float sprintSpeed = 7; //+EP5 スプリントスピード
        [SerializeField]
        float rotationSpeed = 10; //回転スピード

        [SerializeField]
        float fallingSpeed = 45; //+EP7

 public void HandleFalling(float delta, Vector3 moveDirection)
        {
            //地面に接地していない
            playerManager.isGrounded = false;
            RaycastHit hit;
            Vector3 origin = myTransform.position;

            //プレイヤーのy座標にgroundDetectionRayStartPoint(0.5f)プラス
            origin.y += groundDetectionRayStartPoint;

            //originからmyTransform.forward方向へRayを生成。Ray表示時間:0,4f
            if (Physics.Raycast(origin, myTransform.forward, out hit, 0.4f))
            {
                //プレイヤー前方にオブジェクトを検知した場合、movedirectionに0ベクトルを代入
                moveDirection = Vector3.zero;
            }

            //プレイヤーが空中時
            if (playerManager.isInAir)
            {
                //プレイヤー下方向に力を加える
                rigidbody.AddForce(-Vector3.up * fallingSpeed);
                rigidbody.AddForce(moveDirection * fallingSpeed / 10f);
                
            }

            Vector3 dir = moveDirection;
            dir.Normalize();
            origin = origin + dir * groundDirectionRayDistance;

            targetPosition = myTransform.position;

            Debug.DrawRay(origin, -Vector3.up * minimumDistanceNeededToBeginFall, Color.red, 0.1f, false);
            //ワールド座標にて start (開始地点)から start + dir (開始地点+方向)までラインを描画します。
            //duration は命令を発行してからラインが描画され、消えるまでの時間(秒単位)です。duration が 0 の場合は 1 フレームのみ表示されます。

            //プレイヤーが地に足がついている状態
            if (Physics.Raycast(origin,-Vector3.up,out hit, minimumDistanceNeededToBeginFall, ignoreForGroundCheck))
            {

                //Rayが衝突した面が向いている方向のベクトルをvector3で出力
                normalVector = hit.normal;

                //レイがコライダー(地面)にヒットした位置 (ワールド空間)。
                Vector3 tp = hit.point;
               // Debug.Log("tp:" + tp);

                playerManager.isGrounded = true;
                targetPosition.y = tp.y;

                //プレイヤーが空中の場合
                if (playerManager.isInAir)
                {
                    //プレイヤーが設定時間より長く空中にいる場合
                    if (inAirTimer > 0.5f)
                    {
                        Debug.Log("You were in the air for" + inAirTimer);

                        //着地アニメーション
                        animatorHandler.PlayTargetAnimation("Land", true);
                        inAirTimer = 0;
                    }
                    // //プレイヤーが設定時間より短く空中にいる場合
                    else
                    {
                        //移動アニメーション
                        animatorHandler.PlayTargetAnimation("Locomotion", false);
                        inAirTimer = 0;
                    }

                    playerManager.isInAir = false;
                }
            }
            //プレイヤーが空中時
            else
            {

                if (playerManager.isGrounded)
                {
                    playerManager.isGrounded = false;
                }

                if (playerManager.isInAir == false)
                {
                    if (playerManager.isInteracting == false)
                    {
                      //  Debug.Log("010");

                        //プレイヤーが落下中のアニメーション
                        animatorHandler.PlayTargetAnimation("Falling", true);
                    }

                    Vector3 vel = rigidbody.velocity;
                    vel.Normalize();
                    rigidbody.velocity = vel * (movementSpeed / 2);
                    playerManager.isInAir = true;
                }
            }

            //プレイヤーが地面に接地している場合
            if (playerManager.isGrounded)
            {
                 //プレイヤーがアニメーション実行しているまたは、移動している場合
                if (playerManager.isInteracting || inputHandler.moveAmount > 0)
                {
                     // myTransform.position = Vector3.Lerp(myTransform.position, targetPosition, Time.deltaTime*2);

                    myTransform.position = targetPosition;
                }
                //プレイヤーが動いていない場合
                else
                {
                                myTransform.position = targetPosition;
                }
            }
        }

今回は以上です。