OldBlueWater/BlueWater/Assets/02.Scripts/Character/CombatPlayer/CombatPlayer.cs
2024-01-25 16:16:53 +09:00

337 lines
12 KiB
C#

using System;
using System.Collections;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Serialization;
// ReSharper disable once CheckNamespace
namespace BlueWaterProject
{
public class CombatPlayer : MonoBehaviour, IDamageable
{
[Title("초기화 방식")]
[SerializeField] private bool autoInit = true;
[Title("캐릭터 변수")]
[SerializeField] private float maxHp = 100f;
[SerializeField] private float currentHp;
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float maxSlopeAngle = 50f;
[field: Title("대쉬")]
[field: SerializeField] public float DashForce { get; set; } = 20f;
[field: SerializeField] public float DashCooldown { get; set; } = 0.5f;
[field: DisableIf("@true")]
[field: SerializeField] public bool IsDashing { get; set; }
[field: DisableIf("@true")]
[field: SerializeField] public bool EnableDash { get; set; } = true;
[field: Title("공격")]
[field: SerializeField] public int MaxHitNum { get; set; } = 10;
[field: SerializeField] public float AttackDamage { get; set; } = 10f;
[field: SerializeField] public float AttackRange { get; set; } = 1.5f;
[field: SerializeField] public float AttackAngle { get; set; } = 180f;
[field: SerializeField] public float ComboTime { get; set; } = 0.5f;
[field: SerializeField] public LayerMask TargetLayer { get; set; }
[field: DisableIf("@true")]
[field: SerializeField] public bool IsAttacking { get; set; }
[field: DisableIf("@true")]
[field: SerializeField] public bool IsComboAttacking { get; set; }
[field: DisableIf("@true")]
[field: SerializeField] public bool IsComboPossible { get; set; }
[Title("컴포넌트")]
[SerializeField] private PlayerInput playerInput;
[field: SerializeField] public Rigidbody Rb { get; set; }
[SerializeField] private Transform visualLook;
[field: SerializeField] public Animator Animator { get; set; }
[SerializeField] private Transform groundCheck;
[Title("Extensions Data")]
[SerializeField] private float nextFrameCoefficient = 3f;
private Vector2 movementInput;
private Vector3 currentDirection = Vector3.back;
public Vector3 PreviousDirection { get; set; } = Vector3.back;
[field: SerializeField] public Collider[] HitColliders { get; set; }
private RaycastHit slopeHit;
private int groundLayer;
private bool isOnSlope;
private bool isGrounded;
private const float RAY_DISTANCE = 3f;
private static readonly int XDirectionHash = Animator.StringToHash("xDirection");
private static readonly int ZDirectionHash = Animator.StringToHash("zDirection");
private static readonly int IsMovingHash = Animator.StringToHash("isMoving");
public readonly int isDashingHash = Animator.StringToHash("isDashing");
public readonly int isAttackingHash = Animator.StringToHash("isAttacking");
private void OnDrawGizmosSelected()
{
var lossyScale = transform.lossyScale;
var boxSize = new Vector3(lossyScale.x, 0.4f, lossyScale.z * 0.5f);
Gizmos.color = IsGrounded() ? Color.blue : Color.red;
Gizmos.DrawWireCube(groundCheck.position, boxSize);
}
[Button("셋팅 초기화")]
private void Init()
{
playerInput = GetComponent<PlayerInput>();
Rb = GetComponent<Rigidbody>();
visualLook = transform.Find("VisualLook");
Animator = visualLook.GetComponent<Animator>();
groundCheck = transform.Find("GroundCheck");
}
private void Awake()
{
if (autoInit)
{
Init();
}
}
private void Start()
{
HitColliders = new Collider[MaxHitNum];
groundLayer = 1 << LayerMask.NameToLayer("Ground");
SetCurrentHp(maxHp);
}
private void OnEnable()
{
playerInput.actions.FindAction("Attack").performed += OnAttackEvent;
}
private void OnDisable()
{
playerInput.actions.FindAction("Attack").performed -= OnAttackEvent;
}
private void Update()
{
var isMoving = currentDirection.magnitude > 0.01f;
if (isMoving)
{
PreviousDirection = currentDirection.normalized;
Animator.SetFloat(XDirectionHash, PreviousDirection.x);
Animator.SetFloat(ZDirectionHash, PreviousDirection.z);
}
Animator.SetBool(IsMovingHash, isMoving);
var localScale = visualLook.localScale;
localScale.x = PreviousDirection.x switch
{
> 0.01f => Mathf.Abs(localScale.x),
< -0.01f => -Mathf.Abs(localScale.x),
_ => localScale.x
};
visualLook.localScale = localScale;
isOnSlope = IsOnSlope();
isGrounded = IsGrounded();
}
private void FixedUpdate()
{
HandleMovement();
}
public void TakeDamage(float attackerPower, Vector3? attackPos = null)
{
if (IsDashing) return;
var changeHp = Mathf.Max(currentHp - attackerPower, 0);
SetCurrentHp(changeHp);
if (InIslandCamera.Inst.InIslandCam)
{
VisualFeedbackManager.Inst.CameraShake(InIslandCamera.Inst.InIslandCam);
}
// 죽었는지 체크
if (changeHp == 0f)
{
Die();
return;
}
}
public void Die()
{
print("Combat Player Die");
}
private void OnMove(InputValue value)
{
movementInput = value.Get<Vector2>();
}
public void OnDash()
{
if (!EnableDash || IsDashing) return;
Animator.SetBool(isDashingHash, true);
}
private void OnAttackEvent(InputAction.CallbackContext context)
{
if (IsAttacking && !IsComboPossible) return;
// var control = context.control;
//
// if (control.name.Equals("leftButton"))
// {
// UseMouseAttack = true;
// }
// else if (control.name.Equals("k"))
// {
// UseMouseAttack = false;
// }
if (IsComboPossible)
{
IsComboAttacking = true;
return;
}
Animator.SetBool(isAttackingHash, true);
}
private void HandleMovement()
{
currentDirection = new Vector3(movementInput.x, 0, movementInput.y).normalized;
//var currentDirection = IsDashing ? PreviousDirection : currentMovement.normalized;
if (isOnSlope && isGrounded)
{
currentDirection = Vector3.ProjectOnPlane(currentDirection, slopeHit.normal).normalized;
}
var moveValue = IsDashing ? DashForce : moveSpeed;
var newPosition = Rb.position + currentDirection * (moveValue * Time.fixedDeltaTime);
Rb.MovePosition(newPosition);
// var currentMovement = new Vector3(movementInput.x, 0, movementInput.y);
// var currentDirection = IsDashing ? PreviousDirection : currentMovement.normalized;
// var isNextFrameInAngle = CalculateNextFrameGroundAngle(currentDirection) < maxSlopeAngle;
// var velocity = isNextFrameInAngle ? currentDirection : Vector3.zero;
// var gravity = Vector3.down * Mathf.Abs(Rb.velocity.y);
//
// if (IsOnSlope() && IsGrounded())
// {
// velocity = Vector3.ProjectOnPlane(currentDirection, slopeHit.normal).normalized;
// gravity = Vector3.zero;
// Rb.useGravity = false;
// }
// else
// {
// Rb.useGravity = true;
// }
//
// var moveValue = IsDashing ? DashForce : moveSpeed;
// Rb.velocity = velocity * moveValue + gravity;
}
public bool IsOnSlope()
{
var ray = new Ray(transform.position, Vector3.down);
if (Physics.Raycast(ray, out slopeHit, RAY_DISTANCE, groundLayer))
{
var angle = Vector3.Angle(Vector3.up, slopeHit.normal);
Debug.DrawRay(transform.position, Vector3.down, angle != 0f && angle < maxSlopeAngle ? Color.blue : Color.red);
return angle != 0f && angle < maxSlopeAngle;
}
Debug.DrawRay(transform.position, Vector3.down, Color.red);
return false;
}
private bool IsGrounded()
{
var lossyScale = transform.lossyScale;
var boxSize = new Vector3(lossyScale.x, 0.4f, lossyScale.z * 0.5f);
return Physics.CheckBox(groundCheck.position, boxSize, Quaternion.identity, groundLayer);
}
private float CalculateNextFrameGroundAngle(Vector3 direction)
{
var nextFramePlayerPosition = transform.position + direction * (nextFrameCoefficient * moveSpeed * Time.fixedDeltaTime);
if (Physics.Raycast(nextFramePlayerPosition, Vector3.down, out var hitInfo, RAY_DISTANCE, groundLayer))
{
if (Vector3.Angle(Vector3.up, hitInfo.normal) > maxSlopeAngle)
{
Debug.DrawRay(hitInfo.point, hitInfo.normal, Color.red);
}
else
{
Debug.DrawRay(hitInfo.point, hitInfo.normal, Color.cyan);
}
Debug.DrawRay(nextFramePlayerPosition, Vector3.down, Color.green);
return Vector3.Angle(Vector3.up, hitInfo.normal);
}
Debug.DrawRay(nextFramePlayerPosition, Vector3.down, Color.magenta);
return 0;
}
public void AttackTiming()
{
var attackDirection = PreviousDirection;
Array.Clear(HitColliders, 0, MaxHitNum);
var size = Physics.OverlapSphereNonAlloc(transform.position, AttackRange, HitColliders, TargetLayer);
for (var i = 0; i < size; i++)
{
var targetDirection = (HitColliders[i].transform.position - transform.position).normalized;
var angleBetween = Vector3.Angle(attackDirection, targetDirection);
if (angleBetween >= AttackAngle * 0.5f) continue;
if (HitColliders[i].gameObject.layer == LayerMask.NameToLayer("Enemy"))
{
var iDamageable = HitColliders[i].transform.GetComponent<IDamageable>();
iDamageable.TakeDamage(AttackDamage);
VisualFeedbackManager.Inst.TriggerHitStop(0.1f);
}
else if (HitColliders[i].gameObject.layer == LayerMask.NameToLayer("Skill") &&
HitColliders[i].CompareTag("DestructiveSkill"))
{
var iDamageable = HitColliders[i].transform.GetComponent<IDamageable>();
iDamageable.TakeDamage(AttackDamage);
}
}
}
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();
}
private void SetCurrentHp(float value)
{
currentHp = value;
}
public RaycastHit GetSlopeHit() => slopeHit;
}
}