X-Git-Url: https://git.cameronkatri.com/gmtk-gamejam.git/blobdiff_plain/40471cf464366a7f49110a31d00fec160856b60f..e484685210b4db2ee2ab21e0a71d0fe9043ae81d:/Assets/Scripts/Controllers/GameController.cs
diff --git a/Assets/Scripts/Controllers/GameController.cs b/Assets/Scripts/Controllers/GameController.cs
index 54a0425..c9cb0ce 100644
--- a/Assets/Scripts/Controllers/GameController.cs
+++ b/Assets/Scripts/Controllers/GameController.cs
@@ -1,5 +1,9 @@
using System;
+using System.Collections;
+using JetBrains.Annotations;
using UnityEngine;
+using UnityEngine.SceneManagement;
+using UnityEngine.UI;
namespace Controllers
{
@@ -9,19 +13,47 @@ namespace Controllers
public class GameController : MonoBehaviour
{
private State state = State.JumpBall; // A basketball game always starts with a jump ball.
+ internal Animator BallAnimation;
public Player player;
public Player enemy;
+ public bool freezeMotion;
+
+ private float startTime;
+ public static float timeLimit;
+
///
/// The single ball for the game.
///
[SerializeField] public Ball ball;
+ [SerializeField] private float dribbleHeight;
+ [SerializeField] private float dribbleSpeed;
+ private Vector3 ballTarget;
+
+ [SerializeField] public SpriteRenderer[] trenchCoatSegments;
+
+ [Header("Spawn Points")]
+ [SerializeField] private SpawnPoints PlayerSpawnPoints;
+ [SerializeField] private SpawnPoints EnemySpawnPoints;
+
+ [Header("Hoops")]
[SerializeField] public Hoop PlayerHoop;
[SerializeField] public Hoop EnemyHoop;
+ [Header("SFX")]
[SerializeField] public AudioSource dribbleSound;
+ [SerializeField] public AudioSource airhornSound;
+
+ [Header("UI")]
+ [SerializeField] private Text playerScoreText;
+ [SerializeField] private Text enemyScoreText;
+ [SerializeField] private Text timerText;
+
+ [SerializeField] private GameObject resultOverlay;
+ [SerializeField] private Text resultText;
+ [SerializeField] private GameObject actionsUI;
private void Awake()
{
@@ -30,8 +62,72 @@ namespace Controllers
ball.controller = this;
PlayerHoop.controller = this;
EnemyHoop.controller = this;
+ BallAnimation = ball.GetComponentInChildren();
}
-
+
+ private void Start()
+ {
+ startTime = Time.time - 2f;
+ freezeMotion = true;
+ StartCoroutine(FadeInCoat());
+ }
+
+ private IEnumerator FadeInCoat()
+ {
+ foreach (var trenchCoat in trenchCoatSegments)
+ trenchCoat.material.color = new Color(1, 1, 1, 0);
+
+ yield return new WaitForSeconds(1f);
+
+ for (float t = 0; t < 1f; t += Time.deltaTime / 1)
+ {
+ foreach (var trenchCoat in trenchCoatSegments)
+ trenchCoat.material.color = new Color(1, 1, 1, Mathf.Lerp(0, 1, t));
+ yield return null;
+ }
+
+ yield return new WaitForSeconds(1f);
+
+ freezeMotion = false;
+ startTime = Time.time;
+ ball.Rigidbody.velocity = new Vector2(0, 10f);
+ }
+
+ private void Update()
+ {
+ UpdateUI();
+ }
+
+ private void FixedUpdate()
+ {
+ if (player.HasBall || enemy.HasBall)
+ {
+ ball.transform.position = ballTarget - new Vector3(0, (Mathf.Sin(Time.time * dribbleSpeed) + 1f) * (ballTarget.y - 0.75f) * dribbleHeight, 0);
+ }
+ }
+
+ private bool gameover;
+ private void UpdateUI()
+ {
+ playerScoreText.text = $"{player.score}";
+ enemyScoreText.text = $"{enemy.score}";
+
+ var remainingRaw = timeLimit - (Time.time - startTime);
+ var remaining = TimeSpan.FromSeconds(Mathf.Clamp(remainingRaw, 0, float.MaxValue));
+ timerText.text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
+
+ if (remainingRaw <= 0 && !gameover)
+ {
+ airhornSound.Play();
+ var outcome = player.score == enemy.score ? "TIE GAME" : player.score < enemy.score ? "AWAY TEAM WINS" : "HOME TEAM WINS";
+ actionsUI.SetActive(true);
+ ShowModal($"{outcome}\n{player.score}-{enemy.score}");
+
+ freezeMotion = true;
+ gameover = true;
+ }
+ }
+
public struct Player
{
///
@@ -49,16 +145,17 @@ namespace Controllers
private Vector2 lastShotPosition;
public void Score(Vector2 Rim)
{
- if (Vector2.Distance(lastShotPosition, Rim) >= 1)
+ if (Vector2.Distance(lastShotPosition, Rim) >= 10)
{
score += 3;
- Debug.Log("Three point");
}
else
{
score += 2;
- Debug.Log("Two point");
}
+
+ // They made a shot! Now respawn the players and give possession to the opposite player.
+ controller.Respawn(isEnemy ? Possession.Player : Possession.Enemy, $"{controller.player.score}-{controller.enemy.score}");
}
private State dribble => isEnemy ? State.EnemyDribble : State.PlayerDribble;
@@ -75,7 +172,7 @@ namespace Controllers
public void Move(Vector2 handPosition)
{
if (controller.state == (isEnemy ? State.EnemyDribble : State.PlayerDribble)) // Make sure they're dribbling.
- controller.ball.transform.position = handPosition; // TODO: Make this perform a dribbling motion, otherwise it looks like they're travelling.
+ controller.ballTarget = handPosition;
}
///
@@ -92,8 +189,12 @@ namespace Controllers
if (Vector2.Distance(controller.ball.transform.position, handPosition) >= 1f) return false;
controller.state = dribble;
+ controller.BallAnimation.enabled = false;
controller.dribbleSound.Play();
Move(handPosition);
+
+ controller.ball.Rigidbody.bodyType = RigidbodyType2D.Kinematic;
+
return true;
}
@@ -101,24 +202,94 @@ namespace Controllers
/// Shoot the ball if possible.
///
///
+ ///
/// Whether or not the ball was shot
- public bool Shoot(Transform playerTransform)
+ public bool Shoot(Transform playerTransform, float time)
{
if (controller.state != dribble) return false; // We must be dribbling the ball to shoot it.
+ controller.BallAnimation.enabled = true;
controller.dribbleSound.Stop();
controller.state = shoot;
- controller.ball.Shoot(hoop.transform.position);
+ controller.ball.Rigidbody.bodyType = RigidbodyType2D.Dynamic;
+ controller.ball.Shoot(hoop, time);
lastShotPosition = playerTransform.position;
return true;
}
+
+ public void Foul(string reason)
+ {
+ // Give the other player the ball on a foul.
+ controller.Respawn(isEnemy ? Possession.Player : Possession.Enemy, reason);
+ }
}
internal void BallDropped()
{
+ BallAnimation.enabled = true;
dribbleSound.Stop();
+ ball.Rigidbody.bodyType = RigidbodyType2D.Dynamic;
state = State.Idle;
}
+ private void Respawn(Possession possession, string message)
+ {
+ BallDropped();
+
+ PlayerSpawnPoints.body.transform.position = new Vector3(PlayerSpawnPoints.character.position.x, PlayerSpawnPoints.character.position.y, PlayerSpawnPoints.body.transform.position.y);
+ PlayerSpawnPoints.body.GetComponent().velocity = Vector2.zero;
+
+ if (PlayerSpawnPoints.secondBody is { })
+ {
+ if (PlayerSpawnPoints.secondCharacter is { })
+ PlayerSpawnPoints.secondBody.transform.position = new Vector3(PlayerSpawnPoints.secondCharacter.position.x, PlayerSpawnPoints.secondCharacter.position.y, PlayerSpawnPoints.body.transform.position.y);
+ PlayerSpawnPoints.secondBody.GetComponent().velocity = Vector2.zero;
+ }
+
+ PlayerSpawnPoints.body.transform.localRotation = Quaternion.identity;
+ EnemySpawnPoints.body.transform.position = new Vector3(EnemySpawnPoints.character.position.x, EnemySpawnPoints.character.position.y, EnemySpawnPoints.body.transform.position.y);
+ ball.transform.position = possession switch
+ {
+ Possession.Player => new Vector3(PlayerSpawnPoints.ball.position.x, PlayerSpawnPoints.ball.position.y, ball.transform.position.y),
+ Possession.Enemy => new Vector3(EnemySpawnPoints.ball.position.x, EnemySpawnPoints.ball.position.y, ball.transform.position.y),
+ _ => ball.transform.position
+ };
+
+ // Set a cooldown so they can stop trying to wrangle the player while it respawns.
+ StartCoroutine(RespawnCooldown(possession, message));
+ }
+
+ private IEnumerator RespawnCooldown(Possession possession, string message)
+ {
+ // Show the new score.
+ var possessionText = possession == Possession.Player ? "HOME" : "AWAY";
+ ShowModal($"{message}\n{possessionText}'S POSSESSION");
+
+ freezeMotion = true;
+
+ yield return new WaitForSeconds(1f);
+
+ HideModal();
+
+ freezeMotion = false;
+ }
+
+ private void ShowModal(string text)
+ {
+ if (gameover) return;
+ resultOverlay.SetActive(true);
+ resultText.text = text;
+ }
+
+ private void HideModal()
+ {
+ if (gameover) return;
+ resultOverlay.SetActive(false);
+ }
+
+ public void Restart() => SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
+
+ public void MainMenu() => SceneManager.LoadScene("Menu");
+
internal enum State
{
Idle,
@@ -128,6 +299,22 @@ namespace Controllers
EnemyDribble,
EnemyShoot,
}
+
+ [Serializable]
+ private struct SpawnPoints
+ {
+ [SerializeField] internal Transform body;
+ [SerializeField] [CanBeNull] internal Transform secondBody;
+ [SerializeField] internal Transform ball;
+ [SerializeField] internal Transform character;
+ [SerializeField] [CanBeNull] internal Transform secondCharacter;
+ }
+
+ private enum Possession
+ {
+ Player,
+ Enemy
+ }
}
internal static class GameControllerStateExtensions