This commit is contained in:
RochesterX
2025-04-17 18:32:27 -04:00
parent 6b7dbea593
commit 87eb6237e5
444 changed files with 28616 additions and 3331 deletions

View File

@@ -157,7 +157,7 @@ public class GameManager : MonoBehaviour
}
else
{
return;
//return;
}
}

View File

@@ -8,10 +8,14 @@ public class HatRespawn : MonoBehaviour
public const float respawnTime = 10f;
private bool isDropped;
public Vector2 initialSubhatPosition;
public static bool canBePickedUp = true; // Flag to check if the hat can be picked up
void Start()
{
initialSubhatPosition = transform.GetChild(0).transform.localPosition;
lastInteractionTime = Time.time;
isDropped = false;
transform.position = GameManager.Instance.hatSpawnPositions[Random.Range(0, GameManager.Instance.hatSpawnPositions.Count - 1)];

View File

@@ -1,145 +1,214 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Player
{
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
[RequireComponent(typeof(RespawnOnTriggerEnter))]
public class Damageable : MonoBehaviour
{
public float force = 50f; // Force applied when hit
public float damage = 0f;
public float maxDamage = 1000f; // Set max health
public int lives = 0;
private Animator animator;
public bool damageSelfDebug = false;
public bool dying = false;
public event System.Action<GameObject> OnPlayerPunched;
private void Start()
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
[RequireComponent(typeof(RespawnOnTriggerEnter))]
public class Damageable : MonoBehaviour
{
animator = GetComponent<Animator>();
}
private void Update()
{
if (damageSelfDebug)
/// <summary>
/// The force applied to the player when hit.
/// </summary>
public float force = 50f; // Force applied when hit
/// <summary>
/// The current accumulated damage of the player.
/// </summary>
public float damage = 0f;
/// <summary>
/// The maximum damage the player can take before dying.
/// </summary>
public float maxDamage = 1000f; // Set max health
/// <summary>
/// The number of lives the player has.
/// </summary>
public int lives = 0;
private Animator animator;
/// <summary>
/// If true, applies damage to self for debugging purposes.
/// </summary>
public bool damageSelfDebug = false;
/// <summary>
/// Indicates whether the player is currently dying.
/// </summary>
public bool dying = false;
/// <summary>
/// Event triggered when the player is punched.
/// </summary>
public event System.Action<GameObject> OnPlayerPunched;
/// <summary>
/// Unity Start method. Initializes the animator reference.
/// </summary>
private void Start()
{
damageSelfDebug = false;
Damage(gameObject);
animator = GetComponent<Animator>();
}
}
private void OnTriggerEnter2D(Collider2D collision) // Calls Damage method when player is hit
{
if (collision.gameObject.CompareTag("Punch Hurtbox"))
/// <summary>
/// Unity Update method. Handles debug self-damage if enabled.
/// </summary>
private void Update()
{
Damage(collision.transform.parent.gameObject);
}
}
private void Damage(GameObject damageSource) // Damages player
{
if (dying || damageSource.CompareTag("Hat")) return; // Exclude hat from taking damage
float actualForce = damageSource.GetComponent<Damageable>().force;
Block blockComponent = GetComponent<Block>();
GetComponentInChildren<UseItem>().DropItem(); // Drops hat if held
if (blockComponent != null && blockComponent.blocking)
{
if (blockComponent.IsParrying()) // Player receives damage if punching a parrying player
if (damageSelfDebug)
{
damageSource.GetComponent<Damageable>().SuccessfulParry(gameObject, actualForce);
AudioManager.Instance.PlaySound("Parry");
return;
damageSelfDebug = false;
Damage(gameObject);
}
else // Player does less damage if punching a blocking player
}
/// <summary>
/// Unity OnTriggerEnter2D method. Applies damage when colliding with a punch hurtbox.
/// </summary>
/// <param name="collision">The collider that entered the trigger.</param>
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Punch Hurtbox"))
{
Damage(collision.transform.parent.gameObject);
}
}
/// <summary>
/// Applies damage to the player from a given damage source.
/// Handles blocking, parrying, and force application.
/// </summary>
/// <param name="damageSource">The GameObject causing the damage.</param>
private void Damage(GameObject damageSource)
{
if (dying || damageSource.CompareTag("Hat")) return; // Exclude hat from taking damage
float actualForce = damageSource.GetComponent<Damageable>().force;
Block blockComponent = GetComponent<Block>();
GetComponentInChildren<UseItem>().DropItem(); // Drops hat if held
if (blockComponent != null && blockComponent.blocking)
{
if (blockComponent.IsParrying()) // Player receives damage if punching a parrying player
{
damageSource.GetComponent<Damageable>().SuccessfulParry(gameObject, actualForce);
AudioManager.Instance.PlaySound("Parry");
return;
}
else // Player does less damage if punching a blocking player
{
AudioManager.Instance.PlaySound("Punch");
actualForce /= 4;
GetComponent<Rigidbody2D>().AddForce(((transform.position - damageSource.transform.position).normalized + Vector3.up * 2) * actualForce, ForceMode2D.Force);
}
}
else // Player does full damage to a non-blocking player
{
AudioManager.Instance.PlaySound("Punch");
actualForce /= 4;
GetComponent<Rigidbody2D>().AddForce(((transform.position - damageSource.transform.position).normalized + Vector3.up * 2) * actualForce, ForceMode2D.Force);
GetComponent<Rigidbody2D>().AddForce(((transform.position - damageSource.transform.position).normalized + Vector3.up * 2) * actualForce * (1 + (damage / maxDamage) * 3), ForceMode2D.Force);
}
}
else // Player does full damage to a non-blocking player
{
AudioManager.Instance.PlaySound("Punch");
GetComponent<Rigidbody2D>().AddForce(((transform.position - damageSource.transform.position).normalized + Vector3.up * 2) * actualForce * (1 + (damage / maxDamage) * 3), ForceMode2D.Force);
}
damage += actualForce;
damage = Mathf.Clamp(damage, 0f, maxDamage);
if (damage >= maxDamage)
{
Die();
}
}
public void Damage(float damage) // Adds damage to player when hit
{
//if (GameManager.Instance.gameOver) return; // Prevent damage after game is over
this.damage += damage;
if (damage >= maxDamage)
{
Die();
}
}
private void SuccessfulParry(GameObject damageSource, float force)
{
GetComponent<Rigidbody2D>().AddForce(((transform.position - damageSource.transform.position).normalized + Vector3.up * 2) * force, ForceMode2D.Force);
damage += force;
damage = Mathf.Clamp(damage, 0f, maxDamage);
if (damage >= maxDamage)
{
Die();
}
}
private void Die() // Triggers death animation and sets player to dying state
{
if (GameManager.Instance != null && !GameManager.Instance.gameOver) // Prevent death after game is over
{
UseItem useItem = GetComponent<UseItem>();
if (useItem != null)
damage += actualForce;
damage = Mathf.Clamp(damage, 0f, maxDamage);
if (damage >= maxDamage)
{
useItem.DropItem(); // Ensure the player drops the item before the death animation
Die();
}
animator.SetBool("die", true);
dying = true;
AudioManager.Instance.PlaySound("Death Simple");
}
}
public void HandleDeath() // Removes player from dying state after respawn
{
GameManager.Instance.PlayerDied(this);
animator.SetBool("die", false);
dying = false;
}
public void Respawn() // Respawns player to the spawnPosition and resets damage/health bar
{
transform.position = GameManager.Instance.spawnPosition;
if (TryGetComponent<Rigidbody2D>(out var rb))
/// <summary>
/// Adds a specified amount of damage to the player.
/// </summary>
/// <param name="damage">The amount of damage to add.</param>
public void Damage(float damage)
{
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
}
if (TryGetComponent<Damageable>(out var damageable))
{
damageable.ResetDamage();
}
}
//if (GameManager.Instance.gameOver) return; // Prevent damage after game is over
public void ResetDamage()
{
damage = 0f;
this.damage += damage;
if (damage >= maxDamage)
{
Die();
}
}
/// <summary>
/// Handles the effects of a successful parry, applying force and damage.
/// </summary>
/// <param name="damageSource">The GameObject that was parried.</param>
/// <param name="force">The force to apply.</param>
private void SuccessfulParry(GameObject damageSource, float force)
{
GetComponent<Rigidbody2D>().AddForce(((transform.position - damageSource.transform.position).normalized + Vector3.up * 2) * force, ForceMode2D.Force);
damage += force;
damage = Mathf.Clamp(damage, 0f, maxDamage);
if (damage >= maxDamage)
{
Die();
}
}
/// <summary>
/// Triggers the death animation and sets the player to the dying state.
/// </summary>
private void Die()
{
if (GameManager.Instance != null) //&& !GameManager.Instance.gameOver) // Prevent death after game is over
{
UseItem useItem = GetComponent<UseItem>();
if (useItem != null)
{
useItem.DropItem(); // Ensure the player drops the item before the death animation
}
animator.SetBool("die", true);
dying = true;
AudioManager.Instance.PlaySound("Death Simple");
}
}
/// <summary>
/// Handles player state after death and resets dying state after respawn.
/// </summary>
public void HandleDeath()
{
print("Player " + gameObject.name + " died");
GameManager.Instance.PlayerDied(this);
animator.SetBool("die", false);
dying = false;
}
/// <summary>
/// Respawns the player at the spawn position and resets damage/health bar.
/// </summary>
public void Respawn()
{
transform.position = GameManager.Instance.spawnPosition;
if (TryGetComponent<Rigidbody2D>(out var rb))
{
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
}
if (TryGetComponent<Damageable>(out var damageable))
{
damageable.ResetDamage();
}
}
/// <summary>
/// Resets the player's damage to zero.
/// </summary>
public void ResetDamage()
{
damage = 0f;
}
}
}
}

View File

@@ -1,262 +1,432 @@
using System.Collections;
using TMPro;
using Unity.IO.LowLevel.Unsafe;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
using UnityEngine.InputSystem;
namespace Player
{
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(BoxCollider2D))]
[RequireComponent(typeof(PlayerInput))]
[RequireComponent(typeof(AnimationPlayer))]
[RequireComponent(typeof(Punch))]
public class PlayerMovement : MonoBehaviour
{
[Header("Ground Layers")]
public LayerMask ground;
public TextMeshProUGUI playerText;
[Header("Movement")]
public float walkSpeed;
public float walkSpeedFactor = 1f; // Sets walk speed
public float maxSpeed = 5f; // Sets max speed
public float maxSpeedOverride;
public float slowdownMultiplier = 10f; // Sets slow walk speed
public float virtualAxisX;
public float virtualButtonJump;
public float virtualButtonJumpLastFrame;
public float turnaroundMultiplier = 2; // Sets speed when turning around
public float walkSmooth;
public float secondsToFullSpeed;
public float jumpSpeed;
public float coyoteTime;
public float jumpLenience;
public float timeUnableToBeDeclaredNotJumping = 0.1f; // Jump threshold
public float groundCheckDistance;
private Rigidbody2D body;
private BoxCollider2D collide;
private PlayerInput input;
private AnimationPlayer animationPlayer;
private Punch punch;
private Damageable damageable;
private bool jumpInputStillValid = false;
private bool canBeDeclaredNotJumping = true;
private bool jumpPhysics;
private bool jumping;
private float lastTimeJumpPressed;
private float lastTimeOnGround;
private Vector3 positionLastFrame;
void Start() // Sets up player components
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(BoxCollider2D))]
[RequireComponent(typeof(PlayerInput))]
[RequireComponent(typeof(AnimationPlayer))]
[RequireComponent(typeof(Punch))]
public class PlayerMovement : MonoBehaviour
{
maxSpeedOverride = maxSpeed;
GetComponent<RespawnOnTriggerEnter>().spawnPoint = transform.position;
body = GetComponent<Rigidbody2D>();
collide = GetComponent<BoxCollider2D>();
input = GetComponent<PlayerInput>();
animationPlayer = GetComponent<AnimationPlayer>();
punch = GetComponent<Punch>();
damageable = GetComponent<Damageable>();
/// <summary>
/// Layers considered as ground for the player.
/// </summary>
[Header("Ground Layers")]
public LayerMask ground;
playerText.text = input.playerIndex.ToString();
}
/// <summary>
/// Reference to the player's UI text displaying player index.
/// </summary>
public TextMeshProUGUI playerText;
private void Update() // Updates player movement
{
if (GameManager.Instance != null && GameManager.Instance.gameOver) maxSpeed = 1f;
if (damageable.dying) return;
/// <summary>
/// Base walk speed of the player.
/// </summary>
[Header("Movement")]
public float walkSpeed;
Jump();
/// <summary>
/// Multiplier applied to walk speed.
/// </summary>
public float walkSpeedFactor = 1f;
UpdateVirtualAxis();
}
/// <summary>
/// Maximum allowed horizontal speed for the player.
/// </summary>
public float maxSpeed = 5f;
private void FixedUpdate()
{
JumpPhysics();
/// <summary>
/// Runtime override for the maximum speed.
/// </summary>
public float maxSpeedOverride;
HorizontalMovement();
/// <summary>
/// Multiplier for slowing down the player when exceeding max speed.
/// </summary>
public float slowdownMultiplier = 10f;
Land();
}
/// <summary>
/// Current value of the horizontal movement axis.
/// </summary>
public float virtualAxisX;
private void LateUpdate()
{
Animate();
}
/// <summary>
/// Current value of the jump button (pressed or not).
/// </summary>
public float virtualButtonJump;
private void Animate() // Sets player animation
{
if (!IsPhysicallyGrounded())
animationPlayer.SetState(AnimationPlayer.AnimationState.Jump);
else
/// <summary>
/// Value of the jump button in the previous frame.
/// </summary>
public float virtualButtonJumpLastFrame;
/// <summary>
/// Multiplier applied when turning around to adjust speed.
/// </summary>
public float turnaroundMultiplier = 2;
/// <summary>
/// Smoothing factor for walking movement.
/// </summary>
public float walkSmooth;
/// <summary>
/// Time in seconds to reach full speed from rest.
/// </summary>
public float secondsToFullSpeed;
/// <summary>
/// Force applied when jumping.
/// </summary>
public float jumpSpeed;
/// <summary>
/// Time window after leaving ground where jump is still allowed (coyote time).
/// </summary>
public float coyoteTime;
/// <summary>
/// Time window after pressing jump where jump is still buffered.
/// </summary>
public float jumpLenience;
/// <summary>
/// Minimum time before the player can be declared as not jumping.
/// </summary>
public float timeUnableToBeDeclaredNotJumping = 0.1f;
/// <summary>
/// Distance to check below the player for ground detection.
/// </summary>
public float groundCheckDistance;
/// <summary>
/// Reference to the Rigidbody2D component.
/// </summary>
private Rigidbody2D body;
/// <summary>
/// Reference to the BoxCollider2D component.
/// </summary>
private BoxCollider2D collide;
/// <summary>
/// Reference to the PlayerInput component.
/// </summary>
private PlayerInput input;
/// <summary>
/// Reference to the AnimationPlayer component.
/// </summary>
private AnimationPlayer animationPlayer;
/// <summary>
/// Reference to the Punch component.
/// </summary>
private Punch punch;
/// <summary>
/// Reference to the Damageable component.
/// </summary>
private Damageable damageable;
/// <summary>
/// Indicates if the jump input is still valid for buffered jumps.
/// </summary>
private bool jumpInputStillValid = false;
/// <summary>
/// Indicates if the player can be declared as not jumping.
/// </summary>
private bool canBeDeclaredNotJumping = true;
/// <summary>
/// Indicates if jump physics should be applied this frame.
/// </summary>
private bool jumpPhysics;
/// <summary>
/// Indicates if the player is currently jumping.
/// </summary>
private bool jumping;
/// <summary>
/// The last time the jump button was pressed.
/// </summary>
private float lastTimeJumpPressed;
/// <summary>
/// The last time the player was on the ground.
/// </summary>
private float lastTimeOnGround;
/// <summary>
/// The player's position in the previous frame.
/// </summary>
private Vector3 positionLastFrame;
/// <summary>
/// Initializes player components and sets up initial values.
/// </summary>
void Start()
{
if (Mathf.Abs(virtualAxisX) >= 0.05f)
animationPlayer.SetState(GameManager.Instance.gameOver ? AnimationPlayer.AnimationState.Walk : AnimationPlayer.AnimationState.Run);
maxSpeedOverride = maxSpeed;
GetComponent<RespawnOnTriggerEnter>().spawnPoint = transform.position;
body = GetComponent<Rigidbody2D>();
collide = GetComponent<BoxCollider2D>();
input = GetComponent<PlayerInput>();
animationPlayer = GetComponent<AnimationPlayer>();
punch = GetComponent<Punch>();
damageable = GetComponent<Damageable>();
playerText.text = input.playerIndex.ToString();
}
/// <summary>
/// Handles per-frame updates for player movement and jump input.
/// </summary>
private void Update()
{
if (GameManager.Instance != null && GameManager.Instance.gameOver) maxSpeed = 1f;
UpdateVirtualAxis();
if (damageable.dying) return;
Jump();
}
/// <summary>
/// Handles physics-based updates for jumping and horizontal movement.
/// </summary>
private void FixedUpdate()
{
JumpPhysics();
HorizontalMovement();
Land();
}
/// <summary>
/// Handles late frame updates, such as animation.
/// </summary>
private void LateUpdate()
{
Animate();
}
/// <summary>
/// Updates the player's animation state based on movement and grounded status.
/// </summary>
private void Animate()
{
if (!IsPhysicallyGrounded())
animationPlayer.SetState(AnimationPlayer.AnimationState.Jump);
else
animationPlayer.SetState(AnimationPlayer.AnimationState.Idle);
}
if (true)
{
if (virtualAxisX < -0.01f)
animationPlayer.backwards = true;
else if (virtualAxisX > 0.01f)
animationPlayer.backwards = false;
}
else
{
if (body.linearVelocityX < -0.1f)
animationPlayer.backwards = true;
else if (body.linearVelocityX > 0.1f)
animationPlayer.backwards = false;
}
}
private void Land() // Stops jumping when player lands
{
if (body.linearVelocity.y >= 0f) return;
if (IsPhysicallyGrounded())
{
if (canBeDeclaredNotJumping)
{
jumping = false;
if (Mathf.Abs(virtualAxisX) >= 0.05f)
animationPlayer.SetState(GameManager.Instance.gameOver ? AnimationPlayer.AnimationState.Walk : AnimationPlayer.AnimationState.Run);
else
animationPlayer.SetState(AnimationPlayer.AnimationState.Idle);
}
if (true)
{
if (virtualAxisX < -0.01f)
animationPlayer.backwards = true;
else if (virtualAxisX > 0.01f)
animationPlayer.backwards = false;
}
else
{
if (body.linearVelocityX < -0.1f)
animationPlayer.backwards = true;
else if (body.linearVelocityX > 0.1f)
animationPlayer.backwards = false;
}
}
}
private void Jump() // Player jumps when 'jump' is pressed
{
if (virtualButtonJumpLastFrame == 1f)
/// <summary>
/// Handles logic for landing and stopping the jump state when grounded.
/// </summary>
private void Land()
{
jumpInputStillValid = true;
lastTimeJumpPressed = Time.time;
}
if (body.linearVelocity.y >= 0f) return;
bool isBasicallyGrounded = IsBasicallyGrounded();
if ((virtualButtonJumpLastFrame == 1f && isBasicallyGrounded && jumping == false) // Coyote Jump: Must have jump pressed this frame and be grounded in last time frame and not be actually jumping.
|| (jumpInputStillValid && Time.time - lastTimeJumpPressed <= jumpLenience && IsPhysicallyGrounded())) // Buffered Jump: Must have pressed jump in the last time frame and be jumping
{
AudioManager.Instance.PlaySound("Jump");
jumpPhysics = true;
jumping = true;
jumpInputStillValid = false;
StartCoroutine(NotJumpingDelay());
}
}
private void JumpPhysics() // Applies jump physics
{
if (jumpPhysics)
{
if (!GetComponent<Block>().blocking)
if (IsPhysicallyGrounded())
{
if (body.linearVelocity.y < 0 || !IsPhysicallyGrounded()) body.linearVelocity = new Vector2(body.linearVelocity.x, 0);
body.AddForce(Vector2.up * jumpSpeed, ForceMode2D.Impulse);
if (Mathf.Abs(body.linearVelocityX) > maxSpeed)
if (canBeDeclaredNotJumping)
{
body.linearVelocity = new Vector2(Mathf.Sign(body.linearVelocityX) * maxSpeed, body.linearVelocity.y);
jumping = false;
}
}
jumpPhysics = false;
}
if (!IsPhysicallyGrounded() && !(virtualButtonJump == 1f))
body.AddForce(Vector2.down * jumpSpeed);
}
private IEnumerator NotJumpingDelay() // Sets jump threshold
{
canBeDeclaredNotJumping = false;
yield return new WaitUntil(() => !IsBasicallyGrounded());
canBeDeclaredNotJumping = true;
}
private void HorizontalMovement() // Sets player horizontal movement
{
float temporaryMax = IsPhysicallyGrounded() ? maxSpeedOverride : Mathf.Infinity;
float temporarySlowdown = IsPhysicallyGrounded() ? slowdownMultiplier : 1;
if (!GetComponent<Block>().blocking && (Mathf.Abs(body.linearVelocityX) <= maxSpeed || Mathf.Sign(body.linearVelocityX) != Mathf.Sign(virtualAxisX)))
/// <summary>
/// Handles jump input and determines if a jump should be triggered.
/// </summary>
private void Jump()
{
body.AddForce(new Vector2(virtualAxisX * walkSpeed * walkSpeedFactor, 0), ForceMode2D.Force);
if (virtualButtonJumpLastFrame == 1f)
{
jumpInputStillValid = true;
lastTimeJumpPressed = Time.time;
}
bool isBasicallyGrounded = IsBasicallyGrounded();
if ((virtualButtonJumpLastFrame == 1f && isBasicallyGrounded && jumping == false)
|| (jumpInputStillValid && Time.time - lastTimeJumpPressed <= jumpLenience && IsPhysicallyGrounded()))
{
AudioManager.Instance.PlaySound("Jump");
jumpPhysics = true;
jumping = true;
jumpInputStillValid = false;
StartCoroutine(NotJumpingDelay());
}
}
if (Mathf.Abs(body.linearVelocityX) >= temporaryMax)
/// <summary>
/// Applies jump physics and forces to the player.
/// </summary>
private void JumpPhysics()
{
body.AddForce(new Vector2(-Mathf.Sign(body.linearVelocityX) * (Mathf.Abs(body.linearVelocityX) - temporaryMax) * temporarySlowdown, 0));
if (jumpPhysics)
{
if (!GetComponent<Block>().blocking)
{
if (body.linearVelocity.y < 0 || !IsPhysicallyGrounded()) body.linearVelocity = new Vector2(body.linearVelocity.x, 0);
body.AddForce(Vector2.up * jumpSpeed, ForceMode2D.Impulse);
if (Mathf.Abs(body.linearVelocityX) > maxSpeed)
{
body.linearVelocity = new Vector2(Mathf.Sign(body.linearVelocityX) * maxSpeed, body.linearVelocity.y);
}
}
jumpPhysics = false;
}
if (!IsPhysicallyGrounded() && !(virtualButtonJump == 1f))
body.AddForce(Vector2.down * jumpSpeed);
}
if (transform.position == positionLastFrame && (input.actions.FindAction("Move").ReadValue<Vector2>().x == 0))
/// <summary>
/// Coroutine that delays the ability to declare the player as not jumping.
/// </summary>
private IEnumerator NotJumpingDelay()
{
virtualAxisX = 0;
canBeDeclaredNotJumping = false;
yield return new WaitUntil(() => !IsBasicallyGrounded());
canBeDeclaredNotJumping = true;
}
if (GetComponent<Block>().blocking)
/// <summary>
/// Handles horizontal movement, including acceleration, deceleration, and blocking.
/// </summary>
private void HorizontalMovement()
{
body.AddForce(new Vector2(-body.linearVelocityX * 0.8f, 0), ForceMode2D.Force);
float temporaryMax = IsPhysicallyGrounded() ? maxSpeedOverride : Mathf.Infinity;
float temporarySlowdown = IsPhysicallyGrounded() ? slowdownMultiplier : 1;
if (!GetComponent<Block>().blocking && (Mathf.Abs(body.linearVelocityX) <= maxSpeed || Mathf.Sign(body.linearVelocityX) != Mathf.Sign(virtualAxisX)))
{
body.AddForce(new Vector2(virtualAxisX * walkSpeed * walkSpeedFactor, 0), ForceMode2D.Force);
}
if (Mathf.Abs(body.linearVelocityX) >= temporaryMax)
{
body.AddForce(new Vector2(-Mathf.Sign(body.linearVelocityX) * (Mathf.Abs(body.linearVelocityX) - temporaryMax) * temporarySlowdown, 0));
}
if (transform.position == positionLastFrame && (input.actions.FindAction("Move").ReadValue<Vector2>().x == 0))
{
virtualAxisX = 0;
}
if (GetComponent<Block>().blocking)
{
body.AddForce(new Vector2(-body.linearVelocityX * 0.8f, 0), ForceMode2D.Force);
}
positionLastFrame = transform.position;
}
positionLastFrame = transform.position;
}
private void UpdateVirtualAxis() // Updates virtual axis
{
virtualButtonJump = input.actions.FindAction("Action").ReadValue<float>();
virtualButtonJumpLastFrame = input.actions.FindAction("Action").WasPressedThisFrame() ? 1 : 0;
virtualAxisX = input.actions.FindAction("Move").ReadValue<Vector2>().x;
return;
}
public bool IsBasicallyGrounded() // Checks if player is on land within a threshold
{
if (IsPhysicallyGrounded())
/// <summary>
/// Updates the virtual axis and button values from input actions.
/// </summary>
private void UpdateVirtualAxis()
{
lastTimeOnGround = Time.time;
virtualButtonJump = input.actions.FindAction("Action").ReadValue<float>();
virtualButtonJumpLastFrame = input.actions.FindAction("Action").WasPressedThisFrame() ? 1 : 0;
virtualAxisX = input.actions.FindAction("Move").ReadValue<Vector2>().x;
return;
}
if (Time.time - lastTimeOnGround < coyoteTime)
/// <summary>
/// Checks if the player is considered grounded, including coyote time.
/// </summary>
/// <returns>True if the player is basically grounded, otherwise false.</returns>
public bool IsBasicallyGrounded()
{
return true;
if (IsPhysicallyGrounded())
{
lastTimeOnGround = Time.time;
}
if (Time.time - lastTimeOnGround < coyoteTime)
{
return true;
}
return false;
}
return false;
}
public bool IsPhysicallyGrounded() // Checks if player is on land
{
RaycastHit2D leftCheck = Physics2D.Raycast(GetPointInBoxCollider(collide, -1, -1), Vector2.down, groundCheckDistance, ground);
RaycastHit2D rightCheck = Physics2D.Raycast(GetPointInBoxCollider(collide, 1, -1), Vector2.down, groundCheckDistance, ground);
RaycastHit2D midCheck = Physics2D.Raycast(GetPointInBoxCollider(collide, 0, -1), Vector2.down, groundCheckDistance, ground);
if (leftCheck || rightCheck || midCheck)
/// <summary>
/// Checks if the player is physically touching the ground using raycasts.
/// </summary>
/// <returns>True if the player is physically grounded, otherwise false.</returns>
public bool IsPhysicallyGrounded()
{
return true;
RaycastHit2D leftCheck = Physics2D.Raycast(GetPointInBoxCollider(collide, -1, -1), Vector2.down, groundCheckDistance, ground);
RaycastHit2D rightCheck = Physics2D.Raycast(GetPointInBoxCollider(collide, 1, -1), Vector2.down, groundCheckDistance, ground);
RaycastHit2D midCheck = Physics2D.Raycast(GetPointInBoxCollider(collide, 0, -1), Vector2.down, groundCheckDistance, ground);
if (leftCheck || rightCheck || midCheck)
{
return true;
}
return false;
}
return false;
}
/// <summary>
/// Gets a point on the BoxCollider2D based on horizontal and vertical multipliers.
/// </summary>
/// <param name="boxCollider2D">The BoxCollider2D to use.</param>
/// <param name="horizontal">Horizontal offset (-1 for left, 1 for right, 0 for center).</param>
/// <param name="vertical">Vertical offset (-1 for bottom, 1 for top, 0 for center).</param>
/// <returns>The calculated point in world space.</returns>
public Vector2 GetPointInBoxCollider(BoxCollider2D boxCollider2D, float horizontal, float vertical)
{
return new Vector2
(
boxCollider2D.bounds.center.x + (horizontal * boxCollider2D.bounds.extents.x),
boxCollider2D.bounds.center.y + (vertical * boxCollider2D.bounds.extents.y)
);
}
public Vector2 GetPointInBoxCollider(BoxCollider2D boxCollider2D, float horizontal, float vertical)
{
return new Vector2
(
boxCollider2D.bounds.center.x + (horizontal * boxCollider2D.bounds.extents.x),
boxCollider2D.bounds.center.y + (vertical * boxCollider2D.bounds.extents.y)
);
}
/// <summary>
/// Stops the player's velocity if grounded, removing inertia.
/// </summary>
public void StopVelocity()
{
if (IsPhysicallyGrounded()) body.linearVelocity = Vector2.zero;
}
public void StopVelocity() // Stops inertia when landed
{
if (IsPhysicallyGrounded()) body.linearVelocity = Vector2.zero;
}
}
}

View File

@@ -1,101 +1,105 @@
using System.Collections;
using UnityEngine; using Game; using Music; using Player;
using UnityEngine;
using Game;
using Music;
using Player;
namespace Player
{
public class UseItem : MonoBehaviour
{
[SerializeField] private string itemTag;
private GameObject heldItem;
private bool isHoldingItem = false;
private float holdStartTime;
public float holdTime;
private Damageable damageable;
[SerializeField] public Transform head;
private void Start()
public class UseItem : MonoBehaviour
{
damageable = GetComponent<Damageable>();
}
[SerializeField] private string itemTag;
private GameObject heldItem;
private bool isHoldingItem = false;
private float holdStartTime;
public float holdTime;
private Damageable damageable;
void Update()
{
if (isHoldingItem)
[SerializeField] public Transform head;
private void Start()
{
// Keeps hat on the player's head
heldItem.transform.localPosition = Vector3.zero;
if (GameManager.gameMode == GameManager.GameMode.keepAway)
damageable = GetComponent<Damageable>();
}
void Update()
{
if (isHoldingItem)
{
// Adds time to the player's leaderboard standing
holdTime += Time.deltaTime;
GameManager.Instance.UpdatePlayerHoldTime(gameObject, holdTime);
// Keeps hat on the player's head
heldItem.transform.localPosition = Vector3.zero;
if (GameManager.gameMode == GameManager.GameMode.keepAway)
{
// Adds time to the player's leaderboard standing
holdTime += Time.deltaTime;
GameManager.Instance.UpdatePlayerHoldTime(gameObject, holdTime);
}
}
}
}
private void OnCollisionEnter2D(Collision2D collision) // Player automatically picks up hat when touching it
{
if (collision.gameObject.CompareTag("Hat") && !isHoldingItem && !damageable.dying)
private void OnCollisionEnter2D(Collision2D collision) // Player automatically picks up hat when touching it
{
PickUpItem(collision.gameObject);
}
}
public void PickUpItem(GameObject item) // Player picks up hat and starts hold counter
{
if (damageable.dying) return; // Prevent picking up items if the player is dying
if (HatRespawn.canBePickedUp == false) return; // Prevent picking up items if they are not interactable
heldItem = item;
isHoldingItem = true;
holdStartTime = Time.time;
item.GetComponent<Collider2D>().enabled = false;
item.GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Static;
item.GetComponent<HatRespawn>().Interact();
item.transform.parent = head;
item.transform.localRotation = Quaternion.identity;
item.transform.localPosition = Vector3.zero;
if (!GameManager.playerHoldTimes.ContainsKey(gameObject))
{
GameManager.playerHoldTimes[gameObject] = 0f;
}
AudioManager.Instance.PlaySound("Pickup Hat");
GameManager.Instance.StopCoroutine("MoveHatToWinner");
}
public void DropItem() // Player drops hat when hit
{
if (GameManager.Instance.gameOver) return; // Prevent dropping items if the game is over
if (isHoldingItem)
{
heldItem.GetComponent<Collider2D>().enabled = true;
HatRespawn.canBePickedUp = false;
StartCoroutine(WaitForInteractability());
heldItem.GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Dynamic;
heldItem.GetComponent<Rigidbody2D>().AddForce(Vector2.up * Random.Range(10f, 30f) + Vector2.right * Random.Range(-10, 10), ForceMode2D.Impulse);
heldItem.GetComponent<Rigidbody2D>().AddTorque(Random.Range(-5, 5), ForceMode2D.Impulse);
heldItem.GetComponent<HatRespawn>().OnHatDropped();
heldItem.transform.parent = GameManager.Instance.transform;
heldItem = null;
isHoldingItem = false;
if (GameManager.playerHoldTimes.ContainsKey(gameObject))
if (collision.gameObject.CompareTag("Hat") && !isHoldingItem && !damageable.dying)
{
GameManager.playerHoldTimes.Remove(gameObject);
PickUpItem(collision.gameObject);
}
}
}
private IEnumerator WaitForInteractability()
{
yield return new WaitForSeconds(0.1f);
HatRespawn.canBePickedUp = true;
}
public void PickUpItem(GameObject item) // Player picks up hat and starts hold counter
{
if (damageable.dying) return; // Prevent picking up items if the player is dying
if (HatRespawn.canBePickedUp == false) return; // Prevent picking up items if they are not interactable
public bool IsHoldingItem()
{
return isHoldingItem;
heldItem = item;
isHoldingItem = true;
holdStartTime = Time.time;
item.GetComponent<Collider2D>().enabled = false;
item.GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Static;
item.GetComponent<HatRespawn>().Interact();
item.transform.parent = head;
item.transform.localRotation = Quaternion.identity;
item.transform.localPosition = Vector3.zero;
item.transform.GetChild(0).transform.localPosition = item.GetComponent<HatRespawn>().initialSubhatPosition;
if (!GameManager.playerHoldTimes.ContainsKey(gameObject))
{
GameManager.playerHoldTimes[gameObject] = 0f;
}
AudioManager.Instance.PlaySound("Pickup Hat");
GameManager.Instance.StopCoroutine("MoveHatToWinner");
}
public void DropItem() // Player drops hat when hit
{
if (GameManager.Instance.gameOver) return; // Prevent dropping items if the game is over
if (isHoldingItem)
{
heldItem.GetComponent<Collider2D>().enabled = true;
HatRespawn.canBePickedUp = false;
StartCoroutine(WaitForInteractability());
heldItem.GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Dynamic;
heldItem.GetComponent<Rigidbody2D>().AddForce(Vector2.up * Random.Range(10f, 30f) + Vector2.right * Random.Range(-10, 10), ForceMode2D.Impulse);
heldItem.GetComponent<Rigidbody2D>().AddTorque(Random.Range(-5, 5), ForceMode2D.Impulse);
heldItem.GetComponent<HatRespawn>().OnHatDropped();
heldItem.transform.parent = GameManager.Instance.transform;
heldItem = null;
isHoldingItem = false;
if (GameManager.playerHoldTimes.ContainsKey(gameObject))
{
GameManager.playerHoldTimes.Remove(gameObject);
}
}
}
private IEnumerator WaitForInteractability()
{
yield return new WaitForSeconds(0.1f);
HatRespawn.canBePickedUp = true;
}
public bool IsHoldingItem()
{
return isHoldingItem;
}
}
}
}