Add initial project files and configurations for Unity setup

This commit is contained in:
2025-12-12 22:04:14 +01:00
commit b6106cf82b
205 changed files with 79202 additions and 0 deletions

3
Assets/Scripts/Core.meta Normal file
View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 954b5cd1f60c4b04a4069b08d863303c
timeCreated: 1765560145

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 214b1d40bd8c47dab78f4b861f1a3bef
timeCreated: 1765560149

View 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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0d87b26f6f6c4273baa331c65f412eb2
timeCreated: 1765563052

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 75b47c1431af431685b2b6b69775a812
timeCreated: 1765570591

View 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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2f320af19d414e688f733d2f49c85499
timeCreated: 1765560252

View File

@@ -0,0 +1,10 @@
namespace Core.Domain
{
public enum TileState
{
Stable,
Warning,
Falling,
Destroyed
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 251941ec93e643f1bb352d1ba27415fa
timeCreated: 1765560203

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 37708dbb6aaf42298419527eb635dda8
timeCreated: 1765560153

View File

@@ -0,0 +1,8 @@
namespace Core.Ports
{
public interface IPersistenceService
{
void Save(string key, int value);
int Load(string key, int defaultValue = 0);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b21e8384f7154851a4b258200a5eaed8
timeCreated: 1765568951

View 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();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a4d59fe7258e450a868cd36cd306c0da
timeCreated: 1765560752

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6c6aeb2618c14713b70a18d2e853c666
timeCreated: 1765560174

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d3be051e7cc740978a40ac902bf5cd51
timeCreated: 1765560851

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3e132ab29e5849ebb6ef6ccd851e9d58
timeCreated: 1765569876

View 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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aa4bae7bcb0b493f8d6125f1aab4d67c
timeCreated: 1765563955

View 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();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9764956bb29248b4a359e36fc4bf79ae
timeCreated: 1765561348

View 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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d6d1217b4d414725b0b6959561a01de9
timeCreated: 1765572515

View 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);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6ee231a557264fecb3ee52e4f174dcb1
timeCreated: 1765563223

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 49a47df4933945dab48ae9018643dd67
timeCreated: 1765562408

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3ca0344bb0e64ebe82a4209351b6faa8
timeCreated: 1765568985

View 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);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 605a8529ba214e75baf6cc7dcd3e9527
timeCreated: 1765566925

View 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;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 44ac2f92bd734e8b9576e3f008267f1c
timeCreated: 1765560859