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

@@ -0,0 +1,162 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6956164580015912564
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2158095965342707446}
- component: {fileID: 522917209739125732}
- component: {fileID: 54217236340769722}
- component: {fileID: 121674684165614314}
- component: {fileID: 283610869708690177}
- component: {fileID: 4496988857626767934}
m_Layer: 0
m_Name: HunterNpc
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2158095965342707446
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6956164580015912564}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &522917209739125732
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6956164580015912564}
m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &54217236340769722
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6956164580015912564}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: a64e346b67480caceae337f37543db27, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!136 &121674684165614314
CapsuleCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6956164580015912564}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5
m_Height: 2
m_Direction: 1
m_Center: {x: 0, y: 0, z: 0}
--- !u!54 &283610869708690177
Rigidbody:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6956164580015912564}
serializedVersion: 5
m_Mass: 1
m_LinearDamping: 0
m_AngularDamping: 0.05
m_CenterOfMass: {x: 0, y: 0, z: 0}
m_InertiaTensor: {x: 1, y: 1, z: 1}
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_ImplicitCom: 1
m_ImplicitTensor: 1
m_UseGravity: 1
m_IsKinematic: 0
m_Interpolate: 0
m_Constraints: 0
m_CollisionDetection: 0
--- !u!114 &4496988857626767934
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6956164580015912564}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d87e12fdb2c23b238910064019f2ad59, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.HunterNpcController
moveSpeed: 7
tileLayer:
serializedVersion: 2
m_Bits: 8
rb: {fileID: 283610869708690177}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 4ce47d492ed71cb5b9390762eab7a749
guid: ab4e193839fef9a2189f27360914c044
PrefabImporter:
externalObjects: {}
userData:

View File

@@ -1,6 +1,6 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &7219310182397398916
--- !u!1 &6659026961635312632
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -8,25 +8,25 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4070461624121405295}
- component: {fileID: 930667067315408562}
- component: {fileID: 2173926958931993968}
- component: {fileID: 5033597944775254142}
- component: {fileID: 2639250404559520579}
- component: {fileID: 663514258571720717}
- component: {fileID: 8432450954479424108}
- component: {fileID: 7819803615826365176}
- component: {fileID: 6615024751381665233}
- component: {fileID: 7381336953128067686}
m_Layer: 0
m_Name: Light footed
m_Name: PowerUp
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4070461624121405295
--- !u!4 &663514258571720717
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_GameObject: {fileID: 6659026961635312632}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
@@ -35,21 +35,21 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &930667067315408562
--- !u!33 &8432450954479424108
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &2173926958931993968
m_GameObject: {fileID: 6659026961635312632}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &7819803615826365176
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_GameObject: {fileID: 6659026961635312632}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
@@ -92,13 +92,13 @@ MeshRenderer:
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!136 &5033597944775254142
CapsuleCollider:
--- !u!65 &6615024751381665233
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_GameObject: {fileID: 6659026961635312632}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
@@ -110,24 +110,22 @@ CapsuleCollider:
m_IsTrigger: 1
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5
m_Height: 2
m_Direction: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!114 &2639250404559520579
--- !u!114 &7381336953128067686
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_GameObject: {fileID: 6659026961635312632}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5886e2d0e6414ce98bf68f8e5ac887fc, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.PowerUpViewAdapter
type: 0
duration: 10
duration: 5
pickupVfx: {fileID: 0}
meshRenderer: {fileID: 2173926958931993968}
meshRenderer: {fileID: 7819803615826365176}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 32d69de3e08c76e63a4b5086b87b12df
guid: 8b540be4548e610709c2f7eccf8bf9c6
PrefabImporter:
externalObjects: {}
userData:

View File

@@ -1,6 +1,6 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &7219310182397398916
--- !u!1 &2198048708000581807
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -8,48 +8,48 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4070461624121405295}
- component: {fileID: 930667067315408562}
- component: {fileID: 2173926958931993968}
- component: {fileID: 5033597944775254142}
- component: {fileID: 2639250404559520579}
- component: {fileID: 7276685985893193207}
- component: {fileID: 6152181000324460277}
- component: {fileID: 1559715898324095926}
- component: {fileID: 6093691026861389653}
- component: {fileID: 4601941687390792571}
m_Layer: 0
m_Name: Speed Boost
m_Name: Teleporter pad
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4070461624121405295
--- !u!4 &7276685985893193207
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_GameObject: {fileID: 2198048708000581807}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.5, y: 0.5, z: 0.5}
m_LocalScale: {x: 1, y: 0.1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &930667067315408562
--- !u!33 &6152181000324460277
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &2173926958931993968
m_GameObject: {fileID: 2198048708000581807}
m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &1559715898324095926
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_GameObject: {fileID: 2198048708000581807}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
@@ -68,7 +68,7 @@ MeshRenderer:
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: aece89a4ea685f6969a31191b980f70b, type: 2}
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
@@ -92,13 +92,13 @@ MeshRenderer:
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!136 &5033597944775254142
--- !u!136 &6093691026861389653
CapsuleCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_GameObject: {fileID: 2198048708000581807}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
@@ -111,23 +111,19 @@ CapsuleCollider:
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5
m_Radius: 0.5000001
m_Height: 2
m_Direction: 1
m_Center: {x: 0, y: 0, z: 0}
--- !u!114 &2639250404559520579
m_Center: {x: 0.000000059604645, y: 0, z: -0.00000008940697}
--- !u!114 &4601941687390792571
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7219310182397398916}
m_GameObject: {fileID: 2198048708000581807}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5886e2d0e6414ce98bf68f8e5ac887fc, type: 3}
m_Script: {fileID: 11500000, guid: 82fa71545d10e6ee187b00ae7fba2eea, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.PowerUpViewAdapter
type: 1
duration: 10
pickupVfx: {fileID: 0}
meshRenderer: {fileID: 2173926958931993968}
m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.TeleporterAdapter

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 53f1de555c523511e9aaa1dee06fdf79
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -155,6 +155,7 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.TileViewAdapter
stableColor: {r: 0.08627451, g: 0.12941177, b: 0.24313726, a: 1}
warningColor: {r: 1, g: 0.18039216, b: 0.3882353, a: 1}
fragileColor: {r: 0.08627451, g: 0.12941177, b: 0.24313726, a: 0.5}
colorSpeed: 2
rb: {fileID: 6985771395868845393}
meshRenderer: {fileID: 1516947233778832648}

View File

@@ -633,6 +633,7 @@ MonoBehaviour:
soundManager: {fileID: 1341134052}
cameraController: {fileID: 1265730430}
npcPrefab: {fileID: 6083523108754401876, guid: 4b3d84858334857368bde30df360ae3e, type: 3}
hunterNpcPrefab: {fileID: 4496988857626767934, guid: ab4e193839fef9a2189f27360914c044, type: 3}
floorsCount: 3
floorHeightDistance: 15
scoreText: {fileID: 412275999}
@@ -640,8 +641,7 @@ MonoBehaviour:
gameOverUi: {fileID: 87831902}
startScreenUi: {fileID: 1763855010}
restartTime: 3
lightFootedPrefab: {fileID: 2639250404559520579, guid: 4ce47d492ed71cb5b9390762eab7a749, type: 3}
speedBoostPrefab: {fileID: 2639250404559520579, guid: 32d69de3e08c76e63a4b5086b87b12df, type: 3}
powerUpPrefab: {fileID: 7381336953128067686, guid: 8b540be4548e610709c2f7eccf8bf9c6, type: 3}
--- !u!4 &453022421
Transform:
m_ObjectHideFlags: 0
@@ -672,6 +672,7 @@ MonoBehaviour:
tilePrefab: {fileID: 2142595510769725438, guid: a84ad84e96942d35c824885a7c08a844, type: 3}
tileBreakVfxPrefab: {fileID: 3395603910160708684, guid: 7c5c893ba21562b2aad6bffb1887f4b1, type: 3}
jumpPadPrefab: {fileID: 3258547662887829175, guid: e1d1bd44370c9986ebd4bb7730430a12, type: 3}
teleporterPrefab: {fileID: 4601941687390792571, guid: 53f1de555c523511e9aaa1dee06fdf79, type: 3}
gridSizeX: 20
gridSizeY: 20
floorsCount: 3

View File

@@ -9,11 +9,13 @@ namespace Core.Domain
private const string HighScoreKey = "HighScore";
private const float NpcSpawnTime = 4f;
private const float PowerUpSpawnInterval = 25f;
public int Score { get; private set; }
public int HighScore { get; private set; }
public bool IsGameOver { get; private set; }
public float TimeDilation { get; private set; } = 1.0f;
public event Action<int> OnScoreChanged;
public event Action<string> OnOrbSpawned;
public event Action OnOrbReset;
@@ -29,14 +31,21 @@ namespace Core.Domain
private bool _npcSpawned;
private float _powerUpTimer;
private float _timeSlowTimer;
// Combo System
private float _lastOrbTime;
public int ComboMultiplier { get; private set; } = 1;
public event Action<int> OnComboUpdated;
public GameSession(List<Tile> tiles, IPersistenceService persistenceService)
{
_tiles = tiles;
_persistenceService = persistenceService;
Score = 0;
IsGameOver = false;
HighScore = _persistenceService.Load(HighScoreKey, 0);
}
@@ -45,56 +54,88 @@ namespace Core.Domain
_timeSinceStart = 0f;
_powerUpTimer = 0f;
_npcSpawned = false;
TimeDilation = 1.0f;
ComboMultiplier = 1;
_lastOrbTime = -999f;
SpawnNextOrb();
}
public void Tick(float deltaTime)
{
if (IsGameOver) return;
_timeSinceStart += deltaTime;
if (!_npcSpawned && _timeSinceStart >= NpcSpawnTime)
{
_npcSpawned = true;
OnSpawnNpc?.Invoke();
}
_powerUpTimer += deltaTime;
if (_powerUpTimer >= PowerUpSpawnInterval)
{
_powerUpTimer = 0f;
SpawnRandomPowerUp();
}
if (_timeSlowTimer > 0)
{
_timeSlowTimer -= deltaTime;
if (_timeSlowTimer <= 0)
{
TimeDilation = 1.0f;
}
}
}
public void ActivateTimeSlow(float duration)
{
_timeSlowTimer = duration;
TimeDilation = 0.3f; // Slow down to 30%
}
public void OrbCollected()
{
if (IsGameOver) return;
Score += 10;
// Combo Check (2 second window)
if (_timeSinceStart - _lastOrbTime < 2.0f)
{
ComboMultiplier++;
}
else
{
ComboMultiplier = 1;
}
_lastOrbTime = _timeSinceStart;
OnComboUpdated?.Invoke(ComboMultiplier);
Score += 10 * ComboMultiplier;
OnScoreChanged?.Invoke(Score);
SpawnNextOrb();
}
private void SpawnNextOrb()
{
var validTiles = _tiles.FindAll(t =>
t.CurrentState == TileState.Stable &&
{
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);
}
@@ -102,39 +143,56 @@ namespace Core.Domain
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();
}
private void SpawnRandomPowerUp()
{
var validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable);
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) return;
var tile = validTiles[_rng.Next(validTiles.Count)];
var type = _rng.Next(0, 2) == 0 ? PowerUpType.LightFooted : PowerUpType.SpeedBoost;
var rand = _rng.Next(0, 4);
var type = PowerUpType.LightFooted;
switch (rand)
{
case 0: type = PowerUpType.LightFooted; break;
case 1: type = PowerUpType.SpeedBoost; break;
case 2: type = PowerUpType.Hover; break;
case 3: type = PowerUpType.TimeSlow; break;
}
OnSpawnPowerUp?.Invoke(type, tile.Id);
}
}

View File

@@ -4,5 +4,7 @@ namespace Core.Domain
{
LightFooted,
SpeedBoost,
Hover,
TimeSlow
}
}

View File

@@ -6,5 +6,7 @@ namespace Core.Domain.Status.Effects
{
public static readonly Color LightFootedColor = new Color(0.8f, 0.8f, 0.8f);
public static readonly Color SpeedBoostColor = new Color(1f, 0.5f, 0f);
public static readonly Color HoverColor = new Color(0.5f, 1f, 0.5f); // Green
public static readonly Color TimeSlowColor = new Color(0.2f, 0.2f, 1f); // Blue
}
}

View File

@@ -0,0 +1,32 @@
namespace Core.Domain.Status.Effects
{
public class HoverEffect : IStatusEffect
{
private float _duration;
public bool IsExpired => _duration <= 0;
public HoverEffect(float duration)
{
_duration = duration;
}
public void Tick(float deltaTime)
{
_duration -= deltaTime;
}
public void ModifyCapabilities(ref PlayerCapabilities caps)
{
caps.CanHover = true;
}
public void OnApply()
{
}
public void OnRemove()
{
}
}
}

View File

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

View File

@@ -4,11 +4,13 @@ namespace Core.Domain.Status
{
public bool CanTriggerDecay;
public float SpeedMultiplier;
public bool CanHover;
public static PlayerCapabilities Default => new PlayerCapabilities
{
CanTriggerDecay = true,
SpeedMultiplier = 1f
SpeedMultiplier = 1f,
CanHover = false
};
}
}

View File

@@ -6,21 +6,23 @@ namespace Core.Domain
{
public string Id { get; }
public int Floor { get; }
public TileType Type { 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)
public Tile(string id, int floor, float warningDuration, float fallingDuration, TileType type = TileType.Normal)
{
Id = id;
Floor = floor;
Type = type;
_warningDuration = warningDuration;
_fallingDuration = fallingDuration;
CurrentState = TileState.Stable;
@@ -30,16 +32,23 @@ namespace Core.Domain
{
if (CurrentState == TileState.Stable)
{
TransitionTo(TileState.Warning);
if (Type == TileType.Fragile)
{
TransitionTo(TileState.Falling);
}
else
{
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);

View File

@@ -0,0 +1,8 @@
namespace Core.Domain
{
public enum TileType
{
Normal,
Fragile
}
}

View File

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

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()