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