Add initial game systems and input handling for player interactions
This commit is contained in:
8
Assets/Scripts/Abstractions.meta
Normal file
8
Assets/Scripts/Abstractions.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72f24c63b26da6dde87590f51ac98e57
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
10
Assets/Scripts/Abstractions/IInputService.cs
Normal file
10
Assets/Scripts/Abstractions/IInputService.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Abstractions
|
||||
{
|
||||
public interface IInputService
|
||||
{
|
||||
float GetHorizontalMovement();
|
||||
bool IsActionPressed();
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Abstractions/IInputService.cs.meta
Normal file
3
Assets/Scripts/Abstractions/IInputService.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e152fbd10fb4ed5842228e7dca1f25c
|
||||
timeCreated: 1765307263
|
||||
8
Assets/Scripts/Core.meta
Normal file
8
Assets/Scripts/Core.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6e572b7a1cc355cd84f19dfb8400cd8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
36
Assets/Scripts/Core/GameEvents.cs
Normal file
36
Assets/Scripts/Core/GameEvents.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
public static class GameEvents
|
||||
{
|
||||
// Gameplay Events (Inputs to the systems)
|
||||
public static event Action<int> PresentCaught;
|
||||
public static event Action PresentDropped;
|
||||
|
||||
// State Changes (Outputs from the systems)
|
||||
public static event Action<int> ScoreUpdated;
|
||||
public static event Action<int> LivesUpdated;
|
||||
public static event Action<float> TimeUpdated;
|
||||
public static event Action GameOver;
|
||||
|
||||
// Invokers
|
||||
public static void ReportPresentCaught(int value) => PresentCaught?.Invoke(value);
|
||||
public static void ReportPresentDropped() => PresentDropped?.Invoke();
|
||||
public static void ReportScoreUpdated(int score) => ScoreUpdated?.Invoke(score);
|
||||
public static void ReportLivesUpdated(int lives) => LivesUpdated?.Invoke(lives);
|
||||
public static void ReportTimeUpdated(float time) => TimeUpdated?.Invoke(time);
|
||||
public static void ReportGameOver() => GameOver?.Invoke();
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
// Reset events when scene reloads to prevent memory leaks
|
||||
PresentCaught = null;
|
||||
PresentDropped = null;
|
||||
ScoreUpdated = null;
|
||||
LivesUpdated = null;
|
||||
TimeUpdated = null;
|
||||
GameOver = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/GameEvents.cs.meta
Normal file
3
Assets/Scripts/Core/GameEvents.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26df539822f340e29cceac4ec34dd771
|
||||
timeCreated: 1765312863
|
||||
8
Assets/Scripts/Core/GameMode.cs
Normal file
8
Assets/Scripts/Core/GameMode.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Core
|
||||
{
|
||||
public enum GameMode
|
||||
{
|
||||
Arcade,
|
||||
TimeAttack,
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/GameMode.cs.meta
Normal file
3
Assets/Scripts/Core/GameMode.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53351add092b4030b6c58e9f72380f6d
|
||||
timeCreated: 1765311817
|
||||
97
Assets/Scripts/Core/GameSession.cs
Normal file
97
Assets/Scripts/Core/GameSession.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
public class GameSession
|
||||
{
|
||||
public GameMode GameMode { get; private set; }
|
||||
public int Score { get; private set; }
|
||||
public int Lives { get; private set; }
|
||||
public float TimeRemaining { get; private set; }
|
||||
public bool IsGameOver { get; private set; }
|
||||
public int HighScore { get; private set; }
|
||||
|
||||
public event Action<int> OnScoreChanged;
|
||||
public event Action<int> OnLivesChanged;
|
||||
public event Action<float> OnTimeChanged;
|
||||
public event Action OnGameOver;
|
||||
|
||||
private readonly int _initialTimeOrLives;
|
||||
|
||||
public GameSession(GameMode gameMode, int initialValue, int currentHighScore)
|
||||
{
|
||||
GameMode = gameMode;
|
||||
Score = 0;
|
||||
IsGameOver = false;
|
||||
HighScore = currentHighScore;
|
||||
|
||||
switch (gameMode)
|
||||
{
|
||||
case GameMode.TimeAttack:
|
||||
TimeRemaining = initialValue;
|
||||
Lives = 1;
|
||||
break;
|
||||
case GameMode.Arcade:
|
||||
Lives = initialValue;
|
||||
TimeRemaining = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
if (IsGameOver || GameMode != GameMode.TimeAttack) return;
|
||||
|
||||
TimeRemaining -= deltaTime;
|
||||
OnTimeChanged?.Invoke(TimeRemaining);
|
||||
|
||||
if (TimeRemaining <= 0)
|
||||
{
|
||||
TimeRemaining = 0;
|
||||
EndGame();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddScore(int points)
|
||||
{
|
||||
if (IsGameOver) return;
|
||||
|
||||
Score += points;
|
||||
OnScoreChanged?.Invoke(Score);
|
||||
}
|
||||
|
||||
public void LoseLife()
|
||||
{
|
||||
if (IsGameOver) return;
|
||||
|
||||
if (GameMode == GameMode.Arcade)
|
||||
{
|
||||
Lives--;
|
||||
OnLivesChanged?.Invoke(Lives);
|
||||
}
|
||||
else
|
||||
{
|
||||
TimeRemaining -= _initialTimeOrLives;
|
||||
OnTimeChanged?.Invoke(TimeRemaining);
|
||||
}
|
||||
|
||||
if (Lives <= 0)
|
||||
{
|
||||
Lives = 0;
|
||||
EndGame();
|
||||
}
|
||||
}
|
||||
|
||||
private void EndGame()
|
||||
{
|
||||
IsGameOver = true;
|
||||
|
||||
if (Score > HighScore)
|
||||
{
|
||||
HighScore = Score;
|
||||
}
|
||||
|
||||
OnGameOver?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/GameSession.cs.meta
Normal file
3
Assets/Scripts/Core/GameSession.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e5b97c41204455ca44be18f18ddd3b3
|
||||
timeCreated: 1765307365
|
||||
3
Assets/Scripts/Core/Systems.meta
Normal file
3
Assets/Scripts/Core/Systems.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a13e7537abf404e9a3932ec3f9b4e58
|
||||
timeCreated: 1765312897
|
||||
35
Assets/Scripts/Core/Systems/LivesSystem.cs
Normal file
35
Assets/Scripts/Core/Systems/LivesSystem.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace Core.Systems
|
||||
{
|
||||
public class LivesSystem : IDisposable
|
||||
{
|
||||
private int _currentLives;
|
||||
|
||||
public LivesSystem(int initialLives)
|
||||
{
|
||||
_currentLives = initialLives;
|
||||
GameEvents.PresentDropped += OnPresentDropped;
|
||||
|
||||
GameEvents.ReportLivesUpdated(_currentLives);
|
||||
}
|
||||
|
||||
private void OnPresentDropped()
|
||||
{
|
||||
if (_currentLives <= 0) return;
|
||||
|
||||
_currentLives--;
|
||||
GameEvents.ReportLivesUpdated(_currentLives);
|
||||
|
||||
if (_currentLives <= 0)
|
||||
{
|
||||
GameEvents.ReportGameOver();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GameEvents.PresentDropped -= OnPresentDropped;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Systems/LivesSystem.cs.meta
Normal file
3
Assets/Scripts/Core/Systems/LivesSystem.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 694c3777489643ee843bf35f734ac060
|
||||
timeCreated: 1765312961
|
||||
46
Assets/Scripts/Core/Systems/PersistenceSystem.cs
Normal file
46
Assets/Scripts/Core/Systems/PersistenceSystem.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Infrastructure;
|
||||
|
||||
namespace Core.Systems
|
||||
{
|
||||
public class PersistenceSystem : IDisposable
|
||||
{
|
||||
private readonly IPersistenceService _service;
|
||||
private readonly string _saveKey;
|
||||
private int _currentRunScore;
|
||||
|
||||
public PersistenceSystem(IPersistenceService service, string saveKey)
|
||||
{
|
||||
_service = service;
|
||||
_saveKey = saveKey;
|
||||
|
||||
GameEvents.ScoreUpdated += OnScoreUpdated;
|
||||
GameEvents.GameOver += OnGameOver;
|
||||
}
|
||||
|
||||
public int GetHighScore()
|
||||
{
|
||||
return _service.LoadHighScore(_saveKey);
|
||||
}
|
||||
|
||||
private void OnGameOver()
|
||||
{
|
||||
var existingHighScore = _service.LoadHighScore(_saveKey);
|
||||
if (_currentRunScore > existingHighScore)
|
||||
{
|
||||
_service.SaveHighScore(_saveKey, _currentRunScore);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScoreUpdated(int newScore)
|
||||
{
|
||||
_currentRunScore = newScore;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GameEvents.ScoreUpdated -= OnScoreUpdated;
|
||||
GameEvents.GameOver -= OnGameOver;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Systems/PersistenceSystem.cs.meta
Normal file
3
Assets/Scripts/Core/Systems/PersistenceSystem.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99a4c19937ce4a97881d2796dead20d4
|
||||
timeCreated: 1765313464
|
||||
26
Assets/Scripts/Core/Systems/ScoreSystem.cs
Normal file
26
Assets/Scripts/Core/Systems/ScoreSystem.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace Core.Systems
|
||||
{
|
||||
public class ScoreSystem : IDisposable
|
||||
{
|
||||
private int _currentScore;
|
||||
|
||||
public ScoreSystem()
|
||||
{
|
||||
_currentScore = 0;
|
||||
GameEvents.PresentCaught += OnPresentCaught;
|
||||
}
|
||||
|
||||
private void OnPresentCaught(int value)
|
||||
{
|
||||
_currentScore += value;
|
||||
GameEvents.ReportScoreUpdated(_currentScore);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GameEvents.PresentCaught -= OnPresentCaught;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Systems/ScoreSystem.cs.meta
Normal file
3
Assets/Scripts/Core/Systems/ScoreSystem.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9724125bb1674f2e9f8150c290b71d1a
|
||||
timeCreated: 1765312906
|
||||
51
Assets/Scripts/Core/Systems/TimeAttackSystem.cs
Normal file
51
Assets/Scripts/Core/Systems/TimeAttackSystem.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
|
||||
namespace Core.Systems
|
||||
{
|
||||
public class TimeAttackSystem : IDisposable
|
||||
{
|
||||
private float _timeRemaining;
|
||||
private readonly float _penaltyPerDrop;
|
||||
|
||||
public TimeAttackSystem(float initialTime, float penaltyPerDrop = 5f)
|
||||
{
|
||||
_timeRemaining = initialTime;
|
||||
_penaltyPerDrop = penaltyPerDrop;
|
||||
|
||||
GameEvents.ReportTimeUpdated(_timeRemaining);
|
||||
|
||||
GameEvents.PresentDropped += OnPresentDropped;
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
if (_timeRemaining <= 0) return;
|
||||
|
||||
_timeRemaining -= deltaTime;
|
||||
|
||||
if (_timeRemaining <= 0)
|
||||
{
|
||||
_timeRemaining = 0;
|
||||
GameEvents.ReportTimeUpdated(0f);
|
||||
GameEvents.ReportGameOver();
|
||||
}
|
||||
else
|
||||
{
|
||||
GameEvents.ReportTimeUpdated(_timeRemaining);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPresentDropped()
|
||||
{
|
||||
if (_timeRemaining > 0)
|
||||
{
|
||||
_timeRemaining -= _penaltyPerDrop;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GameEvents.PresentDropped -= OnPresentDropped;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Systems/TimeAttackSystem.cs.meta
Normal file
3
Assets/Scripts/Core/Systems/TimeAttackSystem.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81bda016e43341779a714e971304d37d
|
||||
timeCreated: 1765313331
|
||||
8
Assets/Scripts/Infrastructure.meta
Normal file
8
Assets/Scripts/Infrastructure.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec1e68a28d45e3d17a951d9446f09d41
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Infrastructure/IPersistenceService.cs
Normal file
8
Assets/Scripts/Infrastructure/IPersistenceService.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Infrastructure
|
||||
{
|
||||
public interface IPersistenceService
|
||||
{
|
||||
void SaveHighScore(string key, int score);
|
||||
int LoadHighScore(string key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78982eb5342e49b5aa23fe22e84d4da8
|
||||
timeCreated: 1765312139
|
||||
32
Assets/Scripts/Infrastructure/PlayerOneInput.cs
Normal file
32
Assets/Scripts/Infrastructure/PlayerOneInput.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Abstractions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Infrastructure
|
||||
{
|
||||
public class PlayerOneInput : IInputService, IDisposable
|
||||
{
|
||||
private readonly Actions _input = new();
|
||||
|
||||
public PlayerOneInput ()
|
||||
{
|
||||
_input.Player.Enable();
|
||||
}
|
||||
|
||||
public float GetHorizontalMovement()
|
||||
{
|
||||
return _input.Player.Move.ReadValue<Vector2>().x;
|
||||
}
|
||||
|
||||
public bool IsActionPressed()
|
||||
{
|
||||
return _input.Player.Interact.triggered;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_input.Player.Disable();
|
||||
_input?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Infrastructure/PlayerOneInput.cs.meta
Normal file
3
Assets/Scripts/Infrastructure/PlayerOneInput.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb373e773ac5427087b07b63c9accc90
|
||||
timeCreated: 1765308257
|
||||
18
Assets/Scripts/Infrastructure/PlayerPrefsPersistence.cs
Normal file
18
Assets/Scripts/Infrastructure/PlayerPrefsPersistence.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Infrastructure
|
||||
{
|
||||
public class PlayerPrefsPersistence : IPersistenceService
|
||||
{
|
||||
public void SaveHighScore(string key, int score)
|
||||
{
|
||||
PlayerPrefs.SetInt(key, score);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
public int LoadHighScore(string key)
|
||||
{
|
||||
return PlayerPrefs.GetInt(key, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fed5bb3ae38d41dc9cc89464b1c611e9
|
||||
timeCreated: 1765312168
|
||||
23
Assets/Scripts/Infrastructure/PlayerTwoInput.cs
Normal file
23
Assets/Scripts/Infrastructure/PlayerTwoInput.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using Abstractions;
|
||||
|
||||
namespace Infrastructure
|
||||
{
|
||||
public class PlayerTwoInput : IInputService, IDisposable
|
||||
{
|
||||
public float GetHorizontalMovement()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsActionPressed()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Infrastructure/PlayerTwoInput.cs.meta
Normal file
3
Assets/Scripts/Infrastructure/PlayerTwoInput.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8e90fd115b7440b9ccff749d09b6bcd
|
||||
timeCreated: 1765308287
|
||||
8
Assets/Scripts/Presentation.meta
Normal file
8
Assets/Scripts/Presentation.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51b062444819b810b960655ec67f6c70
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
59
Assets/Scripts/Presentation/ElfController.cs
Normal file
59
Assets/Scripts/Presentation/ElfController.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Abstractions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Presentation
|
||||
{
|
||||
public class ElfController : MonoBehaviour
|
||||
{
|
||||
[Header("Movement")] [SerializeField] private float moveSpeed = 10f;
|
||||
|
||||
[Header("Cane mechanics")] [SerializeField]
|
||||
private Transform canePivot;
|
||||
|
||||
[SerializeField] private float maxTiltAngle = 30f;
|
||||
[SerializeField] private float tiltSpeed = 15f;
|
||||
|
||||
private IInputService _inputService;
|
||||
|
||||
public void Configure(IInputService inputService)
|
||||
{
|
||||
_inputService = inputService;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_inputService == null) return;
|
||||
|
||||
var move = _inputService.GetHorizontalMovement();
|
||||
transform.Translate(Vector3.right * (move * moveSpeed * Time.deltaTime));
|
||||
|
||||
HandleCaneRotation(move);
|
||||
}
|
||||
|
||||
private void HandleCaneRotation(float inputDirection)
|
||||
{
|
||||
var targetAngle = 0f;
|
||||
|
||||
if (inputDirection > 0.1f)
|
||||
{
|
||||
targetAngle = -maxTiltAngle;
|
||||
}
|
||||
else if (inputDirection < -0.1f)
|
||||
{
|
||||
targetAngle = maxTiltAngle;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetAngle = 0f;
|
||||
}
|
||||
|
||||
var targetRotation = Quaternion.Euler(0f, 0f, targetAngle);
|
||||
canePivot.localRotation = Quaternion.Lerp(
|
||||
canePivot.localRotation,
|
||||
targetRotation,
|
||||
Time.deltaTime * tiltSpeed
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Presentation/ElfController.cs.meta
Normal file
3
Assets/Scripts/Presentation/ElfController.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef7ae4dbcdba422dbfcf1c7ed761a311
|
||||
timeCreated: 1765307632
|
||||
88
Assets/Scripts/Presentation/GameBootstrap.cs
Normal file
88
Assets/Scripts/Presentation/GameBootstrap.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using Abstractions;
|
||||
using Core;
|
||||
using Core.Systems;
|
||||
using Infrastructure;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Presentation
|
||||
{
|
||||
public class GameBootstrap : MonoBehaviour
|
||||
{
|
||||
private ScoreSystem _scoreSystem;
|
||||
private LivesSystem _livesSystem;
|
||||
private TimeAttackSystem _timeAttackSystem;
|
||||
private PersistenceSystem _persistenceSystem;
|
||||
|
||||
private IDisposable _p1Input;
|
||||
private IDisposable _p2Input;
|
||||
|
||||
[Header("Game Settings")]
|
||||
[SerializeField] private int startValue= 3;
|
||||
[SerializeField] private bool twoPlayerMode = false;
|
||||
[SerializeField] private GameMode mode = GameMode.Arcade;
|
||||
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField] private ElfController elfPrefab;
|
||||
[SerializeField] private Transform spawnP1;
|
||||
[SerializeField] private Transform spawnP2;
|
||||
[SerializeField] private PresentSpawner spawner;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_scoreSystem = new ScoreSystem();
|
||||
_livesSystem = new LivesSystem(startValue);
|
||||
|
||||
var saveKey = mode == GameMode.Arcade ? "HS_Arcade" : "HS_TimeAttack";
|
||||
_persistenceSystem = new PersistenceSystem(new PlayerPrefsPersistence(), saveKey);
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case GameMode.Arcade:
|
||||
_livesSystem = new LivesSystem(startValue);
|
||||
break;
|
||||
case GameMode.TimeAttack:
|
||||
_timeAttackSystem = new TimeAttackSystem(startValue);
|
||||
break;
|
||||
}
|
||||
|
||||
_p1Input = new PlayerOneInput();
|
||||
_p2Input = new PlayerTwoInput();
|
||||
|
||||
SpawnElf(_p1Input as IInputService, spawnP1.position, Color.white);
|
||||
|
||||
if (twoPlayerMode)
|
||||
{
|
||||
SpawnElf(_p2Input as IInputService, spawnP2.position, Color.green);
|
||||
}
|
||||
|
||||
spawner.StartSpawning();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (mode == GameMode.TimeAttack && _timeAttackSystem != null)
|
||||
{
|
||||
_timeAttackSystem.Tick(Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnElf(IInputService input, Vector3 position, Color tint)
|
||||
{
|
||||
var elf = Instantiate(elfPrefab, position, Quaternion.identity);
|
||||
elf.Configure(input);
|
||||
elf.GetComponentInChildren<SpriteRenderer>().color = tint;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_scoreSystem?.Dispose();
|
||||
_livesSystem?.Dispose();
|
||||
_timeAttackSystem?.Dispose();
|
||||
_persistenceSystem?.Dispose();
|
||||
_p1Input?.Dispose();
|
||||
_p2Input?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Presentation/GameBootstrap.cs.meta
Normal file
3
Assets/Scripts/Presentation/GameBootstrap.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e824a9a954a6401395ad66ebb282accc
|
||||
timeCreated: 1765307449
|
||||
68
Assets/Scripts/Presentation/GameHud.cs
Normal file
68
Assets/Scripts/Presentation/GameHud.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Core;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Presentation
|
||||
{
|
||||
public class GameHud : MonoBehaviour
|
||||
{
|
||||
[Header("Text References")]
|
||||
[SerializeField] private TextMeshProUGUI scoreText;
|
||||
[SerializeField] private TextMeshProUGUI livesOrTimeText;
|
||||
[SerializeField] private TextMeshProUGUI highScoreText;
|
||||
|
||||
[Header("Panels")]
|
||||
[SerializeField] private GameObject gameOverPanel;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (gameOverPanel) gameOverPanel.SetActive(false);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
GameEvents.ScoreUpdated += UpdateScore;
|
||||
GameEvents.LivesUpdated += UpdateLives;
|
||||
GameEvents.TimeUpdated += UpdateTime;
|
||||
GameEvents.GameOver += ShowGameOver;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
GameEvents.ScoreUpdated -= UpdateScore;
|
||||
GameEvents.LivesUpdated -= UpdateLives;
|
||||
GameEvents.TimeUpdated -= UpdateTime;
|
||||
GameEvents.GameOver -= ShowGameOver;
|
||||
}
|
||||
|
||||
private void UpdateScore(int score)
|
||||
{
|
||||
if(scoreText) scoreText.text = $"Score: {score}";
|
||||
}
|
||||
|
||||
private void UpdateLives(int lives)
|
||||
{
|
||||
if(livesOrTimeText) livesOrTimeText.text = $"Lives: {lives}";
|
||||
}
|
||||
|
||||
private void UpdateTime(float time)
|
||||
{
|
||||
if(livesOrTimeText) livesOrTimeText.text = $"Time: {time:F0}";
|
||||
|
||||
if (time <= 10f) livesOrTimeText.color = Color.red;
|
||||
else livesOrTimeText.color = Color.white;
|
||||
}
|
||||
|
||||
private void ShowGameOver()
|
||||
{
|
||||
if(gameOverPanel) gameOverPanel.SetActive(true);
|
||||
}
|
||||
|
||||
|
||||
public void RestartGame()
|
||||
{
|
||||
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Presentation/GameHud.cs.meta
Normal file
3
Assets/Scripts/Presentation/GameHud.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 420df631eed549fe9ab3dcddacb210c0
|
||||
timeCreated: 1765314012
|
||||
33
Assets/Scripts/Presentation/Present.cs
Normal file
33
Assets/Scripts/Presentation/Present.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Presentation
|
||||
{
|
||||
public class Present : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private int points = 1;
|
||||
|
||||
private PresentSpawner _spawner;
|
||||
|
||||
public void Configure(PresentSpawner spawner)
|
||||
{
|
||||
_spawner = spawner;
|
||||
}
|
||||
|
||||
private void OnCollisionEnter2D(Collision2D other)
|
||||
{
|
||||
if (other.gameObject.CompareTag("Ground"))
|
||||
{
|
||||
GameEvents.ReportPresentDropped();
|
||||
_spawner.ReturnToPool(this);
|
||||
}
|
||||
|
||||
if (other.gameObject.CompareTag("Sleigh"))
|
||||
{
|
||||
GameEvents.ReportPresentCaught(points);
|
||||
_spawner.ReturnToPool(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Presentation/Present.cs.meta
Normal file
3
Assets/Scripts/Presentation/Present.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a93e3dd5e12b468089cd95e7566ece1d
|
||||
timeCreated: 1765307547
|
||||
112
Assets/Scripts/Presentation/PresentSpawner.cs
Normal file
112
Assets/Scripts/Presentation/PresentSpawner.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Presentation
|
||||
{
|
||||
public class PresentSpawner : MonoBehaviour
|
||||
{
|
||||
[Header("Pooling")]
|
||||
[SerializeField] private Present presentPrefab;
|
||||
[SerializeField] private int poolSize = 20;
|
||||
|
||||
[Header("Spawning")]
|
||||
[SerializeField] private Transform[] spawnPoints;
|
||||
[SerializeField] private float spawnInterval = 1.5f;
|
||||
[SerializeField] private float burstChance = 0.3f;
|
||||
[SerializeField] private Vector2 initialDirection = Vector2.right;
|
||||
|
||||
private Queue<Present> _pool = new();
|
||||
private List<Present> _activePresents = new();
|
||||
private bool _isSpawning;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
for (var i = 0; i < poolSize; i++)
|
||||
{
|
||||
CreateNewPresent();
|
||||
}
|
||||
}
|
||||
|
||||
private Present CreateNewPresent()
|
||||
{
|
||||
var p = Instantiate(presentPrefab, transform);
|
||||
p.gameObject.SetActive(false);
|
||||
p.Configure(this);
|
||||
|
||||
_pool.Enqueue(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
private Present GetPresent()
|
||||
{
|
||||
if (_pool.Count == 0) CreateNewPresent();
|
||||
|
||||
var p = _pool.Dequeue();
|
||||
p.gameObject.SetActive(true);
|
||||
_activePresents.Add(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
public void ReturnToPool(Present p)
|
||||
{
|
||||
if (!p.gameObject.activeSelf) return;
|
||||
|
||||
p.gameObject.SetActive(false);
|
||||
_activePresents.Remove(p);
|
||||
_pool.Enqueue(p);
|
||||
}
|
||||
|
||||
public void StartSpawning()
|
||||
{
|
||||
_isSpawning = true;
|
||||
GameEvents.GameOver += StopSpawning;
|
||||
StartCoroutine(SpawnRoutine());
|
||||
}
|
||||
|
||||
public void StopSpawning()
|
||||
{
|
||||
_isSpawning = false;
|
||||
GameEvents.GameOver -= StopSpawning;
|
||||
StopAllCoroutines();
|
||||
|
||||
|
||||
foreach (var p in _activePresents.ToArray()) ReturnToPool(p);
|
||||
_activePresents.Clear();
|
||||
}
|
||||
|
||||
private IEnumerator SpawnRoutine()
|
||||
{
|
||||
while (_isSpawning)
|
||||
{
|
||||
SpawnOne();
|
||||
|
||||
if (Random.value < burstChance)
|
||||
{
|
||||
yield return new WaitForSeconds(0.2f);
|
||||
SpawnOne();
|
||||
}
|
||||
|
||||
//TODO: Difficulty scaling
|
||||
yield return new WaitForSeconds(spawnInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnOne()
|
||||
{
|
||||
var p = GetPresent();
|
||||
|
||||
var rb = p.GetComponent<Rigidbody2D>();
|
||||
rb.linearVelocity = Vector2.zero;
|
||||
rb.angularVelocity = 0f;
|
||||
|
||||
var index = Random.Range(0, spawnPoints.Length);
|
||||
p.transform.position = spawnPoints[index].position;
|
||||
p.transform.rotation = Quaternion.identity;
|
||||
rb.AddForce(initialDirection + new Vector2(Random.Range(-1f,1f), Random.Range(-1f,1f)), ForceMode2D.Impulse);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Presentation/PresentSpawner.cs.meta
Normal file
3
Assets/Scripts/Presentation/PresentSpawner.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 540dd15a8852481ab85229c9e3f928c8
|
||||
timeCreated: 1765312250
|
||||
Reference in New Issue
Block a user