added comments and organized project files

This commit is contained in:
djkellerman
2025-03-28 17:39:07 -04:00
parent 22f44a3f20
commit a098e8c053
84 changed files with 183 additions and 214 deletions

View File

@@ -0,0 +1,35 @@
using UnityEngine;
[RequireComponent(typeof(Animator))]
public class AnimationPlayer : MonoBehaviour
{
public enum AnimationState { Idle, Run, Jump, Walk };
public AnimationState state;
public bool backwards;
public bool block = false;
public AnimationClip clip;
private Animator animator;
private void Start() // Plays the specified animation clip
{
animator = GetComponent<Animator>();
animator.Play(clip.name);
}
private void LateUpdate() // Updates the animation state
{
animator.SetInteger("state", (int)state);
transform.localScale = new Vector3(Mathf.Sign(backwards ? -1 : 1) * Mathf.Abs(transform.localScale.x), transform.localScale.y, transform.localScale.z);
animator.SetBool("block", block);
}
public void SetState(AnimationState state) // Sets the animation state
{
this.state = state;
}
public void Punch() // Triggers punch animation
{
animator.SetTrigger("punch");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6e9d01bde449c4d0fa146e06804d6a03

View File

@@ -0,0 +1,57 @@
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(PlayerInput))]
public class Block : MonoBehaviour
{
public bool blocking = false;
private InputActionAsset actions;
private float blockPressTime = 0f;
[SerializeField] private float parryThreshold = 0.2f; // Time for successful parry
private bool isParrying = false;
private void Start()
{
actions = GetComponent<PlayerInput>().actions;
}
private void Update() // Player blocks when "block" is pressed
{
InputAction blockAction = actions.FindAction("Block");
if (blockAction.ReadValue<float>() == 1f)
{
if (!blocking)
{
blockPressTime = Time.time; // Start parry timer
}
blocking = true;
}
else
{
if (blocking) // Successful parry if blocked in time
{
float pressDuration = Time.time - blockPressTime;
if (pressDuration <= parryThreshold)
{
Parry();
}
else
{
isParrying = false;
}
}
blocking = false;
}
GetComponent<AnimationPlayer>().block = blocking;
}
private void Parry()
{
isParrying = true;
}
public bool IsParrying()
{
return isParrying;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c8c9288561905664eade3f6b2634fc6d

View File

@@ -0,0 +1,130 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[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()
{
animator = GetComponent<Animator>();
}
private void Update()
{
if (damageSelfDebug)
{
damageSelfDebug = false;
Damage(gameObject);
}
}
private void OnTriggerEnter2D(Collider2D collision) // Calls Damage method when player is hit
{
if (collision.gameObject.CompareTag("Punch Hurtbox"))
{
Damage(collision.transform.parent.gameObject);
}
}
private void Damage(GameObject damageSource) // Damages player
{
if (dying) return;
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);
return;
}
else // Player does less damage if punching a blocking player
{
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
{
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
{
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)
{
GetComponent<UseItem>().DropItem();
animator.SetBool("die", true);
dying = true;
}
}
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))
{
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
}
if (TryGetComponent<Damageable>(out var damageable))
{
damageable.ResetDamage();
}
}
public void ResetDamage()
{
damage = 0f;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6bac5d744efb2eb40be2220da2d52b35

View File

@@ -0,0 +1,83 @@
using System.Collections.Generic;
using UnityEngine;
public class PlayerCameraMovement : MonoBehaviour
{
private Vector3 start;
private Vector3 target;
public float weight;
public float speed;
private GameObject playerThatWon;
public float lowerBound;
public bool winScene = false;
private void Start()
{
start = transform.position;
}
private void Update()
{
if (winScene) // If the game is over, the camera will follow the player that won
{
if (playerThatWon == null || !playerThatWon.activeInHierarchy)
{
playerThatWon = FindWinner();
}
if (playerThatWon != null)
{
target = playerThatWon.transform.position;
transform.position = Vector3.Lerp(transform.position, new Vector3(target.x, target.y, target.z - 10), speed * 12 * Time.deltaTime);
if (transform.position.y < lowerBound)
{
transform.position = new Vector3(transform.position.x, lowerBound, transform.position.z);
}
}
return;
}
// Moves the camera to follow the players
List<GameObject> players = GameManager.players;
if (players.Count == 0) return;
Vector3 playerAverage = Vector3.zero;
int activePlayers = 0;
foreach (GameObject player in players)
{
if (player == null || !player.activeInHierarchy) continue;
Damageable damageable = player.GetComponent<Damageable>();
if (damageable != null && damageable.dying) continue;
playerAverage += player.transform.position;
activePlayers++;
}
if (activePlayers == 0) return;
playerAverage /= activePlayers;
target = start * weight + playerAverage * (1 - weight);
transform.position = Vector3.Lerp(transform.position, new Vector3(target.x, target.y, transform.position.z), speed * Time.deltaTime);
if (transform.position.y < lowerBound)
{
transform.position = new Vector3(transform.position.x, lowerBound, transform.position.z);
}
}
public void WinScene(GameObject player)
{
winScene = true;
playerThatWon = player;
}
private GameObject FindWinner() // Finds the player that won
{
foreach (GameObject player in GameManager.players)
{
if (player != null && player.activeInHierarchy)
{
return player;
}
}
return null;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b9e12ca6f2eb8fa42bba84ae691faeb1

View File

@@ -0,0 +1,97 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerManager : MonoBehaviour
{
public static PlayerManager Instance;
public List<PlayerJoinCard> cards;
[SerializeField] private InputActionAsset playerActions;
public List<Color> playerColors;
public GameObject playerSelect;
private bool gameStarted = false;
private void Awake()
{
Init();
}
private void Start()
{
GetComponent<PlayerInputManager>().onPlayerJoined += OnPlayerJoined;
GetComponent<PlayerInputManager>().onPlayerLeft += OnPlayerLeft;
}
private void OnPlayerJoined(PlayerInput playerInput) // Adds a player when they join
{
if (gameStarted)
{
Destroy(playerInput.gameObject);
return;
}
Debug.Log("Player joined");
DontDestroyOnLoad(playerInput.gameObject);
PlayerJoinCard card = PlayerCardCreator.Instance.CreateCard();
card.playerNumber = GameManager.players.Count + 1;
cards.Add(card);
GameManager.players.Add(playerInput.gameObject);
Colorize(GameManager.players.Count - 1);
}
private void OnPlayerLeft(PlayerInput playerInput) // Removes the player if they leave
{
Destroy(playerInput.gameObject);
GameManager.players.Remove(playerInput.gameObject);
Debug.Log("Player left");
}
private void Init()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(this.gameObject);
}
}
public void StartGame() // Allows game to start after a player has joined
{
if (GameManager.players.Count == 0)
{
return;
}
gameStarted = true;
HubManager.Instance.LoadScene(GameManager.map);
}
private void Colorize(int index) // Pairs each player with a unique color
{
GameObject player = GameManager.players[index];
Color color = playerColors[(GameManager.players.Count - 1) % playerColors.Count];
float tint = Mathf.Floor((GameManager.players.Count - 1) / playerColors.Count);
color = (color + color + Color.white * tint) / (tint + 2);
GameManager.playerColors.Add(color);
ApplyColor(player, color);
ApplyColor(cards[GameManager.players.IndexOf(player)].playerPreview, color);
}
private void ApplyColor(GameObject obj, Color color) // Applies a color to each player
{
if (obj.TryGetComponent<SpriteRenderer>(out _))
{
obj.GetComponent<SpriteRenderer>().color = color;
}
foreach (Transform child in obj.transform)
{
if (child.TryGetComponent<SpriteRenderer>(out _))
{
ApplyColor(child.gameObject, color);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 33e0fad1e452e0140bc99e780d4dda4f

View File

@@ -0,0 +1,248 @@
using System.Collections;
using TMPro;
using Unity.IO.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.InputSystem;
[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
{
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();
}
private void Update() // Updates player movement
{
if (GameManager.Instance != null && GameManager.Instance.gameOver) maxSpeed = 1f;
if (damageable.dying) return;
Jump();
UpdateVirtualAxis();
}
private void FixedUpdate()
{
JumpPhysics();
HorizontalMovement();
Land();
}
private void LateUpdate()
{
Animate();
}
private void Animate() // Sets player animation
{
if (!IsPhysicallyGrounded())
animationPlayer.SetState(AnimationPlayer.AnimationState.Jump);
else
{
if (Mathf.Abs(body.linearVelocityX) >= 0.05f)
animationPlayer.SetState(GameManager.Instance.gameOver ? AnimationPlayer.AnimationState.Walk : AnimationPlayer.AnimationState.Run);
else
animationPlayer.SetState(AnimationPlayer.AnimationState.Idle);
}
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;
}
}
}
private void Jump() // Player jumps when 'jump' is pressed
{
if (virtualButtonJumpLastFrame == 1f)
{
jumpInputStillValid = true;
lastTimeJumpPressed = Time.time;
}
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
{
jumpPhysics = true;
jumping = true;
jumpInputStillValid = false;
StartCoroutine(NotJumpingDelay());
}
}
private void JumpPhysics() // Applies jump physics
{
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);
}
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)))
{
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;
}
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())
{
lastTimeOnGround = Time.time;
}
if (Time.time - lastTimeOnGround < coyoteTime)
{
return true;
}
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)
{
return true;
}
return false;
}
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 void StopVelocity() // Stops inertia when landed
{
if (IsPhysicallyGrounded()) body.linearVelocity = Vector2.zero;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9b920f65db6650d4aa4858ad8a795d19

View File

@@ -0,0 +1,59 @@
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(PlayerInput))]
[RequireComponent(typeof(AnimationPlayer))]
public class Punch : MonoBehaviour
{
public bool cancelable = true;
[SerializeField] private BoxCollider2D hurtbox;
InputActionAsset actions;
private void Start()
{
actions = GetComponent<PlayerInput>().actions;
}
private void Update() // Executes punch when 'punch' is pressed
{
if (actions.FindAction("Punch").WasPressedThisFrame())
{
if (!cancelable) return;
ExecutePunch();
}
}
private void ExecutePunch() // Triggers punch animation
{
GetComponent<AnimationPlayer>().Punch();
DisableCancellation();
GetComponent<PlayerMovement>().maxSpeedOverride = 1f; // Slows player down when punching
}
public void EnableHurtbox()
{
if (hurtbox != null) hurtbox.enabled = true;
}
public void DisableHurtbox()
{
if (hurtbox != null) hurtbox.enabled = false;
}
public void DisableCancellation()
{
cancelable = false;
}
public void EnableCancellation()
{
cancelable = true;
}
public void ReturnToMaxSpeed() // Resets player speed after punch
{
GetComponent<PlayerMovement>().maxSpeedOverride = GetComponent<PlayerMovement>().maxSpeed;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9ae332d743f1a42ce8ee7a958853bcc1

View File

@@ -0,0 +1,37 @@
using UnityEngine;
public class TeleportPlatform : MonoBehaviour
{
public Vector2 teleportPoint;
public string teleportTag;
public string playerTag = "Player";
public bool isPlatform = true;
private void OnTriggerEnter2D(Collider2D collision)
{
if (!isPlatform)
{
// Teleports the platform
if (collision.CompareTag(teleportTag))
{
transform.position = teleportPoint;
if (TryGetComponent<Rigidbody2D>(out var rb))
{
rb.linearVelocity = Vector2.zero;
}
}
}
else
{
// Teleports the player
if (collision.CompareTag(playerTag))
{
collision.transform.position = teleportPoint;
if (collision.TryGetComponent<Rigidbody2D>(out var rb))
{
rb.linearVelocity = Vector2.zero;
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 182a9e619d36aeb4592fb532d3c9db55

View File

@@ -0,0 +1,63 @@
using UnityEngine;
public class UseItem : MonoBehaviour
{
[SerializeField] private string itemTag;
private GameObject heldItem;
private bool isHoldingItem = false;
private float holdStartTime;
public float holdTime;
void Update()
{
if (isHoldingItem)
{
// Keeps hat on the player's head
heldItem.transform.position = transform.position + Vector3.up;
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)
{
PickUpItem(collision.gameObject);
}
}
private void PickUpItem(GameObject item) // Player picks up hat and starts hold counter
{
heldItem = item;
isHoldingItem = true;
holdStartTime = Time.time;
item.GetComponent<Collider2D>().enabled = false;
item.GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Static;
item.transform.rotation = Quaternion.identity;
if (!GameManager.playerHoldTimes.ContainsKey(gameObject))
{
GameManager.playerHoldTimes[gameObject] = 0f;
}
}
public void DropItem() // Player drops hat when hit
{
if (isHoldingItem)
{
heldItem.GetComponent<Collider2D>().enabled = true;
heldItem.GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Dynamic;
heldItem.transform.position += Vector3.up * 3f;
heldItem = null;
isHoldingItem = false;
if (GameManager.playerHoldTimes.ContainsKey(gameObject))
{
GameManager.playerHoldTimes.Remove(gameObject);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 861b3bde023ca7f48a72c48ef9ee25d2