Screenshots
The following screenshots from the game might help you design your game. Use these screenshots as a guide. You don’t need to replicate the design. Feel free to make your game look the way you want.
Main Scene Design
This screenshot displays the main scene design of Racquet AlphaZone.
Project Structure
This screenshot displays the assets of Racquet AlphaZone.
Prefab Properties
This screenshot displays the properties of the prefabs for the power-ups in Racquet AlphaZone.
Main Scene Game Objects Properties
Camera
Orthographic camera with the default values.
Background
Sprite. Set the “Sprite” property in the SpriteRenderer to the background image.
TopBoundary & BottomBoundary
Sprites. Set the “Color” property in the SpriteRenderer to black.
BoxCollider2D has the default properties.
PlayerBarrier
Sprite. Set the “Material” property in the SpriteRenderer to the player barrier material.
BoxCollider2D has the default properties.
PlayerGoalpost & AIGoalpost
Sprites. SpriteRenderer has the default values.
Set the “Is Trigger” property in the BoxCollider2D to true.
Player
Set the “Color” property in the SpriteRenderer to #94C18A
In the RigidBody2D, set the “Body Type” to Dynamic, the “Collision Detection” to Dynamic, the “Freeze position X” to true, and the “Freeze rotation Z” to true.
Add the PlayerController script.
The BoxCollision2D has the default values.
Ball
Set the “Color” property in the SpriteRenderer to #DAA520
In the RigidBody2D, set the “Body Type” to Dynamic, the “Collision Detection” to Dynamic, and the “Freeze rotation Z” to true.
Add the BallController script.
The CircleCollision2D has the default values.
AI Opponent
Set the “Color” property in the SpriteRenderer to #6B3E75
In the RigidBody2D, set the “Body Type” to Dynamic, the “Collision Detection” to Dynamic, the “Freeze position X” to true, and the “Freeze rotation Z” to true.
Add the AIPredictiveTracking script.
The BoxCollision2D has the default values.
The Code
GameState
using UnityEngine;
namespace Assets.Code
{
public static class GameState
{
//Help functions
public static string GetDifficulty()
{
switch (Difficulty)
{
case 0:
return "Easy";
case 1:
return "Normal";
case 2:
return "Hard";
default:
return "Unknown";
}
}
public static float GetBonus()
{
switch (Difficulty)
{
case 0:
return EasyDifficultyPointBonus;
case 1:
return NormalDifficultyPointBonus;
case 2:
return HardDifficultyPointBonus;
default:
return 1f;
}
}
//Runtime calculated. Reset when new game starts.
public static int PlayerScore;
public static int AIScore;
//Configuration values set in code.
public static int WinScore = 1;
public static float LightningBoltDuration = 5.0f;
public static float BarrierDuration = 10.0f;
public static float SecondBallDuration = 10f;
public static float SpeedMultiplier = 1.25f;
public static int PointsPerGoal = 50;
public static float EasyDifficultyPointBonus = 1f;
public static float NormalDifficultyPointBonus = 1.5f;
public static float HardDifficultyPointBonus = 2.5f;
//Settings stored in PlayerPrefs.
//Score
public static int PlayerTotalScore;
public static int AITotalScore;
//Difficulty
public static int Difficulty; //Easy=0, Normal=1, Hard=2
public static float PlayerSpeed; //The the player's racquet speed.
public static float BallSpeed; //The the ball's speed.
public static float AISpeed; //The the AI's racquet speed.
public static float AIError; //Margin of error for the AI's prediction.
public static float AIPerceptionInteval; //How often AI "reads" the environment.
//Sound and Music
public static bool SoundFxOn;
public static float SounFxVolume;
public static bool MusicOn;
public static float MusicVolume;
public static void Save()
{
PlayerPrefs.SetInt("PlayerTotalScore", PlayerTotalScore);
PlayerPrefs.SetInt("AITotalScore", AITotalScore);
PlayerPrefs.SetInt("Difficulty", Difficulty);
PlayerPrefs.SetFloat("PlayerSpeed", PlayerSpeed);
PlayerPrefs.SetFloat("BallSpeed", BallSpeed);
PlayerPrefs.SetFloat("AISpeed", AISpeed);
PlayerPrefs.SetFloat("AIError", AIError);
PlayerPrefs.SetFloat("AIPerceptionInteval", AIPerceptionInteval);
PlayerPrefs.SetString("SoundFxOn", (SoundFxOn == true) ? "1" : "0");
PlayerPrefs.SetFloat("SoundFXVolume", SounFxVolume);
PlayerPrefs.SetString("MusicOn", (MusicOn == true) ? "1" : "0");
PlayerPrefs.SetFloat("MusicVolume", MusicVolume);
}
public static void Load()
{
PlayerTotalScore = PlayerPrefs.GetInt("PlayerTotalScore", 0);
AITotalScore = PlayerPrefs.GetInt("AITotalScore", 0);
Difficulty = PlayerPrefs.GetInt("Difficulty", 1);
PlayerSpeed = PlayerPrefs.GetFloat("PlayerSpeed", 10.0f);
BallSpeed = PlayerPrefs.GetFloat("BallSpeed", 7.0f);
AISpeed = PlayerPrefs.GetFloat("AISpeed", 4.0f);
AIError = PlayerPrefs.GetFloat("AIError", 0.15f);
AIPerceptionInteval = PlayerPrefs.GetFloat("AIPerceptionInteval", 0.075f);
SoundFxOn = (PlayerPrefs.GetString("SoundFxOn", "1") == "1");
SounFxVolume = PlayerPrefs.GetFloat("SoundFXVolume", 0.5f);
MusicOn = (PlayerPrefs.GetString("MusicOn", "1") == "1");
MusicVolume = PlayerPrefs.GetFloat("MusicVolume", 0.5f);
}
}
}
MainMenuFunctions
using Assets.Code;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class MainMenuFunctions : MonoBehaviour
{
//UI Elements
public CanvasGroup MainMenu;
public CanvasGroup SettingsMenu;
public Toggle SoundFxToggle;
public Slider SoundFXSlider;
public Toggle MusicToggle;
public Slider MusicSlider;
//Main Menu Actions
public void Play()
{
SceneManager.LoadScene(1);
}
public void OpenSettings()
{
//Hide main menu
MainMenu.alpha = 0;
MainMenu.blocksRaycasts = false;
MainMenu.interactable = false;
//Show settings menu
SettingsMenu.alpha = 1;
SettingsMenu.blocksRaycasts = true;
SettingsMenu.interactable = true;
}
public void Exit()
{
//Save game data to PlayerPrefs.
GameState.Save();
Debug.Log("Exit");
Application.Quit();
}
//Settings Menu Actions
public void SetEasyDifficulty()
{
GameState.Difficulty = 0;
GameState.PlayerSpeed = 15.0f;
GameState.BallSpeed = 5.0f;
GameState.AISpeed = 3.0f;
GameState.AIError = 0.2f;
GameState.AIPerceptionInteval = 0.075f;
}
public void SetNormalDifficulty()
{
GameState.Difficulty = 1;
GameState.PlayerSpeed = 10.0f;
GameState.BallSpeed = 7.0f;
GameState.AISpeed = 4.0f;
GameState.AIError = 0.15f;
GameState.AIPerceptionInteval = 0.075f;
}
public void SetHardDifficulty()
{
GameState.Difficulty = 2;
GameState.PlayerSpeed = 7.5f;
GameState.BallSpeed = 10.0f;
GameState.AISpeed = 10.0f;
GameState.AIError = 0.005f;
GameState.AIPerceptionInteval = 0.025f;
}
public void SetSoundFx(Toggle toggle)
{
GameState.SoundFxOn = toggle.isOn;
}
public void SetSoundFxVolume(Slider slider)
{
GameState.SounFxVolume = slider.value;
}
public void SetMusic(Toggle toggle)
{
GameState.MusicOn = toggle.isOn;
}
public void SetMusicVolume(Slider slider)
{
GameState.MusicVolume = slider.value;
}
public void ExitSettings()
{
//Save game data to PlayerPrefs.
GameState.Save();
//Hide settings menu
SettingsMenu.alpha = 0;
SettingsMenu.blocksRaycasts = false;
SettingsMenu.interactable = false;
//Show main menu
MainMenu.alpha = 1;
MainMenu.blocksRaycasts = true;
MainMenu.interactable = true;
}
//Other actions
public void OpenProgressSavedLink()
{
Application.OpenURL("https://ProgressSaved.com");
}
//Initialization
void Awake()
{
//Load game data from PlayerPrefs.
GameState.Load();
//Update controls based on data loaded.
SoundFxToggle.isOn = GameState.SoundFxOn;
SoundFXSlider.value = GameState.SounFxVolume;
MusicToggle.isOn = GameState.MusicOn;
MusicSlider.value = GameState.MusicVolume;
}
}
UpdateTotalScore
using Assets.Code;
using UnityEngine;
using UnityEngine.UI;
public class UpdateTotalScore : MonoBehaviour
{
public Text PlayerTotalText;
public Text AITotalText;
void Start()
{
PlayerTotalText.text = GameState.PlayerTotalScore.ToString();
AITotalText.text = GameState.AITotalScore.ToString();
}
}
EndGameMenuFunctions
using Assets.Code;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class EndGameMenuFunctions : MonoBehaviour
{
public CanvasGroup EndGamePanel;
public Text EndGameTitle;
public Text GameScore;
public void Play()
{
GameState.PlayerScore = 0;
GameState.AIScore = 0;
SceneManager.LoadScene(1);
}
public void ExitToMainMenu()
{
GameState.PlayerScore = 0;
GameState.AIScore = 0;
SceneManager.LoadScene(0);
}
void Start()
{
Time.timeScale = 1f;
if (GameState.PlayerScore >= GameState.WinScore)
PlayerWins();
else if (GameState.AIScore >= GameState.WinScore)
AIWins();
else
NobodyWins();
}
private void PlayerWins()
{
EndGameTitle.text = "Player Wins!";
GameScore.text = "Score: " + CalculateScore();
}
private void AIWins()
{
EndGameTitle.text = "AI Wins!";
GameScore.text = "Score: " + CalculateScore();
}
private void NobodyWins()
{
EndGameTitle.text = "Nobody Wins!";
GameScore.text = "Score: 0";
}
private string CalculateScore()
{
//Get score
int score = (int)(Mathf.Abs(GameState.PlayerScore - GameState.AIScore) * GameState.PointsPerGoal * GameState.GetBonus());
//Update total score
if (GameState.PlayerScore >= GameState.WinScore)
GameState.PlayerTotalScore += score;
else
GameState.AITotalScore += score;
//Save game data.
GameState.Save();
return score.ToString();
}
}
UpdateUI
using Assets.Code;
using UnityEngine;
using UnityEngine.UI;
public class UpdateUI : MonoBehaviour
{
public Text PlayerScoreText;
public Text AIScoreText;
public Text DifficultyText;
private void Start()
{
DifficultyText.text = GameState.GetDifficulty();
}
void FixedUpdate()
{
PlayerScoreText.text = GameState.PlayerScore.ToString();
AIScoreText.text = GameState.AIScore.ToString();
}
}
MusicManager
using Assets.Code;
using UnityEngine;
[RequireComponent(typeof(AudioSource))]
public class MusicManager : MonoBehaviour
{
public AudioClip backgroundMusic;
private AudioSource audioSource;
public void StopSound()
{
if (audioSource.isPlaying)
audioSource.Stop();
}
// Start is called before the first frame update
void Start()
{
audioSource = GetComponent<AudioSource>();
audioSource.clip = backgroundMusic;
audioSource.enabled = GameState.MusicOn;
audioSource.volume = GameState.MusicVolume;
audioSource.Play();
}
}
SoundManager
using Assets.Code;
using UnityEngine;
[RequireComponent(typeof(AudioSource))]
public class SoundManager : MonoBehaviour
{
public AudioClip ballBounceClip;
public AudioClip powerUpClip;
public AudioClip playerScoreClip;
public AudioClip aiScoreClip;
public AudioClip countDownClip;
private AudioSource audioSource;
public void StopSound()
{
if (audioSource.isPlaying)
audioSource.Stop();
}
public void PlayBallBounce()
{
PlaySound(0);
}
public void PlayPowerUp()
{
PlaySound(1);
}
public void PlayPlayerScore()
{
PlaySound(2);
}
public void PlayAIScore()
{
PlaySound(3);
}
public void PlayCountDown()
{
PlaySound(4);
}
void Start()
{
audioSource = GetComponent<AudioSource>();
audioSource.enabled = GameState.SoundFxOn;
audioSource.volume = GameState.SounFxVolume;
}
private void PlaySound(int soundIdx)
{
//If other sound playing, stop it.
if (audioSource.isPlaying)
audioSource.Stop();
//Select the sound to play.
switch (soundIdx)
{
case 0:
audioSource.clip = ballBounceClip;
break;
case 1:
audioSource.clip = powerUpClip;
break;
case 2:
audioSource.clip = playerScoreClip;
break;
case 3:
audioSource.clip = aiScoreClip;
break;
case 4:
audioSource.clip = countDownClip;
break;
default:
break;
}
//Play the sound.
audioSource.Play();
}
}
PauseManager
using System.Collections;
using UnityEngine;
public class PauseManager : MonoBehaviour
{
public CanvasGroup PausedCanvas;
private SoundManager soundManager;
private bool isPaused = false;
void Awake()
{
PauseGame();
}
void Start()
{
//Get SoundManager
soundManager = FindAnyObjectByType<SoundManager>();
if (soundManager == null)
Debug.LogError("SoundManager not found!");
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
TogglePause();
}
public void HidePauseMessage()
{
PausedCanvas.alpha = 0f;
}
public void TogglePause()
{
if (isPaused)
ResumeGame();
else
PauseGame();
}
public void PauseGame()
{
Time.timeScale = 0;
isPaused = true;
PausedCanvas.alpha = 1f;
}
public void ResumeGame()
{
//Time.timeScale = 1;
isPaused = false;
PausedCanvas.alpha = 0f;
soundManager.PlayCountDown();
StartCoroutine(ResumeGameCoroutine(3));
}
private IEnumerator ResumeGameCoroutine(float duration)
{
yield return new WaitForSecondsRealtime(duration);
Time.timeScale = 1;
}
}
InGameMenuFunctions
using Assets.Code;
using UnityEngine;
using UnityEngine.SceneManagement;
public class InGameMenuFunctions : MonoBehaviour
{
public CanvasGroup InGameMenu;
private PauseManager pauseManager;
private bool isOpen = false;
public void ResumeGame()
{
HideInGameMenu();
}
public void ExitToMainMenu()
{
pauseManager.PauseGame();
GameState.Save();
ResetGame();
SceneManager.LoadScene(0);
}
void Start()
{
pauseManager = FindAnyObjectByType<PauseManager>();
if (pauseManager == null)
Debug.LogError("PauseManager is not found.");
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
ToggleMenu();
}
private void ToggleMenu()
{
if (isOpen)
HideInGameMenu();
else
ShowInGameMenu();
}
private void ShowInGameMenu()
{
InGameMenu.alpha = 1;
InGameMenu.interactable = true;
InGameMenu.blocksRaycasts = true;
pauseManager.PauseGame();
isOpen = true;
}
private void HideInGameMenu()
{
InGameMenu.alpha = 0;
InGameMenu.interactable = false;
InGameMenu.blocksRaycasts = false;
pauseManager.ResumeGame();
isOpen = false;
}
private void ResetGame()
{
//Reset scores
GameState.PlayerScore = 0;
GameState.AIScore = 0;
}
}
SecondBallController
using Assets.Code;
using UnityEngine;
public class SecondBallController : MonoBehaviour
{
private Rigidbody2D rb;
private SoundManager soundManager;
void Start()
{
rb = GetComponent<Rigidbody2D>();
if (rb == null)
Debug.LogError("Rigidbody2D not found!");
soundManager = FindAnyObjectByType<SoundManager>();
if (soundManager == null)
Debug.LogError("SoundManager not found!");
}
void Update()
{
if (Mathf.Abs(rb.velocity.magnitude) < 2f)
Destroy(gameObject);
}
private void OnTriggerEnter2D(Collider2D other)
{
switch (other.gameObject.tag)
{
case "PlayerGoalpost":
AddToAIScore();
break;
case "AIGoalpost":
AddToPlayerScore();
break;
default:
break;
}
}
private void AddToAIScore()
{
soundManager.PlayAIScore();
GameState.AIScore++;
Destroy(gameObject);
}
private void AddToPlayerScore()
{
soundManager.PlayPlayerScore();
GameState.PlayerScore++;
Destroy(gameObject);
}
}
PowerUpSpawner
using UnityEngine;
public class PowerUpSpawner : MonoBehaviour
{
public GameObject[] powerUpPrefabs; // Array to hold the references to your PowerUp prefabs
public float spawnInterval = 5.0f; // Time interval between spawns
public Vector2 spawnAreaMin; // Minimum (bottom-left) corner of the spawn area
public Vector2 spawnAreaMax; // Maximum (top-right) corner of the spawn area
public float powerUpLifeDuration = 10.0f; // Time for which a power-up stays in the scene
private float nextSpawnTime;
void Start()
{
nextSpawnTime = Time.time + spawnInterval;
}
void Update()
{
if (Time.time >= nextSpawnTime)
{
SpawnPowerUp();
nextSpawnTime = Time.time + spawnInterval;
}
}
void SpawnPowerUp()
{
Vector2 spawnPosition = new Vector2(
Random.Range(spawnAreaMin.x, spawnAreaMax.x),
Random.Range(spawnAreaMin.y, spawnAreaMax.y)
);
// Randomly select a power-up from the array
GameObject selectedPowerUp = powerUpPrefabs[Random.Range(0, powerUpPrefabs.Length)];
// Instantiate and destroy after its life duration
GameObject spawnedPowerUp = Instantiate(selectedPowerUp, spawnPosition, Quaternion.identity);
Destroy(spawnedPowerUp, powerUpLifeDuration);
}
}
PowerUpDisplayManager
using UnityEngine;
using UnityEngine.UI;
public class PowerUpDisplayManager : MonoBehaviour
{
public Image powerUpIcon;
public Sprite defaultSprite;
public Sprite boltSprite;
public Sprite barrierSprite;
public Sprite splitSprite;
public void SetDefaultSpite()
{
SetSprite(0);
}
public void SetBoltSpite()
{
SetSprite(1);
}
public void SetBarrierSpite()
{
SetSprite(2);
}
public void SetSplitSpite()
{
SetSprite(3);
}
void Start()
{
SetSprite(0);
}
public void SetSprite(int spriteIdx)
{
switch(spriteIdx)
{
case 0:
powerUpIcon.sprite = defaultSprite;
break;
case 1:
powerUpIcon.sprite = boltSprite;
break;
case 2:
powerUpIcon.sprite = barrierSprite;
break;
case 3:
powerUpIcon.sprite = splitSprite;
break;
default:
break;
}
}
}
PlayerController
using Assets.Code;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public Camera mainCamera;
private float speed;
private float topBoundary;
private float bottomBoundary;
private float halfRacquetHeight;
private Rigidbody2D rb;
public void Initialize()
{
speed = GameState.PlayerSpeed;
}
public void ResetPlayerPosition()
{
rb.position = new Vector2(rb.position.x, 0);
}
void Start()
{
Initialize();
rb = GetComponent<Rigidbody2D>();
// Calculate the racquet's half height
halfRacquetHeight = transform.localScale.y / 2.0f;
// Calculate the top and bottom boundaries based on camera's orthographic size
topBoundary = mainCamera.orthographicSize - halfRacquetHeight;
bottomBoundary = -topBoundary;
}
void FixedUpdate()
{
// Gets vertical input (W and S or Arrow keys)
float verticalInput = Input.GetAxis("Vertical");
// Calculate new position
float newY = Mathf.Clamp(rb.position.y + verticalInput * speed * Time.deltaTime, bottomBoundary, topBoundary);
// Set the new position
rb.MovePosition(new Vector2(rb.position.x, newY));
}
}
AIPredictiveTracking
using Assets.Code;
using UnityEngine;
public class AIPredictiveTracking : MonoBehaviour
{
public Transform ball;
private Rigidbody2D rb;
private Rigidbody2D ballRb;
private float speed;
private float error;
private float selectedBallDirection;
private float perceptionElapsedTime;
private float perceptionInteval;
public void Initialize()
{
speed = GameState.AISpeed;
error = GameState.AIError;
perceptionInteval = GameState.AIPerceptionInteval;
}
public void ResetPlayerPosition()
{
rb.position = new Vector2(rb.position.x, 0);
}
void Start()
{
Initialize();
if (ball == null)
Debug.LogError("Ball not found.");
rb = GetComponent<Rigidbody2D>();
if (rb == null)
Debug.LogError("AI player Rigidbody2D not found.");
ballRb = ball.GetComponent<Rigidbody2D>();
if (ball == null)
Debug.LogError("Ball Rigidbody2D not found.");
}
void FixedUpdate()
{
perceptionElapsedTime += Time.fixedDeltaTime;
if(perceptionElapsedTime > perceptionInteval)
{
//Select the ball to chase.
Transform ballToChase = SelectBall();
//Predict selected ball's Y position.
float predictedY = PredictBallYPosition(ballToChase);
//Determine selected ball's direction.
selectedBallDirection = (predictedY > transform.position.y) ? 1 : -1;
//rb.velocity = new Vector2(0, selectedBallDirection * speed);
float smoothFactor = 0.1f;
Vector2 desiredVelocity = new Vector2(0, selectedBallDirection * speed);
rb.velocity = Vector2.Lerp(rb.velocity, desiredVelocity, smoothFactor);
perceptionElapsedTime = 0;
}
}
private Transform SelectBall()
{
SecondBallController[] otherBalls = FindObjectsByType<SecondBallController>(FindObjectsSortMode.None);
if (otherBalls == null) return ball;
if (otherBalls.Length <=0) return ball;
float minDistance = float.MaxValue;
SecondBallController closestBall = null;
foreach (SecondBallController otherBall in otherBalls)
{
if (otherBall == null) continue;
float distance = Vector2.Distance(transform.position, otherBall.gameObject.transform.position);
if (distance < minDistance)
{
minDistance = distance;
closestBall = otherBall;
}
}
if (closestBall != null)
{
float distanceFromBall = Vector2.Distance(transform.position, ball.gameObject.transform.position);
float distanceFromSecondBall = Vector2.Distance(transform.position, closestBall.gameObject.transform.position);
if (distanceFromSecondBall < distanceFromBall)
return closestBall.gameObject.transform;
}
return ball;
}
float PredictBallYPosition(Transform ball)
{
// Calculate time for the ball to reach the AI
//The time it would take for the ball to reach the AI is found by dividing the horizontal
//distance the ball has to cover by the ball's horizontal velocity.
//This is based on the formula: time = distance / velocity
float timeToReachAI = (transform.position.x - ball.position.x) / ballRb.velocity.x;
// Predict Y position based on ball's current velocity
//Using the time found in the previous step, we calculate how much the ball will move vertically in that time.
//This is another basic kinematic equation: final position = initial position + velocity * time
float predictedY = ball.position.y + ballRb.velocity.y * timeToReachAI;
// Add some randomness to the prediction for challenge and variability
predictedY += Random.Range(-error, error);
return predictedY;
}
}
BallController
using Assets.Code;
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class BallController : MonoBehaviour
{
public GameObject PlayerBarrier;
public GameObject ballPrefab;
private Rigidbody2D rb;
private SoundManager soundManager;
private MusicManager musicManager;
private PauseManager pauseManager;
private PowerUpDisplayManager powerUpDisplayManager;
private PlayerController player;
private AIPredictiveTracking aiPlayer;
private float ballSpeed;
private float lightningBoltDuration;
private float barrierDuration;
private float secondBallDuration;
private float speedMultiplier;
private Vector2 initialDirection;
private float currentSpeed;
private bool HasGameSEnded = false;
public void Initialize()
{
ballSpeed = GameState.BallSpeed;
lightningBoltDuration = GameState.LightningBoltDuration;
barrierDuration = GameState.BarrierDuration;
secondBallDuration = GameState.SecondBallDuration;
speedMultiplier = GameState.SpeedMultiplier;
}
void Start()
{
Initialize();
currentSpeed = ballSpeed;
RandomizeDirection();
musicManager = FindAnyObjectByType<MusicManager>();
if (musicManager == null)
Debug.LogError("MusicManager not found!");
soundManager = FindAnyObjectByType<SoundManager>();
if (soundManager == null)
Debug.LogError("SoundManager not found!");
pauseManager = FindAnyObjectByType<PauseManager>();
if (pauseManager == null)
Debug.LogError("PauseManager not found!");
powerUpDisplayManager = FindAnyObjectByType<PowerUpDisplayManager>();
if (powerUpDisplayManager == null)
Debug.LogError("PowerUpDisplayManager not found!");
player = FindAnyObjectByType<PlayerController>();
if (player == null)
Debug.LogError("PlayerController not found!");
aiPlayer = FindAnyObjectByType<AIPredictiveTracking>();
if (aiPlayer == null)
Debug.LogError("AIPredictiveTracking not found!");
rb = GetComponent<Rigidbody2D>();
LaunchBall();
}
private void RandomizeDirection()
{
if (Random.Range(0, 2) < 0.5f)
initialDirection = new Vector2(1, 0);
else
initialDirection = new Vector2(-1, 0);
}
private void LaunchBall()
{
RandomizeDirection();
rb.velocity = initialDirection * currentSpeed;
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Racquet"))
BallBounceOnRacquet(collision);
}
private void OnTriggerEnter2D(Collider2D other)
{
switch (other.gameObject.tag)
{
case "LightningBolt":
ApplyLightningBolt(other);
break;
case "Barrier":
EnableBarrier(other);
break;
case "BallSplit":
ApplyBallSplit(other);
break;
case "PlayerGoalpost":
AddToAIScore();
break;
case "AIGoalpost":
AddToPlayerScore();
break;
default:
break;
}
}
void Update()
{
if (HasGameSEnded) return;
// Reset ball if it goes off screen.
if (Mathf.Abs(transform.position.x) > 10)
ResetBall();
// Reset ball if it looses its speed.
if (rb.velocity.magnitude < 2f)
ResetBall();
if (GameState.PlayerScore >= GameState.WinScore || GameState.AIScore >= GameState.WinScore)
{
HasGameSEnded = true;
pauseManager.HidePauseMessage();
soundManager.StopSound();
musicManager.StopSound();
StartCoroutine(EndGame(1));
}
}
private IEnumerator EndGame(float duration)
{
yield return new WaitForSecondsRealtime(duration);
SceneManager.LoadScene(2);
}
private void ResetBall()
{
transform.position = Vector2.zero;
rb.velocity = Vector2.zero;
LaunchBall();
}
private void BallBounceOnRacquet(Collision2D collision)
{
soundManager.PlayBallBounce();
// Reflect the ball's velocity on the y-axis if it hits a racquet.
float y = HitFactor(transform.position, collision.transform.position, collision.collider.bounds.size.y);
Vector2 direction = new Vector2(-1 * initialDirection.x, y).normalized;
rb.velocity = direction * currentSpeed;
}
float HitFactor(Vector2 ballPosition, Vector2 racquetPosition, float racquetHeight)
{
return (ballPosition.y - racquetPosition.y) / racquetHeight;
}
private void ApplyLightningBolt(Collider2D other)
{
//Play sound and update UI
soundManager.PlayPowerUp();
powerUpDisplayManager.SetBoltSpite();
//Apply power-up effect (increase ball speed)
currentSpeed = ballSpeed * speedMultiplier;
rb.velocity *= currentSpeed;
//Schedule reseting power-up's effect.
StartCoroutine(ResetLightningBolt(lightningBoltDuration));
// Destroy the power-up after it's been used
Destroy(other.gameObject);
}
private IEnumerator ResetLightningBolt(float duration)
{
//wait
yield return new WaitForSeconds(duration);
//Update UI
powerUpDisplayManager.SetDefaultSpite();
// reset ball speed
currentSpeed = ballSpeed;
rb.velocity = rb.velocity.normalized * currentSpeed;
}
private void EnableBarrier(Collider2D other)
{
//Play sound and update UI
soundManager.PlayPowerUp();
powerUpDisplayManager.SetBarrierSpite();
//Apply power-up effect (activate barrier)
PlayerBarrier.SetActive(true);
//Schedule reseting power-up's effect.
StartCoroutine(ResetBarrier(barrierDuration));
// Destroy the power-up after it's been used
Destroy(other.gameObject);
}
private IEnumerator ResetBarrier(float duration)
{
//wait
yield return new WaitForSeconds(duration);
//Update UI
powerUpDisplayManager.SetDefaultSpite();
//deactivate barrier
PlayerBarrier.SetActive(false);
}
private void ApplyBallSplit(Collider2D other)
{
//Play sound and update UI
soundManager.PlayPowerUp();
powerUpDisplayManager.SetSplitSpite();
//Apply power-up effect (nstantiate another ball)
GameObject newBall = Instantiate(ballPrefab, transform.position, Quaternion.identity);
Rigidbody2D newBallRb = newBall.GetComponent<Rigidbody2D>();
// Calculate new directions by rotating the original direction by 45 degrees
// in both clockwise and counterclockwise directions
Vector2 currentDirection = rb.velocity.normalized;
Vector2 newDirection1 = RotateVectorByDegrees(currentDirection, 45f);
Vector2 newDirection2 = RotateVectorByDegrees(currentDirection, -45f);
rb.velocity = newDirection1 * ballSpeed;
newBallRb.velocity = newDirection2 * ballSpeed;
//Schedule reseting power-up's effect.
StartCoroutine(ResetSplitBall(secondBallDuration, newBall));
//Destroy the power-up after it's been used
Destroy(other.gameObject);
}
private IEnumerator ResetSplitBall(float duration, GameObject secondBall)
{
//wait
yield return new WaitForSeconds(duration);
//Update UI
powerUpDisplayManager.SetDefaultSpite();
//Destroy senond ball after powerUpDuration seconds
Destroy(secondBall.gameObject, secondBallDuration);
Destroy(secondBall, secondBallDuration);
}
private Vector2 RotateVectorByDegrees(Vector2 v, float degrees)
{
float radians = degrees * Mathf.Deg2Rad;
float sin = Mathf.Sin(radians);
float cos = Mathf.Cos(radians);
float tx = v.x;
float ty = v.y;
return new Vector2(cos * tx - sin * ty, sin * tx + cos * ty);
}
private void AddToAIScore()
{
soundManager.PlayAIScore();
GameState.AIScore++;
player.ResetPlayerPosition();
aiPlayer.ResetPlayerPosition();
pauseManager.PauseGame();
}
private void AddToPlayerScore()
{
soundManager.PlayPlayerScore();
GameState.PlayerScore++;
player.ResetPlayerPosition();
aiPlayer.ResetPlayerPosition();
pauseManager.PauseGame();
}
}