Add Hunter NPC and Teleporter features with associated prefabs and effects

This commit is contained in:
2025-12-13 00:01:29 +01:00
parent c0fb207768
commit d8b0583fac
32 changed files with 823 additions and 217 deletions

View File

@@ -9,6 +9,10 @@ namespace Infrastructure.Unity
[SerializeField] private Vector3 offset = new(-20, 20, -20);
[SerializeField] private float smoothSpeed = 5f;
private Vector3 _originalPos; // This field is declared but not used in the provided LateUpdate logic.
private float _shakeTimer;
private float _shakeMagnitude;
private void LateUpdate()
{
if (!target) return;
@@ -17,14 +21,30 @@ namespace Infrastructure.Unity
// 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);
var targetPos = target.position + offset;
var smoothPos = Vector3.Lerp(transform.position, targetPos, smoothSpeed * Time.deltaTime);
if (_shakeTimer > 0)
{
_shakeTimer -= Time.deltaTime;
var shakeOffset = Random.insideUnitSphere * _shakeMagnitude;
transform.position = smoothPos + shakeOffset;
}
else
{
transform.position = smoothPos;
}
}
public void SetTarget(Transform newTarget)
{
target = newTarget;
}
public void Shake(float duration, float magnitude)
{
_shakeTimer = duration;
_shakeMagnitude = magnitude;
}
}
}

View File

@@ -10,7 +10,7 @@ namespace Infrastructure.Unity
{
public class GameBootstrap : MonoBehaviour
{
[Header("Infrastructure")]
[Header("Infrastructure")]
[SerializeField] private LevelGenerator levelGenerator;
[SerializeField] private OrbViewAdapter orbPrefab;
[SerializeField] private PlayerController playerPrefab;
@@ -18,23 +18,23 @@ namespace Infrastructure.Unity
[SerializeField] private SoundManager soundManager;
[SerializeField] private CameraController cameraController;
[SerializeField] private NpcController npcPrefab;
[SerializeField] private HunterNpcController hunterNpcPrefab;
[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;
[Header("Power Ups")]
[SerializeField] private PowerUpViewAdapter lightFootedPrefab;
[SerializeField] private PowerUpViewAdapter speedBoostPrefab;
[SerializeField] private PowerUpViewAdapter powerUpPrefab;
private readonly List<Tile> _allTiles = new();
private readonly Dictionary<string, TileViewAdapter> _tileViews = new();
@@ -59,17 +59,20 @@ namespace Infrastructure.Unity
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);
// Set Theme based on High Score
ThemeManager.CurrentTheme = ThemeManager.GetTheme(_gameSession.HighScore);
if (levelGenerator) levelGenerator.Generate(soundManager, _allTiles, _tileViews, cameraController);
SpawnDeathPlane();
SpawnPlayer();
if (gameOverUi) gameOverUi.SetActive(false);
if (startScreenUi) startScreenUi.SetActive(true);
WireEvents();
UpdateScoreUi(_gameSession.Score);
}
@@ -90,34 +93,39 @@ namespace Infrastructure.Unity
{
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;
if (_isGameRunning) _gameSession.Tick(dt);
var dilation = _gameSession.TimeDilation;
// Hard Mode: Decay faster as score increases
var decayMultiplier = 1.0f + (_gameSession.Score / 500f);
for (var i = _allTiles.Count - 1; i >= 0; i--)
{
_allTiles[i].Tick(dt);
_allTiles[i].Tick(dt * dilation * decayMultiplier);
}
}
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;
}
@@ -132,22 +140,25 @@ namespace Infrastructure.Unity
private void UpdateScoreUi(int newScore)
{
if (!scoreText) return;
scoreText.text = $"Data: {newScore}";
if (highScoreText) highScoreText.text = $"BEST: {_gameSession.HighScore}";
var combo = _gameSession?.ComboMultiplier ?? 1;
var comboText = combo > 1 ? $" (x{combo})" : "";
scoreText.text = $"Data: {newScore}{comboText}";
if (highScoreText && _gameSession != null) 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);
@@ -162,7 +173,7 @@ namespace Infrastructure.Unity
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();
}
@@ -177,7 +188,7 @@ namespace Infrastructure.Unity
private IEnumerator RestartRoutine()
{
yield return new WaitForSeconds(restartTime);
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
@@ -191,7 +202,7 @@ namespace Infrastructure.Unity
_gameSession.OnSpawnPowerUp += SpawnPowerUp;
if (!soundManager) return;
_gameSession.OnScoreChanged += _ => soundManager.PlayScore();
_gameSession.OnGameOver += () =>
{
@@ -202,18 +213,26 @@ namespace Infrastructure.Unity
private void SpawnNpc()
{
if (!npcPrefab) return;
var spawnPos = new Vector3(levelGenerator.GridSizeX / 2f, 7f, levelGenerator.GridSizeY / 2f);
Instantiate(npcPrefab, spawnPos, Quaternion.identity);
// 30% chance for Hunter if player available
if (_playerInstance && hunterNpcPrefab && Random.value < 0.3f)
{
var hunter = Instantiate(hunterNpcPrefab, spawnPos, Quaternion.identity);
hunter.Initialize(_playerInstance);
}
else if (npcPrefab)
{
Instantiate(npcPrefab, spawnPos, Quaternion.identity);
}
soundManager.PlayNpcSpawn();
}
private void StartGameSequence()
{
_isGameRunning = true;
if (startScreenUi) startScreenUi.SetActive(false);
if (soundManager)
@@ -227,7 +246,7 @@ namespace Infrastructure.Unity
_playerInstance.enabled = true;
_playerInstance.Rigidbody.isKinematic = false;
}
_gameSession.StartGame();
}
@@ -237,15 +256,17 @@ namespace Infrastructure.Unity
if (!tileView) return;
var spawnPos = tileView.transform.position + Vector3.up * 0.5f;
var prefabToSpawn = type == PowerUpType.LightFooted
? lightFootedPrefab
: speedBoostPrefab;
if (!prefabToSpawn) return;
var instance = Instantiate(powerUpPrefab, spawnPos, Quaternion.identity);
var instance = Instantiate(prefabToSpawn, spawnPos, Quaternion.identity);
instance.Configure(type);
instance.OnCollected += (t) =>
{
if (t == PowerUpType.TimeSlow)
{
_gameSession.ActivateTimeSlow(10f);
}
};
}
}
}

View File

@@ -0,0 +1,59 @@
using UnityEngine;
using KBCore.Refs;
namespace Infrastructure.Unity
{
[RequireComponent(typeof(Rigidbody))]
public class HunterNpcController : MonoBehaviour
{
[SerializeField] private float moveSpeed = 7f;
[SerializeField] private LayerMask tileLayer;
[Self] [SerializeField] private Rigidbody rb;
private PlayerController _target;
public void Initialize(PlayerController target)
{
_target = target;
}
private void FixedUpdate()
{
if (_target == null) return;
if (IsGrounded())
{
var direction = (_target.transform.position - transform.position).normalized;
// Flatten direction to avoid flying/digging
direction.y = 0;
direction.Normalize();
rb.MovePosition(rb.position + direction * (moveSpeed * Time.fixedDeltaTime));
}
// Trigger tiles
if (Physics.Raycast(transform.position, Vector3.down, out var hit, 1.5f, tileLayer))
{
if (hit.collider.TryGetComponent<TileViewAdapter>(out var tile))
{
tile.OnPlayerStep();
}
}
}
private bool IsGrounded()
{
return Physics.Raycast(transform.position, Vector3.down, out var hit, 1.15f, tileLayer);
}
private void OnDrawGizmos()
{
if (_target)
{
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, _target.transform.position);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d87e12fdb2c23b238910064019f2ad59

View File

@@ -10,6 +10,7 @@ namespace Infrastructure.Unity
[SerializeField] private TileViewAdapter tilePrefab;
[SerializeField] private ParticleSystem tileBreakVfxPrefab;
[SerializeField] private JumpPadAdapter jumpPadPrefab;
[SerializeField] private TeleporterAdapter teleporterPrefab;
[Header("Settings")]
[SerializeField] private int gridSizeX = 10;
@@ -18,23 +19,27 @@ namespace Infrastructure.Unity
[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)
private TilePool _tilePool;
public void Generate(SoundManager soundManager, List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, CameraController camera)
{
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);
_tilePool = new TilePool(tilePrefab, transform);
GenerateFloor(0, MapPatterns.GenerateSquare(gridSizeX, gridSizeY), soundManager, allTiles, tileViews, camera);
GenerateFloor(1, MapPatterns.GenerateDonut(gridSizeX, Mathf.FloorToInt(gridSizeX / 3f)), soundManager, allTiles, tileViews, camera);
GenerateFloor(2, MapPatterns.GenerateCircle(gridSizeX), soundManager, allTiles, tileViews, camera);
}
private void GenerateFloor(int floorIndex, List<Vector2Int> coordinates, SoundManager soundManager,
List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews)
private void GenerateFloor(int floorIndex, List<Vector2Int> coordinates, SoundManager soundManager,
List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, CameraController camera)
{
var yOffset = -(floorIndex * floorHeightDistance);
var xOffset = gridSizeX / 2f;
@@ -43,46 +48,77 @@ namespace Infrastructure.Unity
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);
CreateTile(pos, $"{floorIndex}_{coord.x}_{coord.y}", floorIndex, soundManager, allTiles, tileViews, camera);
}
if (floorIndex > 0 && jumpPadPrefab)
{
var validSpot = coordinates[Random.Range(0, coordinates.Count)];
var padPos = new Vector3(validSpot.x - xOffset, yOffset + 0.6f, validSpot.y - zOffset);
Instantiate(jumpPadPrefab, padPos, Quaternion.identity, transform);
}
if (floorIndex > 0 && teleporterPrefab && coordinates.Count > 5)
{
// Spawn a pair of teleporters
var indexA = Random.Range(0, coordinates.Count);
int indexB;
do
{
indexB = Random.Range(0, coordinates.Count);
} while (indexB == indexA);
var spotA = coordinates[indexA];
var spotB = coordinates[indexB];
var posA = new Vector3(spotA.x - xOffset, yOffset + 0.6f, spotA.y - zOffset);
var posB = new Vector3(spotB.x - xOffset, yOffset + 0.6f, spotB.y - zOffset);
var telA = Instantiate(teleporterPrefab, posA, Quaternion.identity, transform);
var telB = Instantiate(teleporterPrefab, posB, Quaternion.identity, transform);
telA.Initialize(telB.transform);
telB.Initialize(telA.transform);
}
}
private void CreateTile(Vector3 position, string id, int floorIndex, SoundManager soundManager,
List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews)
private void CreateTile(Vector3 position, string id, int floorIndex, SoundManager soundManager,
List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, CameraController camera)
{
var go = Instantiate(tilePrefab, position, Quaternion.identity, transform);
var go = _tilePool.Get();
go.transform.position = position;
go.transform.rotation = Quaternion.identity;
go.transform.localScale = Vector3.one * 0.95f;
var tileLogic = new Tile(id, floorIndex, decayTime, fallingTime);
go.Initialize(tileLogic);
if (soundManager)
// 15% chance to be Fragile
var isFragile = Random.value < 0.15f;
var type = isFragile ? TileType.Fragile : TileType.Normal;
var tileLogic = new Tile(id, floorIndex, decayTime, fallingTime, type);
go.Initialize(tileLogic, (t) => _tilePool.Return(t));
tileLogic.OnStateChanged += (t) =>
{
tileLogic.OnStateChanged += (t) =>
if (t.CurrentState == TileState.Warning)
{
if (t.CurrentState == TileState.Warning)
soundManager?.PlayTileWarning(position);
}
else if (t.CurrentState == TileState.Falling)
{
soundManager?.PlayTileBreak(position);
if (t.Type == TileType.Fragile)
{
soundManager.PlayTileWarning(position);
camera?.Shake(0.1f, 0.05f);
}
else if (t.CurrentState == TileState.Falling)
if (tileBreakVfxPrefab)
{
soundManager.PlayTileBreak(position);
if (tileBreakVfxPrefab)
{
Instantiate(tileBreakVfxPrefab, position, Quaternion.identity);
}
Instantiate(tileBreakVfxPrefab, position, Quaternion.identity);
}
};
}
}
};
allTiles.Add(tileLogic);
tileViews.Add(id, go);

View File

@@ -14,27 +14,27 @@ namespace Infrastructure.Unity
private float moveSpeed = 8f;
[SerializeField] private float maxVelocityChange = 10f;
[SerializeField] private float snapForce = 15f;
[Header("Controls")]
[SerializeField] private bool useCameraRelativeMovement = true;
[Header("Interaction")]
[SerializeField] private LayerMask tileLayer;
[SerializeField] private float groundCheckDistance = 1.5f;
[Self] [SerializeField] private Rigidbody rb;
[Self][SerializeField] private Rigidbody rb;
private InputSystem_Actions _actions;
private Vector2 _moveInput;
private Transform _camTransform;
public Rigidbody Rigidbody => rb;
public StatusManager Status { get; private set; }
private void OnEnable()
{
_actions.Player.Enable();
_actions.Player.Move.performed += OnMovePerformed;
_actions.Player.Move.canceled += OnMoveCanceled;
}
@@ -43,7 +43,7 @@ namespace Infrastructure.Unity
{
_actions.Player.Move.performed -= OnMovePerformed;
_actions.Player.Move.canceled -= OnMoveCanceled;
_actions.Player.Disable();
}
@@ -51,19 +51,22 @@ namespace Infrastructure.Unity
{
_actions = new InputSystem_Actions();
Status = new StatusManager();
if (Camera.main)
{
_camTransform = Camera.main.transform;
}
rb.freezeRotation = true;
rb.useGravity = true;
// RB gravity is controlled by capabilities
}
private void Update()
{
Status.Tick(Time.deltaTime);
// Apply Status logic
rb.useGravity = !Status.CurrentCapabilities.CanHover;
}
private void FixedUpdate()
@@ -79,7 +82,7 @@ namespace Infrastructure.Unity
var snapAxis = Vector3.zero;
Vector3 desiredDirection;
if (_moveInput.sqrMagnitude < 0.01f)
{
desiredDirection = Vector3.zero;
@@ -100,7 +103,7 @@ namespace Infrastructure.Unity
desiredDirection = new Vector3(_moveInput.x, 0, _moveInput.y).normalized;
}
if (desiredDirection.sqrMagnitude > 0.01f)
{
if (Mathf.Abs(desiredDirection.x) > Mathf.Abs(desiredDirection.z))
@@ -117,11 +120,11 @@ namespace Infrastructure.Unity
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);
if (snapAxis != Vector3.zero)
@@ -134,14 +137,14 @@ namespace Infrastructure.Unity
ApplySnapping(Vector3.forward);
}
}
private void ApplySnapping(Vector3 axis)
{
var currentPos = Vector3.Dot(transform.position, axis);
var targetPos = Mathf.Round(currentPos);
var diff = targetPos - currentPos;
var correction = axis * (diff * snapForce);
rb.AddForce(correction, ForceMode.Acceleration);
}
@@ -149,7 +152,7 @@ namespace Infrastructure.Unity
private void DetectGround()
{
if (!Status.CurrentCapabilities.CanTriggerDecay) return;
if (Physics.Raycast(transform.position, Vector3.down, out var hit, groundCheckDistance, tileLayer))
{
if (hit.collider.TryGetComponent<TileViewAdapter>(out var tileAdapter))

View File

@@ -11,12 +11,14 @@ namespace Infrastructure.Unity
[SerializeField] private PowerUpType type;
[SerializeField] private float duration = 10f;
[SerializeField] private ParticleSystem pickupVfx;
[Self] [SerializeField] private MeshRenderer meshRenderer;
[Self][SerializeField] private MeshRenderer meshRenderer;
private MaterialPropertyBlock _propBlock;
private static readonly int ColorProperty = Shader.PropertyToID("_BaseColor");
public event Action<PowerUpType> OnCollected;
private void Awake()
{
_propBlock = new MaterialPropertyBlock();
@@ -33,6 +35,12 @@ namespace Infrastructure.Unity
case PowerUpType.SpeedBoost:
SetColor(EffectColors.SpeedBoostColor);
break;
case PowerUpType.Hover:
SetColor(EffectColors.HoverColor);
break;
case PowerUpType.TimeSlow:
SetColor(EffectColors.TimeSlowColor);
break;
default:
throw new ArgumentOutOfRangeException();
}
@@ -43,13 +51,14 @@ namespace Infrastructure.Unity
if (other.TryGetComponent<PlayerController>(out var player))
{
ApplyEffect(player);
OnCollected?.Invoke(type);
if (pickupVfx)
{
var vfx = Instantiate(pickupVfx, transform.position, Quaternion.identity);
Destroy(vfx.gameObject, 2f);
}
Destroy(gameObject);
}
}
@@ -64,6 +73,12 @@ namespace Infrastructure.Unity
case PowerUpType.SpeedBoost:
player.Status.AddEffect(new SpeedBoostEffect(duration, 1.5f));
break;
case PowerUpType.Hover:
player.Status.AddEffect(new HoverEffect(duration));
break;
case PowerUpType.TimeSlow:
// Handled globally
break;
default:
throw new ArgumentOutOfRangeException();
}
@@ -72,6 +87,7 @@ namespace Infrastructure.Unity
public void Configure(PowerUpType newType)
{
type = newType;
ConfigureVisuals();
}
private void SetColor(Color color)

View File

@@ -0,0 +1,60 @@
using System;
using UnityEngine;
namespace Infrastructure.Unity
{
public class TeleporterAdapter : MonoBehaviour
{
private Transform _targetDestination;
private TeleporterAdapter _targetAdapter;
private float _cooldownTimer;
public void Initialize(Transform target)
{
_targetDestination = target;
_targetAdapter = target.GetComponent<TeleporterAdapter>();
}
private void Update()
{
if (_cooldownTimer > 0f)
{
_cooldownTimer -= Time.deltaTime;
}
}
public void Lock(float duration)
{
_cooldownTimer = duration;
}
private void OnTriggerEnter(Collider other)
{
if (!_targetDestination) return;
if (_cooldownTimer > 0f) return;
if (other.TryGetComponent<PlayerController>(out var player))
{
var dest = _targetDestination.position;
player.transform.position = dest + Vector3.up * 1.0f;
player.Rigidbody.linearVelocity = Vector3.zero;
if (_targetAdapter)
{
_targetAdapter.Lock(1.0f);
}
Lock(1.0f);
}
}
private void OnDrawGizmos()
{
if (_targetDestination)
{
Gizmos.color = Color.magenta;
Gizmos.DrawLine(transform.position, _targetDestination.position);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 82fa71545d10e6ee187b00ae7fba2eea

View File

@@ -0,0 +1,41 @@
using UnityEngine;
namespace Infrastructure.Unity
{
public static class ThemeManager
{
public struct ThemePalette
{
public Color StableColor;
public Color WarningColor;
public Color BackgroundColor; // Not used yet but good to have
}
public static ThemePalette CurrentTheme { get; set; } = DefaultTheme;
public static ThemePalette DefaultTheme => new ThemePalette
{
StableColor = new Color(0.2f, 0.2f, 0.2f),
WarningColor = new Color(1f, 0.2f, 0.2f)
};
public static ThemePalette CyberpunkTheme => new ThemePalette
{
StableColor = new Color(0.1f, 0.1f, 0.3f), // Dark Blue
WarningColor = new Color(1f, 0f, 1f) // Neon Magenta
};
public static ThemePalette GoldenTheme => new ThemePalette
{
StableColor = new Color(0.3f, 0.3f, 0.1f), // Dark Gold
WarningColor = new Color(1f, 0.8f, 0f) // Gold
};
public static ThemePalette GetTheme(int highScore)
{
if (highScore >= 500) return GoldenTheme;
if (highScore >= 100) return CyberpunkTheme;
return DefaultTheme;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cef34765e5cfa88c1b6e9d72e6c4fdc9

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using UnityEngine;
namespace Infrastructure.Unity
{
public class TilePool
{
private readonly TileViewAdapter _prefab;
private readonly Transform _parent;
private readonly Stack<TileViewAdapter> _pool = new();
public TilePool(TileViewAdapter prefab, Transform parent)
{
_prefab = prefab;
_parent = parent;
}
public TileViewAdapter Get()
{
if (_pool.Count > 0)
{
var item = _pool.Pop();
item.gameObject.SetActive(true);
return item;
}
return Object.Instantiate(_prefab, _parent);
}
public void Return(TileViewAdapter item)
{
item.gameObject.SetActive(false);
_pool.Push(item);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 64b83ab6062e454f5b8765c1d2bfc8a7

View File

@@ -4,7 +4,6 @@ using Core.Domain;
using Core.Ports;
using KBCore.Refs;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Infrastructure.Unity
{
@@ -12,38 +11,58 @@ namespace Infrastructure.Unity
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 Color fragileColor = new Color(0.6f, 0.8f, 1f, 0.5f); // Cyan/Glass
[SerializeField] private float colorSpeed = 2f;
[Self] [SerializeField] private Rigidbody rb;
[Self] [SerializeField] private MeshRenderer meshRenderer;
[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 Action<TileViewAdapter> _onReturnToPool;
private void Awake()
{
_propBlock = new MaterialPropertyBlock();
rb.isKinematic = true;
rb.useGravity = false;
}
public void Initialize(Tile tile)
{
Initialize(tile, null);
}
public void Initialize(Tile tile, Action<TileViewAdapter> onReturnToPool = null)
{
_linkedTile = tile;
TileId = tile.Id;
SetColor(stableColor);
_onReturnToPool = onReturnToPool;
// Reset physics
rb.isKinematic = true;
rb.useGravity = false;
if (_linkedTile.Type == TileType.Fragile)
{
SetColor(fragileColor);
}
else
{
SetColor(ThemeManager.CurrentTheme.StableColor);
}
_linkedTile.OnStateChanged += OnTileStateChanged;
}
@@ -57,18 +76,16 @@ namespace Infrastructure.Unity
switch (state)
{
case TileState.Stable:
StartCoroutine(AnimateColor(stableColor));
StartCoroutine(AnimateColor(ThemeManager.CurrentTheme.StableColor));
break;
case TileState.Warning:
StartCoroutine(AnimateColor(warningColor));
StartCoroutine(AnimateColor(ThemeManager.CurrentTheme.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);
@@ -79,8 +96,6 @@ namespace Infrastructure.Unity
{
rb.isKinematic = false;
rb.useGravity = true;
// rb.AddTorque(Random.insideUnitSphere * 5f, ForceMode.Impulse);
}
public void Dispose()
@@ -124,8 +139,15 @@ namespace Infrastructure.Unity
transform.localScale = Vector3.Lerp(startScale, Vector3.zero, t);
yield return null;
}
Destroy(gameObject);
if (_onReturnToPool != null)
{
_onReturnToPool(this);
}
else
{
Destroy(gameObject);
}
}
private void OnDestroy()