OldBlueWater/BlueWater/Assets/02.Scripts/Character/CombatPlayer/CombatPlayerController.cs
2024-02-19 09:06:16 +09:00

493 lines
19 KiB
C#

using System;
using System.Collections;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Rendering.Universal;
// ReSharper disable once CheckNamespace
namespace BlueWaterProject
{
public class CombatPlayerController : MonoBehaviour, IDamageable
{
/***********************************************************************
* Definitions
***********************************************************************/
#region Class
[Serializable]
public class Components
{
public PlayerInput playerInput;
public Transform visualLook;
public SpriteRenderer spriteRenderer;
public Animator animator;
public PhysicsMovement movement;
}
[Serializable]
public class CharacterOption
{
[Range(0f, 1000f), Tooltip("최대 체력")]
public float maxHp = 100f;
[Title("공격")]
[Range(1, 21), Tooltip("한 번에 공격 가능한 개체 수")]
public int maxHitNum = 10;
[Range(0f, 100f), Tooltip("첫 번째 공격 데미지")]
public float firstAttackDamage = 10f;
[Range(0f, 100f), Tooltip("두 번째 공격 데미지")]
public float secondAttackDamage = 15f;
[Range(0.1f, 2f), Tooltip("콤보 공격 포함 총 걸리는 시간")]
public float attackTime = 0.7f;
[Range(0.1f, 5f), Tooltip("공격 범위 사거리(반지름)")]
public float attackRange = 1.5f;
[Range(0f, 360f), Tooltip("공격 범위 각도")]
public float attackAngle = 180f;
[Tooltip("공격할 레이어 설정")]
public LayerMask targetLayer = -1;
[Tooltip("땅 레이어 설정")]
public LayerMask groundLayer;
}
[Serializable]
[DisableIf("@true")]
public class CurrentState
{
public bool useMouseAttack = true;
public bool isInvincibility;
public bool isAttacking;
public bool isComboPossible;
public bool isComboAttacking;
public bool isUsingSkill;
}
[Serializable]
public class CurrentValue
{
[Tooltip("현재 체력")]
public float currentHp;
[Tooltip("최근에 공격 받은 충돌체 배열")]
public Collider[] hitColliders;
}
#endregion
/***********************************************************************
* Variables
***********************************************************************/
#region Variables
[field: SerializeField] public Components MyComponents { get; private set; }
[field: SerializeField] public CharacterOption MyCharacterOption { get; private set; }
[field: SerializeField] public CurrentState MyCurrentState { get; set; }
[field: SerializeField] public CurrentValue MyCurrentValue { get; set; }
[Title("애니메이션")]
[SerializeField] private float comboAttackSpeed = 1f;
[SerializeField] private float[] comboAttackTiming = new[] { 0.14f, 0.77f };
[SerializeField] private float checkComboAttackTiming = 0.49f;
[Title("효과")]
[SerializeField] private float comboAttackHitStopDuration = 0.2f;
public string mainSkillName;
public GameObject MainSkillObject { get; private set; }
private ISkill mainSkill;
public static readonly int IsMovingHash = Animator.StringToHash("isMoving");
public static readonly int XDirectionHash = Animator.StringToHash("xDirection");
public static readonly int ZDirectionHash = Animator.StringToHash("zDirection");
public static readonly int IsAttackingHash = Animator.StringToHash("isAttacking");
public static readonly int IsActivateMainSkillHash = Animator.StringToHash("isActivateMainSkill");
private static readonly int IsHitHash = Shader.PropertyToID("_IsHit");
#endregion
/***********************************************************************
* Unity Events
***************************************************************m********/
#region Unity Events
private void OnEnable()
{
MyComponents.playerInput.actions.FindAction("Attack").performed += OnAttackEvent;
}
private void OnDisable()
{
MyComponents.playerInput.actions.FindAction("Attack").performed -= OnAttackEvent;
}
private void Start()
{
InitComponents();
InitSkills();
InitStartValue();
}
private void Update()
{
MoveAnimation();
FlipVisualLook(MyComponents.movement.GetPreviousMoveDirection().x);
}
#endregion
/***********************************************************************
* Init Methods
***********************************************************************/
#region Unity Events
private void InitComponents()
{
if (!TryGetComponent(out MyComponents.movement))
{
MyComponents.movement = gameObject.AddComponent<PhysicsMovement>();
}
MyComponents.movement.SetAnimator(MyComponents.animator);
}
private void InitSkills()
{
MainSkillObject = SkillManager.Inst.InstantiateSkill(mainSkillName).gameObject;
mainSkill = MainSkillObject.GetComponent<ISkill>();
if (mainSkill != null)
{
var myCollider = MyComponents.movement.MyComponents.capsuleCollider;
var myRb = MyComponents.movement.MyComponents.rb;
var myVisualLook = MyComponents.visualLook;
var myAnimator = MyComponents.animator;
var targetLayer = MyCharacterOption.targetLayer;
var ui = UiManager.Inst.CombatUi.MainSkillUi;
ui.gameObject.SetActive(true);
mainSkill.SkillInputData.InitInputData(myCollider, myRb, null, myVisualLook, myAnimator, null, targetLayer, ui);
mainSkill.InitData();
}
}
private void InitStartValue()
{
MyCurrentValue.hitColliders = new Collider[MyCharacterOption.maxHitNum];
SetCurrentHp(MyCharacterOption.maxHp);
}
#endregion
/***********************************************************************
* Player Input
***********************************************************************/
#region Unity Events
private void OnAttackEvent(InputAction.CallbackContext context)
{
if (MyCurrentState.isAttacking && !MyCurrentState.isComboPossible) return;
var control = context.control;
if (control.name.Equals("leftButton"))
{
MyCurrentState.useMouseAttack = true;
}
else if (control.name.Equals("k"))
{
MyCurrentState.useMouseAttack = false;
}
if (MyCurrentState.isComboPossible)
{
MyCurrentState.isComboAttacking = true;
return;
}
StartCoroutine(ComboAttackCoroutine());
}
private void OnActivateMainSkill()
{
if (MyCurrentState.isAttacking || GetIsDashing()) return;
mainSkill.SkillInputData.StartPosition = GetCurrentPosition();
if (mainSkill.EnableSkill())
{
SetEnableMoving(false);
MyComponents.animator.SetBool(IsActivateMainSkillHash, true);
mainSkill.ActivateSkill();
}
}
#endregion
/***********************************************************************
* Interfaces
***********************************************************************/
#region Interfaces
// IDamageable
public void TakeDamage(float attackerPower, Vector3? attackPos = null)
{
if (MyCurrentState.isInvincibility) return;
var changeHp = Mathf.Max(MyCurrentValue.currentHp - attackerPower, 0);
SetCurrentHp(changeHp);
// 죽었는지 체크
if (changeHp == 0f)
{
Die();
return;
}
else if (changeHp <= 30f)
{
CameraManager.Inst.CombatCamera.LowHpVignette();
}
else
{
CameraManager.Inst.CombatCamera.StopLowHpVignette();
}
StartCoroutine(nameof(FlashWhiteCoroutine));
}
public void Die()
{
print("Combat Player Die");
}
public float GetCurrentHp() => MyCurrentValue.currentHp;
#endregion
/***********************************************************************
* Methods
***********************************************************************/
#region Methods
private IEnumerator ComboAttackCoroutine()
{
SetEnableMoving(false);
if (MyCurrentState.useMouseAttack)
{
var mousePos = Mouse.current.position.ReadValue();
var ray = CameraManager.Inst.MainCam.ScreenPointToRay(mousePos);
if (!Physics.Raycast(ray, out var hit, float.MaxValue, MyCharacterOption.groundLayer))
{
CancelComboAttack();
yield break;
}
var attackDirection = (hit.point - GetCurrentPosition()).normalized;
attackDirection.y = 0f;
SetPreviousMoveDirection(attackDirection);
}
MyComponents.animator.SetBool(IsAttackingHash, true);
var elapsedTime = 0f;
while (!MyComponents.animator.GetCurrentAnimatorStateInfo(0).IsName("ComboAttack"))
{
elapsedTime += Time.deltaTime;
if (elapsedTime >= 2f)
{
yield break;
}
yield return null;
}
var animationLength = MyComponents.animator.GetCurrentAnimatorStateInfo(0).length;
var attackTimingNormalized = new[]{
comboAttackTiming[0] / animationLength,
comboAttackTiming[1] / animationLength };
var checkComboAttackNormalized = checkComboAttackTiming / animationLength;
var isAttacked = false;
var isComboAttacked = false;
MyComponents.animator.speed = comboAttackSpeed;
SetIsAttacking(true);
SetIsComboPossible(true);
while (MyComponents.animator.GetCurrentAnimatorStateInfo(0).IsName("ComboAttack") &&
MyComponents.animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 1f)
{
if (!isAttacked)
{
if (isComboAttacked && MyComponents.animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= attackTimingNormalized[1])
{
MoveToCurrentDirection(10f);
AttackTiming(MyCharacterOption.secondAttackDamage);
isAttacked = true;
}
else if (!isComboAttacked && MyComponents.animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= attackTimingNormalized[0])
{
MoveToCurrentDirection(1f);
AttackTiming(MyCharacterOption.firstAttackDamage);
isAttacked = true;
}
}
if (MyComponents.animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= checkComboAttackNormalized)
{
if (!MyCurrentState.isComboAttacking)
{
CancelComboAttack();
yield break;
}
if (!isComboAttacked && MyCurrentState.isComboAttacking)
{
isAttacked = false;
isComboAttacked = true;
}
}
yield return null;
}
CancelComboAttack();
}
public void AttackTiming(float damage)
{
var attackDirection = MyComponents.movement.GetPreviousMoveDirection();
var size = Physics.OverlapSphereNonAlloc(transform.position, MyCharacterOption.attackRange, MyCurrentValue.hitColliders, MyCharacterOption.targetLayer, QueryTriggerInteraction.Collide);
for (var i = 0; i < size; i++)
{
var targetDirection = (MyCurrentValue.hitColliders[i].transform.position - transform.position).normalized;
var angleBetween = Vector3.Angle(attackDirection, targetDirection);
if (angleBetween >= MyCharacterOption.attackAngle * 0.5f) continue;
if (MyCurrentValue.hitColliders[i].gameObject.layer == LayerMask.NameToLayer("Enemy"))
{
var iDamageable = MyCurrentValue.hitColliders[i].transform.GetComponent<IDamageable>();
iDamageable?.TakeDamage(damage);
if (MyCurrentState.isComboAttacking)
{
VisualFeedbackManager.Inst.TriggerHitStop(comboAttackHitStopDuration);
}
}
else if (MyCurrentValue.hitColliders[i].gameObject.layer == LayerMask.NameToLayer("Skill") &&
MyCurrentValue.hitColliders[i].CompareTag("DestructiveSkill"))
{
var iDamageable = MyCurrentValue.hitColliders[i].transform.GetComponent<IDamageable>();
iDamageable?.TakeDamage(damage);
}
}
}
private void CancelComboAttack()
{
SetIsComboPossible(false);
SetIsComboAttacking(false);
SetIsAttacking(false);
SetEnableMoving(true);
MyComponents.animator.SetBool(IsAttackingHash, false);
MyComponents.animator.speed = 1f;
}
private void MoveAnimation()
{
var isMoving = MyComponents.movement.GetIsMoving();
var previousDirection = MyComponents.movement.GetPreviousMoveDirection();
MyComponents.animator.SetBool(IsMovingHash, isMoving);
if (isMoving)
{
MyComponents.animator.SetFloat(XDirectionHash, previousDirection.x);
MyComponents.animator.SetFloat(ZDirectionHash, previousDirection.z);
}
}
private void FlipVisualLook(float previousDirectionX)
{
var localScale = MyComponents.visualLook.localScale;
localScale.x = previousDirectionX switch
{
> 0.01f => Mathf.Abs(localScale.x),
< -0.01f => -Mathf.Abs(localScale.x),
_ => localScale.x
};
MyComponents.visualLook.localScale = localScale;
}
private IEnumerator FlashWhiteCoroutine()
{
var spriteRenderer = MyComponents.spriteRenderer;
for (var i = 0; i < 5; i++)
{
spriteRenderer.material.SetInt(IsHitHash, 1);
yield return new WaitForSeconds(0.05f);
spriteRenderer.material.SetInt(IsHitHash, 0);
yield return new WaitForSeconds(0.05f);
}
}
public void CoolDown(float waitTime, Action onCooldownComplete = null)
{
StartCoroutine(CoolDownCoroutine(waitTime, onCooldownComplete));
}
private IEnumerator CoolDownCoroutine(float waitTime, Action onCooldownComplete = null)
{
var time = 0f;
while (time <= waitTime)
{
time += Time.deltaTime;
yield return null;
}
onCooldownComplete?.Invoke();
}
[Button("현재 체력 변경")]
private void SetCurrentHp(float value) => MyCurrentValue.currentHp = value;
public void SetEnableMoving(bool value) => MyComponents.movement.SetEnableMoving(value);
public void Move(Vector3 velocity) => MyComponents.movement.Move(velocity);
public void MoveToCurrentDirection(float speed) => MyComponents.movement.MoveToCurrentDirection(speed);
public bool GetIsAttacking() => MyCurrentState.isAttacking;
public bool GetIsComboAttacking() => MyCurrentState.isComboAttacking;
public bool GetIsDashing() => MyComponents.movement.GetIsDashing();
public float GetDashCooldown() => MyComponents.movement.GetDashCooldown();
public float GetDashTime() => MyComponents.movement.GetDashTime();
public float GetAttackTime() => MyCharacterOption.attackTime;
public void SetIsAttacking(bool value) => MyCurrentState.isAttacking = value;
public void SetIsComboAttacking(bool value) => MyCurrentState.isComboAttacking = value;
public void SetIsComboPossible(bool value) => MyCurrentState.isComboPossible = value;
public void SetIsDashing(bool value) => MyComponents.movement.SetIsDashing(value);
public void SetEnableDashing(bool value) => MyComponents.movement.SetEnableDashing(value);
public Vector3 GetCurrentPosition() => MyComponents.movement.GetCurrentPosition();
public void SetIsInvincibility(bool value) => MyCurrentState.isInvincibility = value;
public void SetIsTrigger(bool value) => MyComponents.movement.SetIsTrigger(value);
public void SetUseGravity(bool value) => MyComponents.movement.SetUseGravity(value);
public void SetPreviousMoveDirection(Vector3 value)
{
MyComponents.movement.SetPreviousMoveDirection(value);
if (value != Vector3.zero)
{
MyComponents.animator.SetFloat(XDirectionHash, value.x);
MyComponents.animator.SetFloat(ZDirectionHash, value.z);
}
}
#endregion
}
}