OldBlueWater/BlueWater/Assets/02.Scripts/Character/CombatPlayer/CombatPlayerController.cs
NTG c63377124f 슬라임 및 로직 수정(상세내용 아래 참고)
수정 내역
1. 토끼 이미지, 크기 변경
2. 슬라임 이미지(색감), 크기 변경
3. 슬라임 그림자 이미지 변경
4. 슬라임 얼룩자국 이미지(색감) 변경
5. 토끼 슬라임 최대 4단계로 변경
6. 맵 이동 및 재시작 과정에서 전투플레이어 삭제 후 생성하는 로직으로 변경
7. 1단계 슬라임이 점프할 때, 카메라 흔들림 효과 강화
8. 슬라임 시작 또는 분열할 때, 0.5초 무적 기능 추가

버그 수정 내역
1. 일반 슬라임이 본체로 인식되는 버그 수정
2. 간헐적으로 슬라임 얼룩 자국이 남아있는 버그 수정
3. 마지막 보스를 스킬로 잡을 때, 전투플레이어 오류 수정
4. 대쉬 중에 디버프를 받는 버그 수정
2024-05-12 17:41:13 +09:00

517 lines
20 KiB
C#

using System;
using System.Collections;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.InputSystem;
// 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;
public HpSlider hpSlider;
}
[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;
[SerializeField] private ParticleSystem swordHit;
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 OnDestroy()
{
if (MyComponents.hpSlider)
{
Destroy(MyComponents.hpSlider.gameObject);
}
}
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 = CombatUiManager.Inst.MainSkillUi;
ui.gameObject.SetActive(true);
mainSkill.SkillInputData.InitInputData(transform, myCollider, myRb, null, myVisualLook, myAnimator, null, targetLayer, ui);
mainSkill.InitData();
}
}
private void InitStartValue()
{
MyCurrentValue.hitColliders = new Collider[MyCharacterOption.maxHitNum];
MyComponents.hpSlider = Instantiate(MyComponents.hpSlider, Vector3.zero, Quaternion.identity,
CombatUiManager.Inst.WorldSpaceCanvas.transform);
MyComponents.hpSlider.SetHpSlider(transform, MyCharacterOption.maxHp);
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);
MyComponents.hpSlider.UpdateHpSlider(changeHp);
// 죽었는지 체크
if (changeHp == 0f)
{
Die();
return;
}
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>();
if (iDamageable != null)
{
iDamageable.TakeDamage(damage);
var closestPoint = MyCurrentValue.hitColliders[i].ClosestPoint(transform.position);
//var spawnPosition = closestPoint + Random.insideUnitSphere * 0.2f;
Instantiate(swordHit, closestPoint, Quaternion.identity);
}
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;
if (MyCurrentValue.currentHp <= 30f)
{
CameraManager.Inst.CombatCamera.LowHpVignette();
}
else
{
CameraManager.Inst.CombatCamera.DefaultHpVignette();
}
}
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
}
}