OldBlueWater/BlueWater/Assets/02.Scripts/Character/CombatPlayer/PhysicsMovement.cs
NTG a27d4f1c8d #199 마을 에셋 적용
+ Terrain Rotate 에셋 추가
+ Combat 씬 시간대별 Light 추가
2024-02-17 19:48:34 +09:00

490 lines
18 KiB
C#

using System;
using System.Collections;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.InputSystem;
// ReSharper disable once CheckNamespace
namespace BlueWaterProject
{
public class PhysicsMovement : MonoBehaviour, IStun
{
/***********************************************************************
* Definitions
***********************************************************************/
#region Class
[Serializable]
public class Components
{
public CapsuleCollider capsuleCollider;
public Rigidbody rb;
[ShowIf("@false")]
public Animator animator;
public Transform[] spawnPosition;
}
[Serializable]
public class CheckOption
{
[Tooltip("지면으로 체크할 레이어 설정")]
public LayerMask groundLayer = -1;
[Range(0.01f, 0.5f), Tooltip("전방 감지 거리")]
public float forwardCheckDistance = 0.1f;
[Range(0.1f, 10.0f), Tooltip("지면 감지 거리")]
public float groundCheckDistance = 2.0f;
[Range(0.0f, 0.5f), Tooltip("지면 인식 허용 거리")]
public float groundCheckThreshold = 0.01f;
}
[Serializable]
public class MovementOption
{
[Range(1f, 10f), Tooltip("이동 속도")]
public float moveSpeed = 10f;
[Range(1f, 75f), Tooltip("등반 가능한 경사각")]
public float maxSlopeAngle = 50f;
[Range(1f, 50f), Tooltip("대쉬 속도")]
public float dashSpeed = 30f;
[Range(0.1f, 1f), Tooltip("대쉬 시간")]
public float dashTime = 0.2f;
[Range(0f, 5f), Tooltip("대쉬 쿨타임")]
public float dashCooldown = 0.5f;
}
[Serializable]
[DisableIf("@true")]
public class CurrentState
{
public bool enableMoving = true;
public bool isMoving;
public bool isGrounded;
public bool isOnSlope;
public bool isOnSteepSlope;
public bool isForwardBlocked;
public bool isOutOfControl;
public bool isDashing;
public bool enableDash = true;
public bool isStunned;
}
[Serializable]
[DisableIf("@true")]
public class CurrentValue
{
public Vector2 movementInput;
public Vector3 currentMoveDirection;
public Vector3 previousMoveDirection = Vector3.back;
public Vector3 groundNormal;
public Vector3 groundCross;
public Vector3 horizontalVelocity;
[Space]
public float outOfControlDuration;
[Space]
public float groundDistance;
public float groundSlopeAngle; // 현재 바닥의 경사각
public float forwardSlopeAngle; // 캐릭터가 바라보는 방향의 경사각
[Space]
public Vector3 gravity;
}
#endregion
/***********************************************************************
* Variables
***********************************************************************/
#region Variables
[field: SerializeField] public Components MyComponents { get; private set; } = new();
[field: SerializeField] public CheckOption MyCheckOption { get; private set; } = new();
[field: SerializeField] public MovementOption MyMovementOption { get; private set; } = new();
[field: SerializeField] public CurrentState MyCurrentState { get; set; } = new();
[field: SerializeField] public CurrentValue MyCurrentValue { get; set; } = new();
[Title("효과")]
[SerializeField] private ParticleSystem stunParticle;
public bool IsStunned
{
get => MyCurrentState.isStunned;
set => MyCurrentState.isStunned = value;
}
private float capsuleRadiusDifferent;
private float castRadius;
private Vector3 CapsuleTopCenterPoint => new(transform.position.x,
transform.position.y + MyComponents.capsuleCollider.height - MyComponents.capsuleCollider.radius,
transform.position.z);
private Vector3 CapsuleBottomCenterPoint => new(transform.position.x,
transform.position.y + MyComponents.capsuleCollider.radius,
transform.position.z);
public static readonly int IsDashingHash = Animator.StringToHash("isDashing");
#endregion
/***********************************************************************
* Unity Events
***********************************************************************/
#region Unity Events
private void Start()
{
InitRigidbody();
InitCapsuleCollider();
InitStartValue();
}
private void FixedUpdate()
{
if (!MyCurrentState.enableMoving || IsStunned) return;
InputMove();
CheckGround();
CheckForward();
UpdateValues();
CalculateMovements();
ApplyMovementsToRigidbody();
}
#endregion
/***********************************************************************
* Init Methods
***********************************************************************/
#region Init Methods
private void InitRigidbody()
{
if (TryGetComponent(out MyComponents.rb)) return;
MyComponents.rb = gameObject.AddComponent<Rigidbody>();
MyComponents.rb.constraints = RigidbodyConstraints.FreezeRotation;
MyComponents.rb.interpolation = RigidbodyInterpolation.Interpolate;
MyComponents.rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
MyComponents.rb.useGravity = true;
}
private void InitCapsuleCollider()
{
if (!TryGetComponent(out MyComponents.capsuleCollider))
{
MyComponents.capsuleCollider = gameObject.AddComponent<CapsuleCollider>();
MyComponents.capsuleCollider.height = 2f;
MyComponents.capsuleCollider.center = Vector3.up;
MyComponents.capsuleCollider.radius = 0.5f;
}
var capsuleColliderRadius = MyComponents.capsuleCollider.radius;
castRadius = capsuleColliderRadius * 0.9f;
capsuleRadiusDifferent = capsuleColliderRadius - castRadius + 0.05f;
}
private void InitStartValue()
{
MyCurrentValue.gravity = Physics.gravity;
if (stunParticle)
{
stunParticle.Stop();
}
}
#endregion
/***********************************************************************
* Interfaces
***********************************************************************/
#region Interfaces
public void Stun(float stunTime)
{
if (MyCurrentState.isDashing) return;
IsStunned = true;
if (stunParticle)
{
stunParticle.Play();
}
StartCoroutine(Utils.CoolDown(stunTime, StopStun));
}
private void StopStun()
{
if (stunParticle)
{
stunParticle.Stop();
}
IsStunned = false;
}
#endregion
/***********************************************************************
* PlayerInput
***********************************************************************/
#region PlayerInput
private void OnMove(InputValue value)
{
MyCurrentValue.movementInput = value.Get<Vector2>();
}
private void OnDash()
{
if (!MyCurrentState.enableDash || MyCurrentState.isDashing) return;
MyComponents.animator.SetBool(IsDashingHash, true);
}
#endregion
/***********************************************************************
* Methods
***********************************************************************/
#region Methods
private void InputMove()
{
if (MyCurrentValue.currentMoveDirection != Vector3.zero)
{
MyCurrentValue.previousMoveDirection = MyCurrentValue.currentMoveDirection;
}
MyCurrentValue.currentMoveDirection = MyCurrentState.isDashing
? MyCurrentValue.previousMoveDirection
: new Vector3(MyCurrentValue.movementInput.x, 0,MyCurrentValue.movementInput.y).normalized;
MyCurrentState.isMoving = MyCurrentValue.currentMoveDirection != Vector3.zero;
}
/// <summary> 하단 지면 검사 </summary>
private void CheckGround()
{
MyCurrentValue.groundDistance = float.MaxValue;
MyCurrentValue.groundNormal = Vector3.up;
MyCurrentValue.groundSlopeAngle = 0f;
MyCurrentValue.forwardSlopeAngle = 0f;
var groundRaycast = Physics.SphereCast(CapsuleBottomCenterPoint, castRadius,Vector3.down,
out var hit, MyCheckOption.groundCheckDistance, MyCheckOption.groundLayer, QueryTriggerInteraction.Ignore);
MyCurrentState.isGrounded = false;
if (groundRaycast)
{
MyCurrentValue.groundNormal = hit.normal;
MyCurrentValue.groundSlopeAngle = Vector3.Angle(MyCurrentValue.groundNormal, Vector3.up);
MyCurrentValue.forwardSlopeAngle = Vector3.Angle(MyCurrentValue.groundNormal, MyCurrentValue.currentMoveDirection) - 90f;
MyCurrentState.isOnSlope = MyCurrentValue.groundSlopeAngle > 0f && MyCurrentValue.groundSlopeAngle < MyMovementOption.maxSlopeAngle;
MyCurrentState.isOnSteepSlope = MyCurrentValue.groundSlopeAngle >= MyMovementOption.maxSlopeAngle;
// 경사각 이중검증 (수직 레이캐스트) : 뾰족하거나 각진 부분 체크
//if (State.isOnSteepSlope)
//{
// Vector3 ro = hit.point + Vector3.up * 0.1f;
// Vector3 rd = Vector3.down;
// bool rayD =
// Physics.SphereCast(ro, 0.09f, rd, out var hitRayD, 0.2f, COption.groundLayerMask, QueryTriggerInteraction.Ignore);
// Current.groundVerticalSlopeAngle = rayD ? Vector3.Angle(hitRayD.normal, Vector3.up) : Current.groundSlopeAngle;
// State.isOnSteepSlope = Current.groundVerticalSlopeAngle >= MOption.maxSlopeAngle;
//}
MyCurrentValue.groundDistance = Mathf.Max(hit.distance - capsuleRadiusDifferent - MyCheckOption.groundCheckThreshold, 0f);
MyCurrentState.isGrounded = (MyCurrentValue.groundDistance <= 0.0001f) && !MyCurrentState.isOnSteepSlope;
GizmosUpdateValue(ref gzGroundTouch, hit.point);
}
MyCurrentValue.groundCross = Vector3.Cross(MyCurrentValue.groundNormal, Vector3.up);
}
/// <summary> 전방 장애물 검사 : 레이어 관계 없이 trigger가 아닌 모든 장애물 검사 </summary>
private void CheckForward()
{
var obstacleRaycast = Physics.CapsuleCast(CapsuleBottomCenterPoint, CapsuleTopCenterPoint, castRadius,
MyCurrentValue.currentMoveDirection + Vector3.down * 0.1f,
out var hit, MyCheckOption.forwardCheckDistance, -1, QueryTriggerInteraction.Ignore);
MyCurrentState.isForwardBlocked = false;
if (obstacleRaycast)
{
var forwardObstacleAngle = Vector3.Angle(hit.normal, Vector3.up);
MyCurrentState.isForwardBlocked = forwardObstacleAngle >= MyMovementOption.maxSlopeAngle;
GizmosUpdateValue(ref gzForwardTouch, hit.point);
}
}
private void UpdateValues()
{
MyCurrentState.isOutOfControl = MyCurrentValue.outOfControlDuration > 0f;
if (MyCurrentState.isOutOfControl)
{
MyCurrentValue.outOfControlDuration -= Time.fixedDeltaTime;
MyCurrentValue.currentMoveDirection = Vector3.zero;
}
}
private void CalculateMovements()
{
if (MyCurrentState.isOutOfControl)
{
MyComponents.rb.useGravity = true;
MyCurrentValue.horizontalVelocity = Vector3.zero;
return;
}
var speed = 0f;
if (MyCurrentState.isDashing)
{
speed = MyMovementOption.dashSpeed;
}
else
{
speed = MyCurrentState.isMoving ? MyMovementOption.moveSpeed : 0f;
}
if (MyCurrentState.isOnSlope)
{
MyComponents.rb.useGravity = false;
if (MyCurrentState.isMoving)
{
var changeMoveDirection = Vector3.ProjectOnPlane(MyCurrentValue.currentMoveDirection, MyCurrentValue.groundNormal).normalized;
MyCurrentValue.horizontalVelocity = changeMoveDirection * speed;
}
else
{
MyCurrentValue.horizontalVelocity = Vector3.zero;
}
return;
}
MyComponents.rb.useGravity = true;
if (MyCurrentState.isForwardBlocked || !MyCurrentState.isGrounded)
{
MyCurrentValue.horizontalVelocity = Vector3.zero;
}
else
{
MyCurrentValue.horizontalVelocity = MyCurrentValue.currentMoveDirection * speed;
}
}
/// <summary> 리지드바디 최종 속도 적용 </summary>
private void ApplyMovementsToRigidbody()
{
Vector3 finalVelocity;
if (MyCurrentState.isOutOfControl || MyCurrentState.isOnSteepSlope || !MyCurrentState.isGrounded)
{
// var velocity = MyComponents.rb.velocity;
// finalVelocity = MyComponents.rb.position + new Vector3(velocity.x, MyCurrentValue.gravity.y, velocity.z) * Time.fixedDeltaTime;
// MyComponents.rb.MovePosition(finalVelocity);
return;
}
if (MyCurrentValue.horizontalVelocity == Vector3.zero)
{
MyComponents.rb.velocity = Vector3.zero;
return;
}
finalVelocity = MyComponents.rb.position + MyCurrentValue.horizontalVelocity * Time.fixedDeltaTime;
MyComponents.rb.MovePosition(finalVelocity);
// finalVelocity = MyCurrentValue.horizontalVelocity;
// MyComponents.rb.velocity = finalVelocity;
}
public void Move(Vector3 position) => MyComponents.rb.position = position;
public void MoveToCurrentDirection(float speed)
{
var finalVelocity = MyComponents.rb.position + MyCurrentValue.previousMoveDirection * (speed * Time.fixedDeltaTime);
MyComponents.rb.MovePosition(finalVelocity);
// var finalVelocity = MyCurrentValue.previousMoveDirection * speed;
// MyComponents.rb.velocity = finalVelocity;
}
public void SetEnableMoving(bool value) => MyCurrentState.enableMoving = value;
public bool GetIsMoving() => MyCurrentState.isMoving;
public bool GetIsDashing() => MyCurrentState.isDashing;
public float GetDashCooldown() => MyMovementOption.dashCooldown;
public float GetDashTime() => MyMovementOption.dashTime;
public void SetIsDashing(bool value) => MyCurrentState.isDashing = value;
public void SetEnableDashing(bool value) => MyCurrentState.enableDash = value;
public Vector3 GetPreviousMoveDirection() => MyCurrentValue.previousMoveDirection;
public void SetPreviousMoveDirection(Vector3 value) => MyCurrentValue.previousMoveDirection = value;
public void SetAnimator(Animator animator) => MyComponents.animator = animator;
public Vector3 GetCurrentPosition() => MyComponents.rb.position;
public void SetIsTrigger(bool value) => MyComponents.capsuleCollider.isTrigger = value;
public void SetUseGravity(bool value) => MyComponents.rb.useGravity = value;
#endregion
/***********************************************************************
* Gizmos, GUI
***********************************************************************/
#region Gizmos, GUI
private Vector3 gzGroundTouch;
private Vector3 gzForwardTouch;
[Header("Gizmos Option")] public bool showGizmos = true;
[SerializeField, Range(0.01f, 2f)] private float gizmoRadius = 0.05f;
[System.Diagnostics.Conditional("UNITY_EDITOR")]
private void OnDrawGizmos()
{
if (Application.isPlaying == false) return;
if (!showGizmos) return;
if (!enabled) return;
Gizmos.color = Color.red;
Gizmos.DrawSphere(gzGroundTouch, gizmoRadius);
if (MyCurrentState.isForwardBlocked)
{
Gizmos.color = Color.blue;
Gizmos.DrawSphere(gzForwardTouch, gizmoRadius);
}
Gizmos.color = Color.blue;
Gizmos.DrawLine(gzGroundTouch - MyCurrentValue.groundCross,
gzGroundTouch + MyCurrentValue.groundCross);
Gizmos.color = new Color(0.5f, 1.0f, 0.8f, 0.8f);
Gizmos.DrawWireSphere(CapsuleTopCenterPoint, castRadius);
Gizmos.DrawWireSphere(CapsuleBottomCenterPoint, castRadius);
}
[System.Diagnostics.Conditional("UNITY_EDITOR")]
private void GizmosUpdateValue<T>(ref T variable, in T value)
{
variable = value;
}
#endregion
}
}