Add NPC and Power-Up features with associated prefabs and effects

This commit is contained in:
2025-12-12 23:05:40 +01:00
parent 1cfcd09928
commit ee7a2fb4cb
24 changed files with 1051 additions and 5 deletions

View File

@@ -17,6 +17,7 @@ namespace Infrastructure.Unity
[SerializeField] private DeathPlaneAdapter deathPlanePrefab;
[SerializeField] private SoundManager soundManager;
[SerializeField] private CameraController cameraController;
[SerializeField] private NpcController npcPrefab;
[Header("Level Generation")]
[SerializeField] private int floorsCount = 3;
@@ -30,7 +31,10 @@ namespace Infrastructure.Unity
[Header("Settings")]
[SerializeField] private float restartTime = 3f;
[Header("Power Ups")]
[SerializeField] private PowerUpViewAdapter lightFootedPrefab;
[SerializeField] private PowerUpViewAdapter speedBoostPrefab;
private readonly List<Tile> _allTiles = new();
private readonly Dictionary<string, TileViewAdapter> _tileViews = new();
@@ -93,6 +97,9 @@ namespace Infrastructure.Unity
}
var dt = Time.deltaTime;
if (_isGameRunning) _gameSession.Tick(dt);
for (var i = _allTiles.Count - 1; i >= 0; i--)
{
_allTiles[i].Tick(dt);
@@ -180,6 +187,8 @@ namespace Infrastructure.Unity
_gameSession.OnOrbSpawned += SpawnVisualOrb;
_gameSession.OnOrbReset += HandleOrbReset;
_gameSession.OnGameOver += HandleGameOver;
_gameSession.OnSpawnNpc += SpawnNpc;
_gameSession.OnSpawnPowerUp += SpawnPowerUp;
if (!soundManager) return;
@@ -191,6 +200,16 @@ 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);
soundManager.PlayNpcSpawn();
}
private void StartGameSequence()
{
_isGameRunning = true;
@@ -211,5 +230,22 @@ namespace Infrastructure.Unity
_gameSession.StartGame();
}
private void SpawnPowerUp(PowerUpType type, string tileId)
{
if (!_tileViews.TryGetValue(tileId, out var tileView)) return;
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(prefabToSpawn, spawnPos, Quaternion.identity);
instance.Configure(type);
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using KBCore.Refs;
using UnityEngine;
using UnityEngine.AI;
using Random = UnityEngine.Random;
namespace Infrastructure.Unity
{
[RequireComponent(typeof(Rigidbody))]
public class NpcController : MonoBehaviour
{
[SerializeField] private float moveSpeed = 6f;
[SerializeField] private float changeDirInterval = 1f;
[SerializeField] private LayerMask tileLayer;
[Self] [SerializeField] private Rigidbody rb;
private Vector3 _currentDir;
private float _timer;
private void Awake()
{
PickNewDirection();
}
private void Update()
{
_timer += Time.deltaTime;
if (_timer >= changeDirInterval)
{
_timer = 0;
PickNewDirection();
}
}
private void FixedUpdate()
{
if (IsGrounded()) rb.MovePosition(rb.position + _currentDir * (moveSpeed * Time.fixedDeltaTime));
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 PickNewDirection()
{
var rand = Random.Range(0, 4);
switch (rand)
{
case 0: _currentDir = Vector3.forward; break;
case 1: _currentDir = Vector3.back; break;
case 2: _currentDir = Vector3.left; break;
case 3: _currentDir = Vector3.right; break;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d8629598f36246c484f046e5ebd69266
timeCreated: 1765575497

View File

@@ -0,0 +1,84 @@
using System;
using Core.Domain;
using Core.Domain.Status.Effects;
using KBCore.Refs;
using UnityEngine;
namespace Infrastructure.Unity
{
public class PowerUpViewAdapter : MonoBehaviour
{
[SerializeField] private PowerUpType type;
[SerializeField] private float duration = 10f;
[SerializeField] private ParticleSystem pickupVfx;
[Self] [SerializeField] private MeshRenderer meshRenderer;
private MaterialPropertyBlock _propBlock;
private static readonly int ColorProperty = Shader.PropertyToID("_BaseColor");
private void Awake()
{
_propBlock = new MaterialPropertyBlock();
ConfigureVisuals();
}
private void ConfigureVisuals()
{
switch (type)
{
case PowerUpType.LightFooted:
SetColor(EffectColors.LightFootedColor);
break;
case PowerUpType.SpeedBoost:
SetColor(EffectColors.SpeedBoostColor);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private void OnTriggerEnter(Collider other)
{
if (other.TryGetComponent<PlayerController>(out var player))
{
ApplyEffect(player);
if (pickupVfx)
{
var vfx = Instantiate(pickupVfx, transform.position, Quaternion.identity);
Destroy(vfx.gameObject, 2f);
}
Destroy(gameObject);
}
}
private void ApplyEffect(PlayerController player)
{
switch (type)
{
case PowerUpType.LightFooted:
player.Status.AddEffect(new LightFootedEffect(duration));
break;
case PowerUpType.SpeedBoost:
player.Status.AddEffect(new SpeedBoostEffect(duration, 1.5f));
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public void Configure(PowerUpType newType)
{
type = newType;
}
private void SetColor(Color color)
{
meshRenderer.GetPropertyBlock(_propBlock);
_propBlock.SetColor(ColorProperty, color);
meshRenderer.SetPropertyBlock(_propBlock);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5886e2d0e6414ce98bf68f8e5ac887fc
timeCreated: 1765576409

View File

@@ -12,14 +12,14 @@ namespace Infrastructure.Unity
[Header("Sound Effects")]
[SerializeField] private AudioClip startClip;
[SerializeField] private AudioClip scoreClip;
[SerializeField] private AudioClip gameOverClip;
[SerializeField] private AudioClip tileWarningClip;
[SerializeField] private AudioClip tileBreakClip;
[SerializeField] private AudioClip npcSpawnClip;
[Self] [SerializeField] private AudioSource musicSource;
[Self] [SerializeField] private AudioSource sfxSource;
[SerializeField] private AudioSource musicSource;
[SerializeField] private AudioSource sfxSource;
private void Awake()
{
@@ -43,6 +43,8 @@ namespace Infrastructure.Unity
public void PlayGameStart() => PlaySfx(startClip);
public void PlayScore() => PlaySfx(scoreClip);
public void PlayGameOver() => PlaySfx(gameOverClip);
public void PlayNpcSpawn() => PlaySfx(npcSpawnClip);
public void PlayTileBreak(Vector3 position)
{