【ダークソウル風ゲームを作りたい(Unity)。その10】
今回はHP表示とダメージを受けた場合の処理を実装します。
・追加したスクリプト
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】
今回は攻撃アクションを実装します。
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); } } }
今回は以上です。
【ダークソウル風ゲームを作りたい(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; } } }
今回は以上です。