Add initial project files and configurations for Unity setup
This commit is contained in:
3
Assets/Scripts/Core.meta
Normal file
3
Assets/Scripts/Core.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 954b5cd1f60c4b04a4069b08d863303c
|
||||
timeCreated: 1765560145
|
||||
3
Assets/Scripts/Core/Domain.meta
Normal file
3
Assets/Scripts/Core/Domain.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 214b1d40bd8c47dab78f4b861f1a3bef
|
||||
timeCreated: 1765560149
|
||||
99
Assets/Scripts/Core/Domain/GameSession.cs
Normal file
99
Assets/Scripts/Core/Domain/GameSession.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Core.Ports;
|
||||
|
||||
namespace Core.Domain
|
||||
{
|
||||
public class GameSession
|
||||
{
|
||||
private const string HighScoreKey = "HighScore";
|
||||
|
||||
public int Score { get; private set; }
|
||||
public int HighScore { get; private set; }
|
||||
public bool IsGameOver { get; private set; }
|
||||
|
||||
public event Action<int> OnScoreChanged;
|
||||
public event Action<string> OnOrbSpawned;
|
||||
public event Action OnOrbReset;
|
||||
public event Action OnGameOver;
|
||||
|
||||
private readonly List<Tile> _tiles;
|
||||
private readonly IPersistenceService _persistenceService;
|
||||
private readonly Random _rng = new();
|
||||
private int _playerFloorIndex = 0;
|
||||
|
||||
public GameSession(List<Tile> tiles, IPersistenceService persistenceService)
|
||||
{
|
||||
_tiles = tiles;
|
||||
_persistenceService = persistenceService;
|
||||
|
||||
Score = 0;
|
||||
IsGameOver = false;
|
||||
|
||||
HighScore = _persistenceService.Load(HighScoreKey, 0);
|
||||
}
|
||||
|
||||
public void StartGame()
|
||||
{
|
||||
SpawnNextOrb();
|
||||
}
|
||||
|
||||
public void OrbCollected()
|
||||
{
|
||||
if (IsGameOver) return;
|
||||
|
||||
Score += 10;
|
||||
OnScoreChanged?.Invoke(Score);
|
||||
SpawnNextOrb();
|
||||
}
|
||||
|
||||
private void SpawnNextOrb()
|
||||
{
|
||||
var validTiles = _tiles.FindAll(t =>
|
||||
t.CurrentState == TileState.Stable &&
|
||||
t.Floor == _playerFloorIndex
|
||||
);
|
||||
|
||||
if (validTiles.Count == 0)
|
||||
{
|
||||
validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable);
|
||||
}
|
||||
|
||||
if (validTiles.Count == 0)
|
||||
{
|
||||
EndGame();
|
||||
return;
|
||||
}
|
||||
|
||||
var pick = validTiles[_rng.Next(validTiles.Count)];
|
||||
OnOrbSpawned?.Invoke(pick.Id);
|
||||
}
|
||||
|
||||
public void EndGame()
|
||||
{
|
||||
if (IsGameOver) return;
|
||||
|
||||
IsGameOver = true;
|
||||
|
||||
if (Score > HighScore)
|
||||
{
|
||||
HighScore = Score;
|
||||
_persistenceService.Save(HighScoreKey, HighScore);
|
||||
}
|
||||
|
||||
OnGameOver?.Invoke();
|
||||
}
|
||||
|
||||
public void SetPlayerFloor(int floorIndex)
|
||||
{
|
||||
if (_playerFloorIndex == floorIndex) return;
|
||||
|
||||
_playerFloorIndex = floorIndex;
|
||||
|
||||
if (IsGameOver) return;
|
||||
|
||||
OnOrbReset?.Invoke();
|
||||
SpawnNextOrb();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Domain/GameSession.cs.meta
Normal file
3
Assets/Scripts/Core/Domain/GameSession.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d87b26f6f6c4273baa331c65f412eb2
|
||||
timeCreated: 1765563052
|
||||
61
Assets/Scripts/Core/Domain/MapPatterns.cs
Normal file
61
Assets/Scripts/Core/Domain/MapPatterns.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Core.Domain
|
||||
{
|
||||
public static class MapPatterns
|
||||
{
|
||||
public static List<Vector2Int> GenerateSquare(int width, int height)
|
||||
{
|
||||
var positions = new List<Vector2Int>();
|
||||
for (var x = 0; x < width; x++)
|
||||
{
|
||||
for (var y = 0; y < height; y++)
|
||||
{
|
||||
positions.Add(new Vector2Int(x, y));
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
public static List<Vector2Int> GenerateCircle(int diameter)
|
||||
{
|
||||
var positions = new List<Vector2Int>();
|
||||
var radius = diameter / 2.0f;
|
||||
var center = new Vector2(radius, radius);
|
||||
|
||||
for (var x = 0; x < diameter; x++)
|
||||
{
|
||||
for (var y = 0; y < diameter; y++)
|
||||
{
|
||||
if (Vector2.Distance(new Vector2(x, y), center) <= radius)
|
||||
{
|
||||
positions.Add(new Vector2Int(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
public static List<Vector2Int> GenerateDonut(int diameter, int holeSize)
|
||||
{
|
||||
var positions = new List<Vector2Int>();
|
||||
var radius = diameter / 2.0f;
|
||||
var holeRadius = holeSize / 2.0f;
|
||||
var center = new Vector2(radius, radius);
|
||||
|
||||
for (var x = 0; x < diameter; x++)
|
||||
{
|
||||
for (var y = 0; y < diameter; y++)
|
||||
{
|
||||
var dist = Vector2.Distance(new Vector2(x, y), center);
|
||||
if (dist <= radius && dist >= holeRadius)
|
||||
{
|
||||
positions.Add(new Vector2Int(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Domain/MapPatterns.cs.meta
Normal file
3
Assets/Scripts/Core/Domain/MapPatterns.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75b47c1431af431685b2b6b69775a812
|
||||
timeCreated: 1765570591
|
||||
60
Assets/Scripts/Core/Domain/Tile.cs
Normal file
60
Assets/Scripts/Core/Domain/Tile.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
|
||||
namespace Core.Domain
|
||||
{
|
||||
public class Tile
|
||||
{
|
||||
public string Id { get; }
|
||||
public int Floor { get; }
|
||||
|
||||
public TileState CurrentState { get; private set; }
|
||||
|
||||
private readonly float _warningDuration;
|
||||
private readonly float _fallingDuration;
|
||||
|
||||
private float _stateTimer;
|
||||
|
||||
public event Action<Tile> OnStateChanged;
|
||||
|
||||
public Tile(string id, int floor, float warningDuration, float fallingDuration)
|
||||
{
|
||||
Id = id;
|
||||
Floor = floor;
|
||||
|
||||
_warningDuration = warningDuration;
|
||||
_fallingDuration = fallingDuration;
|
||||
CurrentState = TileState.Stable;
|
||||
}
|
||||
|
||||
public void StepOn()
|
||||
{
|
||||
if (CurrentState == TileState.Stable)
|
||||
{
|
||||
TransitionTo(TileState.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
if (CurrentState == TileState.Stable || CurrentState == TileState.Destroyed) return;
|
||||
|
||||
_stateTimer += deltaTime;
|
||||
|
||||
if (CurrentState == TileState.Warning && _stateTimer >= _warningDuration)
|
||||
{
|
||||
TransitionTo(TileState.Falling);
|
||||
}
|
||||
else if (CurrentState == TileState.Falling && _stateTimer >= _fallingDuration)
|
||||
{
|
||||
TransitionTo(TileState.Destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
private void TransitionTo(TileState newState)
|
||||
{
|
||||
CurrentState = newState;
|
||||
_stateTimer = 0f;
|
||||
OnStateChanged?.Invoke(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Domain/Tile.cs.meta
Normal file
3
Assets/Scripts/Core/Domain/Tile.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f320af19d414e688f733d2f49c85499
|
||||
timeCreated: 1765560252
|
||||
10
Assets/Scripts/Core/Domain/TileState.cs
Normal file
10
Assets/Scripts/Core/Domain/TileState.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Core.Domain
|
||||
{
|
||||
public enum TileState
|
||||
{
|
||||
Stable,
|
||||
Warning,
|
||||
Falling,
|
||||
Destroyed
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Domain/TileState.cs.meta
Normal file
3
Assets/Scripts/Core/Domain/TileState.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 251941ec93e643f1bb352d1ba27415fa
|
||||
timeCreated: 1765560203
|
||||
3
Assets/Scripts/Core/Ports.meta
Normal file
3
Assets/Scripts/Core/Ports.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37708dbb6aaf42298419527eb635dda8
|
||||
timeCreated: 1765560153
|
||||
8
Assets/Scripts/Core/Ports/IPersistenceService.cs
Normal file
8
Assets/Scripts/Core/Ports/IPersistenceService.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Core.Ports
|
||||
{
|
||||
public interface IPersistenceService
|
||||
{
|
||||
void Save(string key, int value);
|
||||
int Load(string key, int defaultValue = 0);
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Ports/IPersistenceService.cs.meta
Normal file
3
Assets/Scripts/Core/Ports/IPersistenceService.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b21e8384f7154851a4b258200a5eaed8
|
||||
timeCreated: 1765568951
|
||||
13
Assets/Scripts/Core/Ports/ITileView.cs
Normal file
13
Assets/Scripts/Core/Ports/ITileView.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Core.Domain;
|
||||
|
||||
namespace Core.Ports
|
||||
{
|
||||
public interface ITileView
|
||||
{
|
||||
string TileId { get; }
|
||||
void Initialize(Tile tile);
|
||||
void SetVisualState(TileState state);
|
||||
void DropPhysics();
|
||||
void Dispose();
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Core/Ports/ITileView.cs.meta
Normal file
3
Assets/Scripts/Core/Ports/ITileView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4d59fe7258e450a868cd36cd306c0da
|
||||
timeCreated: 1765560752
|
||||
3
Assets/Scripts/Infrastructure.meta
Normal file
3
Assets/Scripts/Infrastructure.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c6aeb2618c14713b70a18d2e853c666
|
||||
timeCreated: 1765560174
|
||||
3
Assets/Scripts/Infrastructure/Unity.meta
Normal file
3
Assets/Scripts/Infrastructure/Unity.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3be051e7cc740978a40ac902bf5cd51
|
||||
timeCreated: 1765560851
|
||||
30
Assets/Scripts/Infrastructure/Unity/CameraController.cs
Normal file
30
Assets/Scripts/Infrastructure/Unity/CameraController.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using KBCore.Refs;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Infrastructure.Unity
|
||||
{
|
||||
public class CameraController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Transform target;
|
||||
[SerializeField] private Vector3 offset = new(-20, 20, -20);
|
||||
[SerializeField] private float smoothSpeed = 5f;
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!target) return;
|
||||
|
||||
// We follow the player, but we might want to clamp X/Z movement
|
||||
// so the camera doesn't jitter too much, only tracking Y (falling).
|
||||
// For a dynamic feel, let's track everything loosely.
|
||||
|
||||
var desiredPos = target.position + offset;
|
||||
|
||||
transform.position = Vector3.Lerp(transform.position, desiredPos, Time.deltaTime * smoothSpeed);
|
||||
}
|
||||
|
||||
public void SetTarget(Transform newTarget)
|
||||
{
|
||||
target = newTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e132ab29e5849ebb6ef6ccd851e9d58
|
||||
timeCreated: 1765569876
|
||||
29
Assets/Scripts/Infrastructure/Unity/DeathPlaneAdapter.cs
Normal file
29
Assets/Scripts/Infrastructure/Unity/DeathPlaneAdapter.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Infrastructure.Unity
|
||||
{
|
||||
[RequireComponent(typeof(BoxCollider))]
|
||||
public class DeathPlaneAdapter : MonoBehaviour
|
||||
{
|
||||
public event Action OnPlayerFell;
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.TryGetComponent<PlayerController>(out var player))
|
||||
{
|
||||
OnPlayerFell?.Invoke();
|
||||
}
|
||||
else if (other.TryGetComponent<TileViewAdapter>(out var tile))
|
||||
{
|
||||
Destroy(tile.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = new Color(1f, 0f, 0f, 0.3f);
|
||||
Gizmos.DrawCube(transform.position, transform.localScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa4bae7bcb0b493f8d6125f1aab4d67c
|
||||
timeCreated: 1765563955
|
||||
215
Assets/Scripts/Infrastructure/Unity/GameBootstrap.cs
Normal file
215
Assets/Scripts/Infrastructure/Unity/GameBootstrap.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Core.Domain;
|
||||
using Core.Ports;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Infrastructure.Unity
|
||||
{
|
||||
public class GameBootstrap : MonoBehaviour
|
||||
{
|
||||
[Header("Infrastructure")]
|
||||
[SerializeField] private LevelGenerator levelGenerator;
|
||||
[SerializeField] private OrbViewAdapter orbPrefab;
|
||||
[SerializeField] private PlayerController playerPrefab;
|
||||
[SerializeField] private DeathPlaneAdapter deathPlanePrefab;
|
||||
[SerializeField] private SoundManager soundManager;
|
||||
[SerializeField] private CameraController cameraController;
|
||||
|
||||
[Header("Level Generation")]
|
||||
[SerializeField] private int floorsCount = 3;
|
||||
[SerializeField] private float floorHeightDistance = 15f;
|
||||
|
||||
[Header("Ui")]
|
||||
[SerializeField] private TMP_Text scoreText;
|
||||
[SerializeField] private TMP_Text highScoreText;
|
||||
[SerializeField] private GameObject gameOverUi;
|
||||
[SerializeField] private GameObject startScreenUi;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private float restartTime = 3f;
|
||||
|
||||
|
||||
private readonly List<Tile> _allTiles = new();
|
||||
private readonly Dictionary<string, TileViewAdapter> _tileViews = new();
|
||||
private GameSession _gameSession;
|
||||
private IPersistenceService _persistenceService;
|
||||
private InputSystem_Actions _actions;
|
||||
private PlayerController _playerInstance;
|
||||
private GameObject _currentOrbInstance;
|
||||
private bool _isGameRunning;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_actions = new InputSystem_Actions();
|
||||
_actions.Player.Enable();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_actions.Player.Disable();
|
||||
}
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (levelGenerator) levelGenerator.Generate(soundManager, _allTiles, _tileViews);
|
||||
|
||||
SpawnDeathPlane();
|
||||
SpawnPlayer();
|
||||
|
||||
if (gameOverUi) gameOverUi.SetActive(false);
|
||||
if (startScreenUi) startScreenUi.SetActive(true);
|
||||
|
||||
_persistenceService = new PlayerPrefsPersistenceAdapter();
|
||||
_gameSession = new GameSession(_allTiles, _persistenceService);
|
||||
|
||||
WireEvents();
|
||||
UpdateScoreUi(_gameSession.Score);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isGameRunning)
|
||||
{
|
||||
if (_actions.Player.StartGame.triggered)
|
||||
{
|
||||
StartGameSequence();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_playerInstance)
|
||||
{
|
||||
var playerY = _playerInstance.transform.position.y;
|
||||
var currentFloor = Mathf.RoundToInt(-playerY / floorHeightDistance);
|
||||
|
||||
currentFloor = Mathf.Clamp(currentFloor, 0, floorsCount - 1);
|
||||
|
||||
_gameSession.SetPlayerFloor(currentFloor);
|
||||
}
|
||||
|
||||
var dt = Time.deltaTime;
|
||||
for (var i = _allTiles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
_allTiles[i].Tick(dt);
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnVisualOrb(string tileId)
|
||||
{
|
||||
if (_currentOrbInstance) Destroy(_currentOrbInstance);
|
||||
|
||||
if (!_tileViews.TryGetValue(tileId, out var tileView)) return;
|
||||
if (!tileView) return;
|
||||
|
||||
var spawnPos = tileView.transform.position + Vector3.up;
|
||||
var orb = Instantiate(orbPrefab, spawnPos, Quaternion.identity);
|
||||
|
||||
orb.OnCollected += () => _gameSession.OrbCollected();
|
||||
|
||||
_currentOrbInstance = orb.gameObject;
|
||||
}
|
||||
|
||||
private void HandleOrbReset()
|
||||
{
|
||||
if (_currentOrbInstance)
|
||||
{
|
||||
Destroy(_currentOrbInstance);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateScoreUi(int newScore)
|
||||
{
|
||||
if (!scoreText) return;
|
||||
|
||||
scoreText.text = $"Data: {newScore}";
|
||||
|
||||
if (highScoreText) highScoreText.text = $"BEST: {_gameSession.HighScore}";
|
||||
|
||||
}
|
||||
|
||||
private void SpawnPlayer()
|
||||
{
|
||||
var spawnPos = new Vector3(0f, 5f, 0f);
|
||||
_playerInstance = Instantiate(playerPrefab, spawnPos, Quaternion.identity);
|
||||
|
||||
_playerInstance.enabled = false;
|
||||
|
||||
_playerInstance.Rigidbody.isKinematic = true;
|
||||
|
||||
if (cameraController)
|
||||
{
|
||||
cameraController.SetTarget(_playerInstance.transform);
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnDeathPlane()
|
||||
{
|
||||
if (!levelGenerator) return;
|
||||
|
||||
var lowestY = -(levelGenerator.FloorsCount * levelGenerator.FloorHeightDistance) - 5f;
|
||||
var pos = new Vector3(levelGenerator.GridSizeX / 2f, lowestY, levelGenerator.GridSizeY / 2f);
|
||||
var plane = Instantiate(deathPlanePrefab, pos, Quaternion.identity);
|
||||
plane.transform.localScale = new Vector3(levelGenerator.GridSizeX * 2f, 1f, levelGenerator.GridSizeY * 2f);
|
||||
|
||||
plane.OnPlayerFell += () => _gameSession.EndGame();
|
||||
}
|
||||
|
||||
private void HandleGameOver()
|
||||
{
|
||||
_isGameRunning = false;
|
||||
if (gameOverUi) gameOverUi.SetActive(true);
|
||||
|
||||
StartCoroutine(RestartRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator RestartRoutine()
|
||||
{
|
||||
yield return new WaitForSeconds(restartTime);
|
||||
|
||||
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
|
||||
}
|
||||
|
||||
private void WireEvents()
|
||||
{
|
||||
_gameSession.OnScoreChanged += UpdateScoreUi;
|
||||
_gameSession.OnOrbSpawned += SpawnVisualOrb;
|
||||
_gameSession.OnOrbReset += HandleOrbReset;
|
||||
_gameSession.OnGameOver += HandleGameOver;
|
||||
|
||||
if (!soundManager) return;
|
||||
|
||||
_gameSession.OnScoreChanged += _ => soundManager.PlayScore();
|
||||
_gameSession.OnGameOver += () =>
|
||||
{
|
||||
soundManager.StopMusic();
|
||||
soundManager.PlayGameOver();
|
||||
};
|
||||
}
|
||||
|
||||
private void StartGameSequence()
|
||||
{
|
||||
_isGameRunning = true;
|
||||
|
||||
if (startScreenUi) startScreenUi.SetActive(false);
|
||||
|
||||
if (soundManager)
|
||||
{
|
||||
soundManager.PlayGameStart();
|
||||
soundManager.PlayMusic();
|
||||
}
|
||||
|
||||
if (_playerInstance)
|
||||
{
|
||||
_playerInstance.enabled = true;
|
||||
_playerInstance.Rigidbody.isKinematic = false;
|
||||
}
|
||||
|
||||
_gameSession.StartGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9764956bb29248b4a359e36fc4bf79ae
|
||||
timeCreated: 1765561348
|
||||
82
Assets/Scripts/Infrastructure/Unity/LevelGenerator.cs
Normal file
82
Assets/Scripts/Infrastructure/Unity/LevelGenerator.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
using Core.Domain;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Infrastructure.Unity
|
||||
{
|
||||
public class LevelGenerator : MonoBehaviour
|
||||
{
|
||||
[Header("Prefabs")]
|
||||
[SerializeField] private TileViewAdapter tilePrefab;
|
||||
[SerializeField] private ParticleSystem tileBreakVfxPrefab;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private int gridSizeX = 10;
|
||||
[SerializeField] private int gridSizeY = 10;
|
||||
[SerializeField] private int floorsCount = 3;
|
||||
[SerializeField] private float floorHeightDistance = 15f;
|
||||
[SerializeField] private float decayTime = 0.5f;
|
||||
[SerializeField] private float fallingTime = 2.0f;
|
||||
|
||||
public float FloorHeightDistance => floorHeightDistance;
|
||||
public int FloorsCount => floorsCount;
|
||||
public int GridSizeX => gridSizeX;
|
||||
public int GridSizeY => gridSizeY;
|
||||
|
||||
public void Generate(SoundManager soundManager, List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews)
|
||||
{
|
||||
GenerateFloor(0, MapPatterns.GenerateSquare(gridSizeX, gridSizeY), soundManager, allTiles, tileViews);
|
||||
|
||||
GenerateFloor(1, MapPatterns.GenerateDonut(gridSizeX, Mathf.FloorToInt(gridSizeX / 3f)), soundManager, allTiles, tileViews);
|
||||
|
||||
GenerateFloor(2, MapPatterns.GenerateCircle(gridSizeX), soundManager, allTiles, tileViews);
|
||||
}
|
||||
|
||||
private void GenerateFloor(int floorIndex, List<Vector2Int> coordinates, SoundManager soundManager,
|
||||
List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews)
|
||||
{
|
||||
var yOffset = -(floorIndex * floorHeightDistance);
|
||||
var xOffset = gridSizeX / 2f;
|
||||
var zOffset = gridSizeY / 2f;
|
||||
|
||||
foreach (var coord in coordinates)
|
||||
{
|
||||
var pos = new Vector3(coord.x - xOffset, yOffset, coord.y - zOffset);
|
||||
CreateTile(pos, $"{floorIndex}_{coord.x}_{coord.y}", floorIndex, soundManager, allTiles, tileViews);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateTile(Vector3 position, string id, int floorIndex, SoundManager soundManager,
|
||||
List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews)
|
||||
{
|
||||
var go = Instantiate(tilePrefab, position, Quaternion.identity, transform);
|
||||
go.transform.localScale = Vector3.one * 0.95f;
|
||||
|
||||
var tileLogic = new Tile(id, floorIndex, decayTime, fallingTime);
|
||||
|
||||
go.Initialize(tileLogic);
|
||||
|
||||
if (soundManager)
|
||||
{
|
||||
tileLogic.OnStateChanged += (t) =>
|
||||
{
|
||||
if (t.CurrentState == TileState.Warning)
|
||||
{
|
||||
soundManager.PlayTileWarning(position);
|
||||
}
|
||||
else if (t.CurrentState == TileState.Falling)
|
||||
{
|
||||
soundManager.PlayTileBreak(position);
|
||||
if (tileBreakVfxPrefab)
|
||||
{
|
||||
Instantiate(tileBreakVfxPrefab, position, Quaternion.identity);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
allTiles.Add(tileLogic);
|
||||
tileViews.Add(id, go);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6d1217b4d414725b0b6959561a01de9
|
||||
timeCreated: 1765572515
|
||||
19
Assets/Scripts/Infrastructure/Unity/OrbViewAdapter.cs
Normal file
19
Assets/Scripts/Infrastructure/Unity/OrbViewAdapter.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Infrastructure.Unity
|
||||
{
|
||||
public class OrbViewAdapter : MonoBehaviour
|
||||
{
|
||||
public event Action OnCollected;
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.TryGetComponent<PlayerController>(out var player))
|
||||
{
|
||||
OnCollected?.Invoke();
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ee231a557264fecb3ee52e4f174dcb1
|
||||
timeCreated: 1765563223
|
||||
92
Assets/Scripts/Infrastructure/Unity/PlayerController.cs
Normal file
92
Assets/Scripts/Infrastructure/Unity/PlayerController.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using KBCore.Refs;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Infrastructure.Unity
|
||||
{
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class PlayerController : MonoBehaviour
|
||||
{
|
||||
[Header("Movement Settings")]
|
||||
[SerializeField]
|
||||
private float moveSpeed = 8f;
|
||||
[SerializeField] private float maxVelocityChange = 10f;
|
||||
|
||||
[Header("Interaction")]
|
||||
[SerializeField] private LayerMask tileLayer;
|
||||
[SerializeField] private float groundCheckDistance = 1.5f;
|
||||
|
||||
[Self] [SerializeField] private Rigidbody rb;
|
||||
|
||||
private InputSystem_Actions _actions;
|
||||
private Vector2 _moveInput;
|
||||
|
||||
public Rigidbody Rigidbody => rb;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_actions.Player.Enable();
|
||||
|
||||
_actions.Player.Move.performed += OnMovePerformed;
|
||||
_actions.Player.Move.canceled += OnMoveCanceled;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_actions.Player.Move.performed -= OnMovePerformed;
|
||||
_actions.Player.Move.canceled -= OnMoveCanceled;
|
||||
|
||||
_actions.Player.Disable();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_actions = new InputSystem_Actions();
|
||||
|
||||
rb.freezeRotation = true;
|
||||
rb.useGravity = true;
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
HandleMovement();
|
||||
DetectGround();
|
||||
}
|
||||
|
||||
private void HandleMovement()
|
||||
{
|
||||
var targetVelocity = new Vector3(_moveInput.x, 0f, _moveInput.y) * moveSpeed;
|
||||
|
||||
var velocity = rb.linearVelocity;
|
||||
var velocityChange = (targetVelocity - velocity);
|
||||
|
||||
velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange);
|
||||
velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange);
|
||||
velocityChange.y = 0f;
|
||||
|
||||
rb.AddForce(velocityChange, ForceMode.VelocityChange);
|
||||
}
|
||||
|
||||
private void DetectGround()
|
||||
{
|
||||
if (Physics.Raycast(transform.position, Vector3.down, out var hit, groundCheckDistance, tileLayer))
|
||||
{
|
||||
if (hit.collider.TryGetComponent<TileViewAdapter>(out var tileAdapter))
|
||||
{
|
||||
tileAdapter.OnPlayerStep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMovePerformed(InputAction.CallbackContext ctx)
|
||||
{
|
||||
_moveInput = ctx.ReadValue<Vector2>();
|
||||
}
|
||||
|
||||
private void OnMoveCanceled(InputAction.CallbackContext ctx)
|
||||
{
|
||||
_moveInput = Vector2.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49a47df4933945dab48ae9018643dd67
|
||||
timeCreated: 1765562408
|
||||
@@ -0,0 +1,19 @@
|
||||
using Core.Ports;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Infrastructure.Unity
|
||||
{
|
||||
public class PlayerPrefsPersistenceAdapter : IPersistenceService
|
||||
{
|
||||
public void Save(string key, int value)
|
||||
{
|
||||
PlayerPrefs.SetInt(key, value);
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
public int Load(string key, int defaultValue = 0)
|
||||
{
|
||||
return PlayerPrefs.GetInt(key, defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ca0344bb0e64ebe82a4209351b6faa8
|
||||
timeCreated: 1765568985
|
||||
68
Assets/Scripts/Infrastructure/Unity/SoundManager.cs
Normal file
68
Assets/Scripts/Infrastructure/Unity/SoundManager.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using KBCore.Refs;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Infrastructure.Unity
|
||||
{
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
public class SoundManager : MonoBehaviour
|
||||
{
|
||||
[Header("Music")]
|
||||
[SerializeField] private AudioClip bgMusicClip;
|
||||
|
||||
[Header("Sound Effects")]
|
||||
[SerializeField] private AudioClip startClip;
|
||||
|
||||
[SerializeField] private AudioClip scoreClip;
|
||||
[SerializeField] private AudioClip gameOverClip;
|
||||
[SerializeField] private AudioClip tileWarningClip;
|
||||
[SerializeField] private AudioClip tileBreakClip;
|
||||
|
||||
[Self] [SerializeField] private AudioSource musicSource;
|
||||
[Self] [SerializeField] private AudioSource sfxSource;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
musicSource.loop = true;
|
||||
musicSource.playOnAwake = false;
|
||||
}
|
||||
|
||||
public void PlayMusic()
|
||||
{
|
||||
if (!bgMusicClip) return;
|
||||
|
||||
musicSource.clip = bgMusicClip;
|
||||
musicSource.Play();
|
||||
}
|
||||
|
||||
public void StopMusic()
|
||||
{
|
||||
musicSource.Stop();
|
||||
}
|
||||
|
||||
public void PlayGameStart() => PlaySfx(startClip);
|
||||
public void PlayScore() => PlaySfx(scoreClip);
|
||||
public void PlayGameOver() => PlaySfx(gameOverClip);
|
||||
|
||||
public void PlayTileBreak(Vector3 position)
|
||||
{
|
||||
if (!tileBreakClip) return;
|
||||
|
||||
AudioSource.PlayClipAtPoint(tileBreakClip, position, 1f);
|
||||
}
|
||||
|
||||
public void PlayTileWarning(Vector3 position)
|
||||
{
|
||||
if (!tileWarningClip) return;
|
||||
|
||||
AudioSource.PlayClipAtPoint(tileWarningClip, position, 0.5f);
|
||||
}
|
||||
|
||||
private void PlaySfx(AudioClip clip)
|
||||
{
|
||||
if (!clip) return;
|
||||
|
||||
sfxSource.PlayOneShot(clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Infrastructure/Unity/SoundManager.cs.meta
Normal file
3
Assets/Scripts/Infrastructure/Unity/SoundManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 605a8529ba214e75baf6cc7dcd3e9527
|
||||
timeCreated: 1765566925
|
||||
139
Assets/Scripts/Infrastructure/Unity/TileViewAdapter.cs
Normal file
139
Assets/Scripts/Infrastructure/Unity/TileViewAdapter.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Core.Domain;
|
||||
using Core.Ports;
|
||||
using KBCore.Refs;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace Infrastructure.Unity
|
||||
{
|
||||
[RequireComponent(typeof(MeshRenderer), typeof(Rigidbody))]
|
||||
public class TileViewAdapter : MonoBehaviour, ITileView
|
||||
{
|
||||
private Tile _linkedTile;
|
||||
|
||||
public string TileId { get; private set; }
|
||||
|
||||
[Header("Visuals Settings")]
|
||||
[SerializeField] private Color stableColor = new Color(0.2f, 0.2f, 0.2f); // Dark Grey
|
||||
[SerializeField] private Color warningColor = new Color(1f, 0.2f, 0.2f); // Neon Red
|
||||
[SerializeField] private float colorSpeed = 2f;
|
||||
|
||||
[Self] [SerializeField] private Rigidbody rb;
|
||||
[Self] [SerializeField] private MeshRenderer meshRenderer;
|
||||
|
||||
public Rigidbody Rigidbody => rb;
|
||||
public MeshRenderer MeshRenderer => meshRenderer;
|
||||
|
||||
private MaterialPropertyBlock _propBlock;
|
||||
private static readonly int ColorProperty = Shader.PropertyToID("_BaseColor");
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_propBlock = new MaterialPropertyBlock();
|
||||
|
||||
rb.isKinematic = true;
|
||||
rb.useGravity = false;
|
||||
}
|
||||
|
||||
public void Initialize(Tile tile)
|
||||
{
|
||||
_linkedTile = tile;
|
||||
TileId = tile.Id;
|
||||
|
||||
SetColor(stableColor);
|
||||
|
||||
_linkedTile.OnStateChanged += OnTileStateChanged;
|
||||
}
|
||||
|
||||
private void OnTileStateChanged(Tile tile)
|
||||
{
|
||||
SetVisualState(tile.CurrentState);
|
||||
}
|
||||
|
||||
public void SetVisualState(TileState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case TileState.Stable:
|
||||
StartCoroutine(AnimateColor(stableColor));
|
||||
break;
|
||||
case TileState.Warning:
|
||||
StartCoroutine(AnimateColor(warningColor));
|
||||
break;
|
||||
case TileState.Falling:
|
||||
DropPhysics();
|
||||
// Optionally change material or add effects for falling state
|
||||
break;
|
||||
case TileState.Destroyed:
|
||||
Dispose();
|
||||
// Optionally change material or add effects for destroyed state
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(state), state, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void DropPhysics()
|
||||
{
|
||||
rb.isKinematic = false;
|
||||
rb.useGravity = true;
|
||||
|
||||
// rb.AddTorque(Random.insideUnitSphere * 5f, ForceMode.Impulse);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StartCoroutine(ShrinkAndDestroy());
|
||||
}
|
||||
|
||||
public void OnPlayerStep()
|
||||
{
|
||||
_linkedTile?.StepOn();
|
||||
}
|
||||
|
||||
private void SetColor(Color color)
|
||||
{
|
||||
meshRenderer.GetPropertyBlock(_propBlock);
|
||||
_propBlock.SetColor(ColorProperty, color);
|
||||
meshRenderer.SetPropertyBlock(_propBlock);
|
||||
}
|
||||
|
||||
private IEnumerator AnimateColor(Color targetColor)
|
||||
{
|
||||
meshRenderer.GetPropertyBlock(_propBlock);
|
||||
var startColor = _propBlock.GetColor(ColorProperty);
|
||||
var t = 0f;
|
||||
|
||||
while (t < 1f)
|
||||
{
|
||||
t += Time.deltaTime * colorSpeed;
|
||||
SetColor(Color.Lerp(startColor, targetColor, t));
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator ShrinkAndDestroy()
|
||||
{
|
||||
var t = 0f;
|
||||
var startScale = transform.localScale;
|
||||
while (t < 1f)
|
||||
{
|
||||
t += Time.deltaTime;
|
||||
transform.localScale = Vector3.Lerp(startScale, Vector3.zero, t);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_linkedTile != null)
|
||||
{
|
||||
_linkedTile.OnStateChanged -= OnTileStateChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44ac2f92bd734e8b9576e3f008267f1c
|
||||
timeCreated: 1765560859
|
||||
Reference in New Issue
Block a user