using System; using System.Collections; using System.Data; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using static UnityEngine.EventSystems.EventTrigger; [SelectionBase] public class Player : MonoBehaviour { public static Player Instance { get; private set; } [Header("Asset/Prefab")] [SerializeField] public BuilderManager Builder; [Header("Movement")] [SerializeField] private Rigidbody2D Rigidbody; [SerializeField] public Animator Animator; [SerializeField] private SpriteRenderer Renderer; [Header("Character Class")] [SerializeField] private ParticleSystem Aura; [SerializeField] private GameObject[] ClassIndicators; private ClassBase ActiveClass; [SerializeField] private MeleeFighterClass FighterClass; public PlayerAttackAnimatorFactory AttackAnimator; [Header("Stamina")] [SerializeField] public float StaminaMax = 100; [SerializeField] public float Stamina = 0; [SerializeField] public float StaminaRegenPerSecond = 5; [SerializeField] public Slider StaminaSliderHud; [Header("Movement Attributes")] [SerializeField] public float MoveSpeed = 8; [SerializeField] public float MoveSpeedDampener = 1; [SerializeField] private float DashSpeedInitial = 200; [SerializeField] private float DashSpeed = 0; [SerializeField] private float DashDecayRate = 30; [SerializeField] private float DashDelay1 = 0.05f; [SerializeField] private float DashDelay2 = 0.05f; [SerializeField] private float DashCooldown = 0.1f; //[SerializeField] private float DriftSpeed = 60; //[SerializeField] private float DriftFactorial = 0.85f; [SerializeField] private float JumpDelay = 0.3f; [Header("VFX")] [SerializeField] private GameObject VfxDash; private VfxHandlerBase VfxDashHandler; [SerializeField] private GameObject VfxKineticSurge; [HideInInspector] public VfxHandlerBase VfxKineticSurgeHandler; [SerializeField] private GameObject VfxShockwave; [HideInInspector] public GameObjectPool VfxShockwavePool; private BoxCollider2D[] BoxColliders; private bool LockMovement; public Vector2 PrevDirection = Vector2.zero; private Vector2 MoveDirection = Vector2.zero; private Vector2 DashDirection = Vector2.zero; public bool IsJumping { get; set; } public float LastJumpTime { get; private set; } public bool ActionAfterJumpReady { get { return (!IsJumping || (Time.time - LastJumpTime > 0.03f)); } } public bool IsDashing { get; private set; } public float DashTime { get; private set; } public bool CanDash { get; private set; } = true; public bool SkillInUse = false; private enum Directions { Left, Right, Up, Down } void Awake() { Instance = this; Builder = GetComponent(); VfxDashHandler = new VfxHandlerBase(VfxDash, 5, 5); VfxKineticSurgeHandler = new VfxHandlerBase(VfxKineticSurge, 5, 5); FighterClass = new MeleeFighterClass(this); VfxShockwavePool = new GameObjectPool(VfxShockwave, 5, 5); BoxColliders = GetComponentsInChildren(); StaminaSliderHud.maxValue = StaminaMax; SetClass(1); } private void Update() { KeyPressActions(); GatherInput(); UpdatePlayerStatus(); UpdateActiveClass(); } private void GatherInput() { if (LockMovement) return; MoveDirection.x = Input.GetAxisRaw("Horizontal"); MoveDirection.y = Input.GetAxisRaw("Vertical"); if (MoveDirection.x != 0 || MoveDirection.y != 0) PrevDirection = MoveDirection; } private void KeyPressActions() { if (Input.GetKeyDown(KeyCode.LeftShift) || Input.GetKeyDown(KeyCode.RightShift)) { DoDash(); } else if (!IsJumping && Input.GetKeyDown(KeyCode.Space)) { Jump(); } else if (Input.GetKeyDown(KeyCode.F1)) { SetClass(0); } else if (Input.GetKeyDown(KeyCode.F2)) { SetClass(1); } else if (Input.GetKeyDown(KeyCode.F3)) { SetClass(2); } else if (Input.GetKeyDown(KeyCode.F4)) { SetClass(3); } if (Input.GetKeyDown(KeyCode.X)) ActiveClass.HandlePrimaryAttack(); else if (Input.GetKeyDown(KeyCode.C)) ActiveClass.HandleSecondaryAttack(); } private void DoDash() { if (!CanDash) return; if (!ActionAfterJumpReady) return; if (LockMovement) return; if (Stamina < 25) return; Stamina -= 25; CanDash = false; IsDashing = true; DashDirection = MoveDirection.normalized; if (DashDirection == Vector2.zero) return; DashSpeed = DashSpeedInitial; if (IsJumping) { LockMovement = true; DashSpeed /= 2; } } private void Jump(bool setJumpTime = true) { if (SkillInUse) return; IsJumping = true; if (setJumpTime) LastJumpTime = Time.time; foreach (var col in BoxColliders) col.enabled = false; if (CoroutineJumpReset != null) StopCoroutine(CoroutineJumpReset); CoroutineJumpReset = StartCoroutine(ResetJumpAfterDelay()); MoveSpeedDampener = 2; } public Coroutine CoroutineJumpReset { get; private set; } private IEnumerator ResetJumpAfterDelay() { yield return new WaitForSeconds(JumpDelay); foreach (var col in BoxColliders) col.enabled = true; IsJumping = false; LockMovement = false; MoveSpeedDampener = 1; } private void SetClass(int classIdx) { Builder.SetBuildMode(classIdx == 3); ActiveClass = FighterClass; foreach (var (indicator, i) in ClassIndicators.Select((obj, i) => (obj, i))) if (i != classIdx) { indicator.transform.localScale = Vector3.one; indicator.transform.rotation = Quaternion.identity; } else { indicator.transform.localScale = Vector3.one * 2; indicator.transform.rotation = Quaternion.AngleAxis(90, Vector3.forward); } RawImage image = ClassIndicators[classIdx].GetComponent(); var main = Aura.main; main.startColor = image.color; } private void UpdatePlayerStatus() { if (Stamina == StaminaMax) return; Stamina = Mathf.Min(Stamina + StaminaRegenPerSecond * Time.deltaTime, StaminaMax); StaminaSliderHud.value = Stamina; } private void UpdateActiveClass() { if (ActiveClass == null) return; ActiveClass.Tick(); if (ActiveClass.HitBoxDraw == null) return; if (!ActiveClass.HitBoxDraw.Active) return; Collider2D[] hits = Physics2D.OverlapCircleAll(ActiveClass.HitBoxDraw.Center, ActiveClass.HitBoxDraw.Radius); foreach (var hit in hits) { if (!hit.CompareTag("EnemyHurtBox")) continue; GameObject parent = hit.transform.parent?.gameObject; //EnemySpawnerData.EnemyMap[parent].TakeDamage(0, transform.position - parent.transform.position); EnemySpawnerData.EnemyMap[parent].TakeDamage(0, PrevDirection); } } private void FixedUpdate() { MovementUpdate(); UpdateAnimation(); } private void MovementUpdate() { var movement = (MoveDirection.normalized * MoveSpeed) / MoveSpeedDampener; if (IsDashing) { if (DashSpeed < (IsJumping ? 0.4f : 0.2f)) { DashSpeed = 0; CanDash = true; IsDashing = false; } else if (DashSpeed > (movement.magnitude * 2)) { if (IsJumping) Jump(false); movement = DashDirection * DashSpeed; if (IsJumping) DashSpeed *= Mathf.Exp(-DashDecayRate * Time.deltaTime / 3); else DashSpeed *= Mathf.Exp(-DashDecayRate * Time.deltaTime); } else { movement = ((DashDirection * DashSpeed) + movement) / 2; DashSpeed *= Mathf.Exp(-DashDecayRate * Time.deltaTime); } } Rigidbody.linearVelocity = movement; } private void UpdateAnimation() { string state = GetAnimationState(MoveDirection); if (state == "") return; if (state.Last() == '_') state = state.Replace("_", ""); else state += LastDirection.ToString(); Animator.CrossFade(state, 0); } public enum Direction { Up, Down, Left, Right } public Direction LastDirection = Direction.Down; private string GetAnimationState(Vector2 input) { if (Mathf.Abs(input.x) > 0) LastDirection = (input.x > 0) ? Direction.Right : Direction.Left; else if (Mathf.Abs(input.y) > 0) LastDirection = (input.y > 0) ? Direction.Up : Direction.Down; if (SkillInUse) return ActiveClass.AnimationToPlay; if (!IsJumping && input.sqrMagnitude < 0.01f) return "Idle"; if (IsJumping) return "Jump"; return "Run"; } //[Header("Health Settings")] //public int maxHealth = 100; //public float invincibilityTime = 1f; //[Header("Knockback")] //public float knockbackForce = 5f; //private Rigidbody2D rb; //private PlayerHealth health; //void Start() { // rb = GetComponent(); // health = new PlayerHealth(maxHealth, invincibilityTime); // health.OnTakeDamage += HandleDamage; // health.OnDeath += HandleDeath; // health.OnHeal += HandleHeal; //} //void HandleDamage(int damage, Vector2 hitDirection) { // Debug.Log($"Took {damage} damage from {hitDirection}. Remaining: {health.CurrentHealth}"); // // Apply knockback // Vector2 knockDir = hitDirection.normalized; // rb.velocity = Vector2.zero; // cancel momentum before applying // rb.AddForce(knockDir * knockbackForce, ForceMode2D.Impulse); // // TODO: Play hit animation, flash sprite, etc. //} //void HandleHeal(int amount) { // Debug.Log($"Healed {amount}. Current HP: {health.CurrentHealth}"); //} //void HandleDeath() { // Debug.Log("Player has died."); // // TODO: Trigger death animation, disable input, etc. //} //public void ReceiveHit(int damage, Vector2 hitOrigin) { // Vector2 hitDirection = (transform.position - (Vector3)hitOrigin).normalized; // health.TakeDamage(damage, hitDirection); //} //public void Heal(int amount) { // health.Heal(amount); //} public static Action GizmoTick; void OnDrawGizmos() { DrawPlayerDirection(); GizmoTick?.Invoke(); } public static void DrawWireSphere(Color color, Vector3 center, float radius) { Gizmos.color = color; Gizmos.DrawWireSphere(center, radius); } private void DrawPlayerDirection() { if (Rigidbody == null) Rigidbody = GetComponent(); if (Rigidbody == null) return; Gizmos.color = Color.cyan; Vector3 start = Rigidbody.transform.position; Vector3 end = start + (Vector3)(Rigidbody.linearVelocity * 1); Gizmos.DrawLine(start, end); DrawArrowHead(end, (end - start).normalized); } void DrawArrowHead(Vector3 position, Vector3 direction) { float arrowHeadAngle = 20f; float arrowHeadLength = 0.25f; Vector3 right = Quaternion.Euler(0, 0, arrowHeadAngle) * -direction; Vector3 left = Quaternion.Euler(0, 0, -arrowHeadAngle) * -direction; Gizmos.DrawLine(position, position + right * arrowHeadLength); Gizmos.DrawLine(position, position + left * arrowHeadLength); } }