Second commit

This commit is contained in:
ThomasCharester committed 2026-09-20 15:26:03 +03:00
commit 36840f3ad4
999 files changed
+61364

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f948a9778c6c4fd78629a4618a7e5302
timeCreated: 1785829267
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 601de2d2ea58417f8b5a46faf0b11196
timeCreated: 1785923690
+31
View File
@@ -0,0 +1,31 @@
using R3;
using UnityEngine;
namespace Code.D.Cleaning.CleanTools
{
public abstract class CleanTool
{
protected float DurabilityUsageStep;
protected Texture2D BrushTexture;
protected float CleanRadius;
public abstract CleanToolType ToolType { get; }
protected CleanTool(float durabilityUsageStep, float cleanRadius, Texture2D brushTexture)
{
IsDirty.Value = false;
DurabilityUsageStep = durabilityUsageStep;
CleanRadius = cleanRadius;
BrushTexture = brushTexture;
}
public ReactiveProperty<bool> IsDirty { get; } = new();
public ReactiveProperty<float> CurrentDirtiness { get; }
= new(0f);
public abstract TryapkaUsageInfo Use();
public abstract void Clean();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cc959003316e4db99a7f190b1b431298
timeCreated: 1785939044
@@ -0,0 +1,15 @@
using Code.Infrastructure.Generators;
using UnityEngine;
namespace Code.D.Cleaning.CleanTools
{
[Config("Cleaning/CleanToolConfig", multiple: true)]
[CreateAssetMenu(fileName = "CleanToolConfig", menuName = "Config/CleanToolConfig")]
public class CleanToolConfig : ScriptableObject
{
public float DurabilityUsageStep;
public Texture2D BrushTexture;
public float CleanRadius;
public CleanToolType ToolType;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 98cb6ddb562747d8b4549287a391a98e
timeCreated: 1786032650
@@ -0,0 +1,9 @@
using UnityEngine;
namespace Code.D.Cleaning.CleanTools
{
public abstract class CleanToolInteractable : MonoBehaviour
{
public abstract void Use(CleanTool tool);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1d17365593864a6e9c5cab8971c33c75
timeCreated: 1785938942
@@ -0,0 +1,7 @@
namespace Code.D.Cleaning.CleanTools
{
public enum CleanToolType
{
Tryapka
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 56a8b3bb2a354e59a3a9a3d6e83c8fe0
timeCreated: 1786028280
@@ -0,0 +1,14 @@
using System;
using UnityEngine;
namespace Code.D.Cleaning.CleanTools
{
public class DirtyToolCleaner : CleanToolInteractable
{
public override void Use(CleanTool tool)
{
tool.Clean();
Debug.Log("Tool cleaned");
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 90e6386fcc8249afad82234df1fcf3d4
timeCreated: 1785923693
+11
View File
@@ -0,0 +1,11 @@
using UnityEngine;
namespace Code.D.Cleaning.CleanTools
{
public record ToolUsageInfo
{
public float Dirtiness;
public bool IsDirty;
public Texture BrushTexture;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0723fb7c41244d8c8b9272d09977dc8f
timeCreated: 1785939283
+44
View File
@@ -0,0 +1,44 @@
using UnityEngine;
namespace Code.D.Cleaning.CleanTools
{
public class Tryapka : CleanTool
{
public Tryapka(float durabilityUsageStep, float cleanRadius, Texture2D brushTexture) : base(durabilityUsageStep, cleanRadius, brushTexture)
{
}
public override CleanToolType ToolType => CleanToolType.Tryapka;
public override TryapkaUsageInfo Use()
{
if (!IsDirty.CurrentValue)
{
CurrentDirtiness.Value += DurabilityUsageStep;
if (CurrentDirtiness.Value >= 1) IsDirty.Value = true;
}
return new(CleanRadius, CurrentDirtiness.Value, IsDirty.CurrentValue, BrushTexture);
}
public override void Clean()
{
CurrentDirtiness.Value = 0;
IsDirty.Value = false;
}
}
public record TryapkaUsageInfo : ToolUsageInfo
{
public float Radius;
public TryapkaUsageInfo(float radius, float dirtiness, bool isDirty, Texture brush)
{
Radius = radius;
Dirtiness = dirtiness;
IsDirty = isDirty;
BrushTexture = brush;
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5da5c20e36c0403691a65f6e0317c129
timeCreated: 1785923693
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6b6718c9ff8a4963941f9f966f49f795
timeCreated: 1785923035
+11
View File
@@ -0,0 +1,11 @@
using R3;
using UnityEngine;
namespace Code.D.Cleaning
{
public class Cleanable : MonoBehaviour
{
public ReactiveProperty<bool> Cleaned { get; } = new();
public ReactiveProperty<float> CleanProgress { get; } = new();
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 219620ccf64f42d7a64d27a3a152f961
timeCreated: 1785923122
+35
View File
@@ -0,0 +1,35 @@
using UnityEngine;
namespace Code.D.Cleaning
{
public class CleanableAsset : Cleanable
{
private Collider _dirtyCollider;
private static readonly int MaskMap = Shader.PropertyToID("_Mask");
[SerializeField] private float cleanCountPerFrame;
private Material _renderMaterial;
private void Awake()
{
_dirtyCollider = GetComponent<Collider>();
_renderMaterial = GetComponent<MeshRenderer>().material;
}
private void AssumedAsCleaned()
{
Cleaned.Value = true;
_dirtyCollider.enabled = false;
_renderMaterial.SetTexture(MaskMap, Texture2D.blackTexture);
}
public void Clean()
{
CleanProgress.Value += cleanCountPerFrame;
if(CleanProgress.CurrentValue >= 100)
AssumedAsCleaned();
Debug.Log($"{name} прогресс очистки: {CleanProgress.CurrentValue}");
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b184056d80d64c5d9f9cdb03cb00efc7
timeCreated: 1785923122
+490
View File
@@ -0,0 +1,490 @@
using System.Collections.Generic;
using Code.D.Cleaning.CleanTools;
using Cysharp.Threading.Tasks;
using TinyGoose.Tremble;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Code.D.Cleaning
{
[BrushEntity("CleanableSurface", layer: "Cleanable")]
public class CleanableSurface : Cleanable, IOnImportFromMapEntity
{
private static readonly int MaskMap = Shader.PropertyToID("_Mask");
private static readonly int DrawPosition = Shader.PropertyToID("_DrawPosition");
private static readonly int DrawRadius = Shader.PropertyToID("_DrawRadius");
private static readonly int ToolDirtiness = Shader.PropertyToID("_ToolDirtiness");
private static readonly int BrushTex = Shader.PropertyToID("_BrushTex");
private static readonly int DrawAngleX = Shader.PropertyToID("_DrawAngleX");
private static readonly int DrawAngleY = Shader.PropertyToID("_DrawAngleY");
private const int CalcResolution = 64;
[SerializeField] private float cleanedThreshold = 0.7f;
private RenderTexture _maskRT;
private RenderTexture _calcRT;
private Material _brushMaterial;
private Material _renderMaterial;
private bool _onProcess;
private UniTask _calculationOperation;
private void Awake()
{
gameObject.tag = "CleanableSurface";
_maskRT = new RenderTexture(1024, 1024, 0, RenderTextureFormat.ARGB32);
_maskRT.Create();
Graphics.Blit(Texture2D.whiteTexture, _maskRT);
_calcRT = new RenderTexture(CalcResolution, CalcResolution, 0, RenderTextureFormat.ARGB32);
_calcRT.Create();
_brushMaterial = new Material(Shader.Find("Unlit/BrushPainter"));
if (!TryGetComponent<MeshRenderer>(out var renderer)) return;
_renderMaterial = renderer.material;
_renderMaterial.SetTexture(MaskMap, _maskRT);
}
/// <summary>
/// Нейросеть написала и этот метод, слишком много математики мне он не нравится. Он заставляет игру лагать. Нужно сделать проще.
/// Хотябы комментарии оставлены человеком.
/// Я профнепригоден.
/// </summary>
/// <param name="hit"></param>
/// <param name="tryapkaUsageInfo"></param>
/// <param name="playerCamera"></param>
public void Clean(RaycastHit hit, TryapkaUsageInfo tryapkaUsageInfo, Transform playerCamera)
{
if (Cleaned.CurrentValue) return;
Vector2 uv = hit.textureCoord;
// Логика вычисления направления кисти
Vector3 dirToCamera = playerCamera.position - hit.point;
Vector3 projDir = Vector3.ProjectOnPlane(dirToCamera, hit.normal);
if (projDir.sqrMagnitude < 0.0001f)
{
projDir = Vector3.ProjectOnPlane(playerCamera.up, hit.normal);
}
projDir.Normalize();
// Логика определения U V или W
Vector3 localDir = hit.transform.InverseTransformDirection(projDir);
Vector3 localNormal = hit.transform.InverseTransformDirection(hit.normal).normalized;
ComputeTangentBasis(localNormal, out var axisU, out var axisV);
// Определяем синус и косинус поворота
float u = Vector3.Dot(localDir, axisU);
float v = Vector3.Dot(localDir, axisV);
_brushMaterial.SetVector(DrawPosition, uv);
_brushMaterial.SetFloat(DrawRadius, tryapkaUsageInfo.Radius);
_brushMaterial.SetFloat(DrawAngleX, v);
_brushMaterial.SetFloat(DrawAngleY, u);
_brushMaterial.SetFloat(ToolDirtiness, tryapkaUsageInfo.Dirtiness);
_brushMaterial.SetTexture(BrushTex, tryapkaUsageInfo.BrushTexture);
Graphics.Blit(Texture2D.blackTexture, _maskRT, _brushMaterial, 0);
if (_calculationOperation.Status != UniTaskStatus.Pending)
{
_calculationOperation = CalculateCleanPercentage();
}
}
private static void ComputeTangentBasis(Vector3 normal, out Vector3 axisU, out Vector3 axisV)
{
if (Mathf.Abs(normal.y) > FlatNormalThreshold)
{
axisU = Vector3.right;
axisV = Vector3.forward;
}
else
{
axisU = Vector3.Cross(Vector3.up, normal).normalized;
axisV = Vector3.up;
}
}
private async UniTask CalculateCleanPercentage()
{
if (!_maskRT || _onProcess) return;
_onProcess = true;
Graphics.Blit(_maskRT, _calcRT);
var destroyToken = this.GetCancellationTokenOnDestroy();
var request = AsyncGPUReadback.Request(_calcRT, 0, TextureFormat.RGBA32);
await request.ToUniTask(cancellationToken: destroyToken);
if (request.hasError)
{
_onProcess = false;
return;
}
NativeArray<byte> data = request.GetData<byte>();
int totalBytes = data.Length;
long totalBrightness = 0;
for (var i = 0; i < totalBytes; i += 4)
{
totalBrightness += data[i];
}
int pixelCount = totalBytes / 4;
float maxPossibleBrightness = pixelCount * 255f;
float progress = 1.0f - (totalBrightness / maxPossibleBrightness);
if (Mathf.Abs(progress - CleanProgress.CurrentValue) > 0.01f)
{
CleanProgress.Value = progress;
}
if (CleanProgress.CurrentValue >= cleanedThreshold && !Cleaned.CurrentValue)
{
AssumedAsCleaned();
}
_onProcess = false;
}
private void AssumedAsCleaned()
{
Cleaned.Value = true;
FinishClean();
}
private void FinishClean()
{
Graphics.Blit(Texture2D.blackTexture, _maskRT);
}
private void OnDestroy()
{
if (_maskRT) _maskRT.Release();
if (_calcRT) _calcRT.Release();
if (_brushMaterial) Destroy(_brushMaterial);
if (_renderMaterial) Destroy(_renderMaterial);
}
public void OnImportFromMapEntity(MapBsp mapBsp, BspEntity entity)
{
Debug.Log("Found a brush!");
var meshFilter = gameObject.GetComponent<MeshFilter>();
if (meshFilter == null)
{
Debug.LogError($"[CleanableSurface] MeshFilter отсутствует на {gameObject.name}");
return;
}
var sourceMesh = meshFilter.sharedMesh;
if (sourceMesh == null)
{
Debug.LogError($"[CleanableSurface] sharedMesh == null на {gameObject.name}");
return;
}
if (!sourceMesh.isReadable)
{
Debug.LogError($"[CleanableSurface] Меш '{sourceMesh.name}' не readable, не могу построить UV");
return;
}
if (sourceMesh.vertexCount == 0 || sourceMesh.triangles.Length == 0)
{
Debug.LogError($"[CleanableSurface] Меш '{sourceMesh.name}' пустой");
return;
}
try
{
BuildLayeredUVInPlace(sourceMesh);
}
catch (System.Exception ex)
{
Debug.LogError($"[CleanableSurface] Исключение при построении UV для {gameObject.name}: {ex}");
return;
}
var meshCollider = gameObject.GetComponent<MeshCollider>();
if (meshCollider != null)
meshCollider.sharedMesh = sourceMesh;
#if UNITY_EDITOR
var existingFlags = GameObjectUtility.GetStaticEditorFlags(gameObject);
GameObjectUtility.SetStaticEditorFlags(gameObject, existingFlags | StaticEditorFlags.ContributeGI);
#endif
Debug.Log(
$"[CleanableSurface] UV построен для {gameObject.name}: verts={sourceMesh.vertexCount}, tris={sourceMesh.triangles.Length / 3}");
}
private const float MaskUvPadding = 0.08f;
private const float WeldEpsilon = 0.0005f;
private const float CoplanarDotThreshold = 0.999f;
private const float FlatNormalThreshold = 0.999f;
/// <summary>
/// Этот большой и сложный метод написан нейросетью Claude Sonnet 5 Medium. Я профнепригоден
/// </summary>
/// <param name="mesh"></param>
private static void BuildLayeredUVInPlace(Mesh mesh)
{
var srcVerts = mesh.vertices;
var srcNormals = mesh.normals;
var srcUvTile = mesh.uv;
bool hasNormals = srcNormals.Length == srcVerts.Length;
bool hasUvTile = srcUvTile.Length == srcVerts.Length;
int subMeshCount = mesh.subMeshCount;
var posToId = new Dictionary<Vector3Int, int>();
int CanonicalId(Vector3 p)
{
var key = new Vector3Int(
Mathf.RoundToInt(p.x / WeldEpsilon),
Mathf.RoundToInt(p.y / WeldEpsilon),
Mathf.RoundToInt(p.z / WeldEpsilon));
if (posToId.TryGetValue(key, out var id)) return id;
id = posToId.Count;
posToId[key] = id;
return id;
}
var triSubMesh = new List<int>();
var triI0 = new List<int>();
var triI1 = new List<int>();
var triI2 = new List<int>();
var triCanonA = new List<int>();
var triCanonB = new List<int>();
var triCanonC = new List<int>();
var triNormal = new List<Vector3>();
for (int s = 0; s < subMeshCount; s++)
{
var tris = mesh.GetTriangles(s);
for (int t = 0; t < tris.Length; t += 3)
{
int i0 = tris[t], i1 = tris[t + 1], i2 = tris[t + 2];
Vector3 p0 = srcVerts[i0], p1 = srcVerts[i1], p2 = srcVerts[i2];
Vector3 n = Vector3.Cross(p1 - p0, p2 - p0);
n = n.sqrMagnitude > 1e-10f ? n.normalized : Vector3.up;
triSubMesh.Add(s);
triI0.Add(i0);
triI1.Add(i1);
triI2.Add(i2);
triCanonA.Add(CanonicalId(p0));
triCanonB.Add(CanonicalId(p1));
triCanonC.Add(CanonicalId(p2));
triNormal.Add(n);
}
}
int triCount = triSubMesh.Count;
var parent = new int[triCount];
for (int i = 0; i < triCount; i++) parent[i] = i;
int Find(int x)
{
while (parent[x] != x)
{
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
void Union(int a, int b)
{
a = Find(a);
b = Find(b);
if (a != b) parent[a] = b;
}
var edgeMap = new Dictionary<long, int>();
long PackEdge(int a, int b)
{
if (a > b) (a, b) = (b, a);
return ((long)a << 32) | (uint)b;
}
void ProcessEdge(long e, int t)
{
if (edgeMap.TryGetValue(e, out var other))
{
if (Vector3.Dot(triNormal[t], triNormal[other]) > CoplanarDotThreshold)
Union(t, other);
}
else
{
edgeMap[e] = t;
}
}
for (int t = 0; t < triCount; t++)
{
int a = triCanonA[t], b = triCanonB[t], c = triCanonC[t];
ProcessEdge(PackEdge(a, b), t);
ProcessEdge(PackEdge(b, c), t);
ProcessEdge(PackEdge(c, a), t);
}
var islandOf = new int[triCount];
var islandRootToIndex = new Dictionary<int, int>();
for (int t = 0; t < triCount; t++)
{
int root = Find(t);
if (!islandRootToIndex.TryGetValue(root, out var idx))
{
idx = islandRootToIndex.Count;
islandRootToIndex[root] = idx;
}
islandOf[t] = idx;
}
int islandCount = islandRootToIndex.Count;
var accumNormal = new Vector3[islandCount];
for (int t = 0; t < triCount; t++)
accumNormal[islandOf[t]] += triNormal[t];
var islandAxisU = new Vector3[islandCount];
var islandAxisV = new Vector3[islandCount];
for (int i = 0; i < islandCount; i++)
{
var n = accumNormal[i].normalized;
ComputeTangentBasis(n, out islandAxisU[i], out islandAxisV[i]);
}
var islandMinU = new float[islandCount];
var islandMinV = new float[islandCount];
var islandMaxU = new float[islandCount];
var islandMaxV = new float[islandCount];
for (int i = 0; i < islandCount; i++)
{
islandMinU[i] = islandMinV[i] = float.PositiveInfinity;
islandMaxU[i] = islandMaxV[i] = float.NegativeInfinity;
}
Vector2 ProjectUV(Vector3 local, Vector3 axisU, Vector3 axisV) =>
new Vector2(Vector3.Dot(local, axisU), Vector3.Dot(local, axisV));
for (int t = 0; t < triCount; t++)
{
int island = islandOf[t];
Vector2 a = ProjectUV(srcVerts[triI0[t]], islandAxisU[island], islandAxisV[island]);
Vector2 b = ProjectUV(srcVerts[triI1[t]], islandAxisU[island], islandAxisV[island]);
Vector2 c = ProjectUV(srcVerts[triI2[t]], islandAxisU[island], islandAxisV[island]);
islandMinU[island] = Mathf.Min(islandMinU[island], a.x, b.x, c.x);
islandMinV[island] = Mathf.Min(islandMinV[island], a.y, b.y, c.y);
islandMaxU[island] = Mathf.Max(islandMaxU[island], a.x, b.x, c.x);
islandMaxV[island] = Mathf.Max(islandMaxV[island], a.y, b.y, c.y);
}
int gridSize = Mathf.Max(1, Mathf.CeilToInt(Mathf.Sqrt(islandCount)));
float cellSize = 1f / gridSize;
float cellPad = cellSize * MaskUvPadding * 0.5f;
var islandCellOrigin = new Vector2[islandCount];
var islandScale = new float[islandCount];
for (int i = 0; i < islandCount; i++)
{
int col = i % gridSize;
int row = i / gridSize;
islandCellOrigin[i] = new Vector2(col * cellSize, row * cellSize);
float span = Mathf.Max(Mathf.Max(islandMaxU[i] - islandMinU[i], islandMaxV[i] - islandMinV[i]), 1e-5f);
float usableCell = cellSize - cellPad * 2f;
islandScale[i] = usableCell / span;
}
var newVerts = new List<Vector3>();
var newNormals = new List<Vector3>();
var newUvMask = new List<Vector2>();
var newUvTile = new List<Vector2>();
var subTriangleLists = new List<int>[subMeshCount];
for (int s = 0; s < subMeshCount; s++) subTriangleLists[s] = new List<int>();
for (int t = 0; t < triCount; t++)
{
int island = islandOf[t];
Vector2 MapAtlas(Vector3 local)
{
Vector2 uv = ProjectUV(local, islandAxisU[island], islandAxisV[island]);
Vector2 norm = new Vector2(uv.x - islandMinU[island], uv.y - islandMinV[island]) *
islandScale[island];
return islandCellOrigin[island] + new Vector2(cellPad, cellPad) + norm;
}
int i0 = triI0[t], i1 = triI1[t], i2 = triI2[t];
Vector3 p0 = srcVerts[i0], p1 = srcVerts[i1], p2 = srcVerts[i2];
int baseIndex = newVerts.Count;
newVerts.Add(p0);
newVerts.Add(p1);
newVerts.Add(p2);
newNormals.Add(hasNormals ? srcNormals[i0] : triNormal[t]);
newNormals.Add(hasNormals ? srcNormals[i1] : triNormal[t]);
newNormals.Add(hasNormals ? srcNormals[i2] : triNormal[t]);
newUvMask.Add(MapAtlas(p0));
newUvMask.Add(MapAtlas(p1));
newUvMask.Add(MapAtlas(p2));
newUvTile.Add(hasUvTile ? srcUvTile[i0] : Vector2.zero);
newUvTile.Add(hasUvTile ? srcUvTile[i1] : Vector2.zero);
newUvTile.Add(hasUvTile ? srcUvTile[i2] : Vector2.zero);
int subMesh = triSubMesh[t];
subTriangleLists[subMesh].Add(baseIndex);
subTriangleLists[subMesh].Add(baseIndex + 1);
subTriangleLists[subMesh].Add(baseIndex + 2);
}
mesh.Clear();
mesh.indexFormat = newVerts.Count > 65000
? UnityEngine.Rendering.IndexFormat.UInt32
: UnityEngine.Rendering.IndexFormat.UInt16;
mesh.SetVertices(newVerts);
mesh.SetNormals(newNormals);
mesh.SetUVs(0, newUvMask);
mesh.SetUVs(2, newUvTile);
mesh.subMeshCount = subMeshCount;
for (int s = 0; s < subMeshCount; s++)
mesh.SetTriangles(subTriangleLists[s], s);
mesh.RecalculateBounds();
if (!hasNormals) mesh.RecalculateNormals();
mesh.RecalculateTangents();
#if UNITY_EDITOR
Unwrapping.GenerateSecondaryUVSet(mesh);
#endif
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a09905e536314ef1bd4c016d3b5cfb76
timeCreated: 1785923052
+58
View File
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using R3;
using UnityEngine;
namespace Code.D.Cleaning
{
public interface ICleaningService
{
}
public class CleaningService : IDisposable, ICleaningService
{
private List<Cleanable> _cleanableSurfaces = new();
private IDisposable _surfaces;
public ReactiveProperty<bool> LevelCleaned { get; } = new();
public CleaningService(Cleanable[] cleanableSurfaces)
{
DisposableBuilder builder = new DisposableBuilder();
foreach (var cleanable in cleanableSurfaces)
{
_cleanableSurfaces.Add(cleanable);
builder.Add(cleanable.Cleaned
.Subscribe(x => CheckProgress(cleanable)));
}
_surfaces = builder.Build();
}
private void CheckProgress(Cleanable cleanable)
{
if(cleanable.Cleaned.CurrentValue) Debug.Log($"{cleanable.name} cleaned");
CheckOverallProgress();
}
private void CheckOverallProgress()
{
if (LevelCleaned.CurrentValue)
return;
foreach (var t in _cleanableSurfaces)
if (!t.Cleaned.CurrentValue) return;
Debug.Log($"VICTORY!!!");
LevelCleaned.Value = true;
}
public void Dispose()
{
_surfaces?.Dispose();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5ce468dffcf438d98691a527f9c51ba
timeCreated: 1785923122
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9a45200a1a0d48beaea636b4b794ed95
timeCreated: 1785925560
@@ -0,0 +1,85 @@
Shader "Unlit/BrushPainter"
{
Properties
{
_DrawPosition("Draw Position", Vector) = (0.5, 0.5, 0, 0)
_DrawRadius("Draw Radius", Float) = 0.05
_DrawAngleX("Draw Angle (Rad) X", Float) = 0.0
_DrawAngleY("Draw Angle (Rad) Y", Float) = 0.0
_ToolDirtiness("Tool Dirtiness", Range(0, 1)) = 0
[MainTexture] _BrushTex("Brush Texture", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType" = "Transparent"
"Queue" = "Transparent"
}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
ZTest Always
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
float4 _DrawPosition;
float _DrawRadius;
float _DrawAngleX;
float _DrawAngleY;
float _ToolDirtiness;
TEXTURE2D(_BrushTex);
SAMPLER(sampler_BrushTex);
Varyings vert(Attributes IN)
{
Varyings OUT;
OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
OUT.uv = IN.uv;
return OUT;
}
half4 frag(Varyings IN) : SV_Target
{
float2 centeredUV = (IN.uv - _DrawPosition.xy) / (_DrawRadius * 2.0);
float2 rotatedUV;
rotatedUV.x = centeredUV.x * _DrawAngleX - centeredUV.y * _DrawAngleY;
rotatedUV.y = centeredUV.x * _DrawAngleY + centeredUV.y * _DrawAngleX;
float2 brushUV = rotatedUV + 0.5;
float inBounds = step(0.0, brushUV.x) * step(brushUV.x, 1.0) *
step(0.0, brushUV.y) * step(brushUV.y, 1.0);
half4 brushColor = SAMPLE_TEXTURE2D(_BrushTex, sampler_BrushTex, brushUV);
float brushMask = brushColor.a * inBounds;
float fillValue = _ToolDirtiness * brushMask;
return half4(fillValue, fillValue, fillValue, brushMask);
}
ENDHLSL
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 31f42842d3a8460ea125575cb5a72a5a
timeCreated: 1785925570
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5e608514d754bcfb44c3df7c722601d
timeCreated: 1786025274
@@ -0,0 +1,9 @@
using UnityEngine;
namespace Code.D.Cleaning.UI
{
public abstract class CleanToolWidget : MonoBehaviour
{
public abstract void SetToolDirtiness(float dirtiness);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 95907fda6bb942a1865c6f1d9a6c1f91
timeCreated: 1786025360
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Threading;
using Code.D.Cleaning.CleanTools;
using Code.Infrastructure.UI;
using Cysharp.Threading.Tasks;
using Reflex.Attributes;
using UnityEngine;
namespace Code.D.Cleaning.UI
{
public class CleaningPanel : UIPanel
{
[Inject]
private void Construct(List<CleanTool> cleanTools)
{
Debug.Log("InjectingPanel");
foreach (var cleanTool in cleanTools) Debug.Log(cleanTool);
}
public override UniTask Open(CancellationToken cts = default)
{
return UniTask.CompletedTask;
}
public override UniTask Close(CancellationToken cts = default)
{
return UniTask.CompletedTask;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4f7be6d67e6944899ab2c3ae0255b58e
timeCreated: 1786025297
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Code.D.Cleaning.CleanTools;
using R3;
using Reflex.Attributes;
using UnityEngine;
using UnityEngine.UI;
namespace Code.D.Cleaning.UI
{
public class TryapkaWidget : CleanToolWidget
{
[SerializeField] private Image toolImage;
[SerializeField] private Color cleanToolColor;
[SerializeField] private Color dirtyToolColor;
private IDisposable _disposable;
[Inject]
private void Construct(List<CleanTool> cleanTools)
{
Debug.Log("InjectingWidget");
var tool = cleanTools.SingleOrDefault(x => x.ToolType == CleanToolType.Tryapka);
if (tool == null) return;
_disposable = tool.CurrentDirtiness
.Subscribe(SetToolDirtiness);
}
private void OnDestroy()
{
_disposable?.Dispose();
}
public override void SetToolDirtiness(float dirtiness)
{
toolImage.color = Color.Lerp(cleanToolColor, dirtyToolColor, dirtiness);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6cbf9cd4573e4544a67318da5d5fa072
timeCreated: 1786025415
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9891a4d4490a4d35ab073ab28b4d0c25
timeCreated: 1785846398
@@ -0,0 +1,93 @@
using System;
using Code.Infrastructure.Camera;
using Code.Infrastructure.Camera.CameraStates;
using Code.Infrastructure.Generators;
using Code.Infrastructure.Input;
using Cysharp.Threading.Tasks;
using R3;
using UnityEngine;
using UnityEngine.InputSystem;
namespace Code.Game.Gameplay.Player
{
[State(typeof(CameraMode),typeof(CameraStateMachineContext), ManualInherit = true)]
public partial class CleanerCameraState : BaseCameraState
{
public void SetTarget(Transform target)
{
_target = target;
Context.cameraLoadedData.Camera.transform.position = _target.position;
Context.cameraLoadedData.Camera.transform.rotation = _target.rotation;
}
private IDisposable _disposable;
public void SetPlayer(PlayerMovementController playerMovementController)
{
_disposable = playerMovementController.IsSliding.Subscribe(SlideToggle);
}
private void SlideToggle(bool isSliding) => _isSliding = isSliding;
private Transform _target;
private Vector2 _mouseDelta;
private Vector2 _rotation = Vector2.zero;
private float _sensitivityX = 0.1f;
private float _sensitivityY = 0.1f;
private float _slideStupidity = 1f;
private bool _isSliding;
public CleanerCameraState(CameraStateMachineContext context) : base(context)
{
}
private void LookCancelled(InputAction.CallbackContext obj) => _mouseDelta = Vector2.zero;
private void LookPerformed(InputAction.CallbackContext obj) => _mouseDelta = obj.ReadValue<Vector2>();
public override CameraMode MyState => CameraMode.Cleaner;
public override UniTask EnterState()
{
Context.InputService.BindActionPerformed((int)ActionMappings.Cleaning, (int)CleaningActions.Watchment, LookPerformed);
Context.InputService.BindActionCancelled((int)ActionMappings.Cleaning, (int)CleaningActions.Watchment, LookCancelled);
return UniTask.CompletedTask;
}
public override UniTask ExitState()
{
Context.InputService.UnbindActionPerformed((int)ActionMappings.Cleaning, (int)CleaningActions.Watchment, LookPerformed);
Context.InputService.UnbindActionCancelled((int)ActionMappings.Cleaning, (int)CleaningActions.Watchment, LookCancelled);
_disposable.Dispose();
return UniTask.CompletedTask;
}
public override void Update()
{
}
public override void LateUpdate()
{
if (_target is null) return;
var camTransform = Context.cameraLoadedData.Camera.transform;
camTransform.position = _target.position;
var mouseDeltaX = _mouseDelta.x * _sensitivityX;
var mouseDeltaY = _mouseDelta.y * _sensitivityY;
if (_isSliding)
{
mouseDeltaX = Mathf.Clamp(mouseDeltaX, -_slideStupidity, _slideStupidity);
mouseDeltaY = Mathf.Clamp(mouseDeltaY, -_slideStupidity, _slideStupidity);
}
_rotation.x += mouseDeltaX;
_rotation.y -= mouseDeltaY;
_rotation.y = Mathf.Clamp(_rotation.y, -90f, 90f);
camTransform.localRotation = Quaternion.Euler(_rotation.y, _rotation.x, 0f);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9e592628c3fe45bf9659d1050680c296
timeCreated: 1785846388
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c9b8ea0290514b809c83dd30d68391ca
timeCreated: 1785846388
@@ -0,0 +1,45 @@
using System;
using Code.Infrastructure.Utils;
using Cysharp.Threading.Tasks;
using Reflex.Attributes;
using Reflex.Core;
using Reflex.Injectors;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Game.Gameplay.Player.Factory
{
public interface IPlayerFactory
{
UniTask<GameObject> CreatePlayer();
void InjectPlayer(Container container);
}
public class PlayerFactory : IDisposable, IPlayerFactory
{
private readonly SingleConfig<PlayerFactorySettings> _config;
private GameObject _playerInstance;
public PlayerFactory(SingleConfig<PlayerFactorySettings> config)
{
_config = config;
}
public async UniTask<GameObject> CreatePlayer()
{
_playerInstance ??= await Addressables.InstantiateAsync(_config.Config.Player).ToUniTask();
return _playerInstance;
}
public void InjectPlayer(Container container)
{
GameObjectInjector.InjectRecursive(_playerInstance, container);
}
public void Dispose()
{
Addressables.ReleaseInstance(_playerInstance);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fdc8848de9734ba3bb1c3e3ec81145e6
timeCreated: 1778956904
@@ -0,0 +1,13 @@
using Code.Infrastructure.Generators;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Game.Gameplay.Player.Factory
{
[Config("Configs/Gameplay/PlayerFactory")]
[CreateAssetMenu(fileName = "PlayerFactorySettings", menuName = "Configs/Gameplay/PlayerFactory")]
public class PlayerFactorySettings : ScriptableObject
{
public AssetReference Player;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c312323c8dc049b2b7e7330f54b09aad
timeCreated: 1785871572
@@ -0,0 +1,13 @@
using Code.Infrastructure.Camera;
using Reflex.Attributes;
using UnityEngine;
namespace Code.Game.Gameplay.Player
{
public class PlayerCameraTarget : MonoBehaviour
{
[Inject]
private void Construct(CameraStateMachine cameraStateMachine) =>
((CleanerCameraState)cameraStateMachine.CurrentBaseState).SetTarget(transform);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c579fd0c46544b26b3dd27ecdf36fc37
timeCreated: 1785846388
@@ -0,0 +1,96 @@
using Code.Game.Gameplay.Player;
using Code.Infrastructure.Camera;
using Code.Infrastructure.Input;
using Reflex.Attributes;
using UnityEngine;
using UnityEngine.InputSystem;
namespace Code.Game.Cleaning.Player
{
public class PlayerControlsProvider : MonoBehaviour
{
[SerializeField] private PlayerMovementController playerMovementController;
private IInputService _inputService;
private Vector2 _movementInput;
private bool _jumpDown;
private bool _crouchDown;
private bool _walkOn;
private Transform _cameraTransform;
[Inject]
private void Construct(IInputService inputService, CameraStateMachine cameraStateMachine)
{
_cameraTransform = cameraStateMachine.Camera.transform;
_inputService = inputService;
_inputService.BindActionPerformed((int)ActionMappings.Cleaning, (int)CleaningActions.Movement,
MovementPerformed);
_inputService.BindActionCancelled((int)ActionMappings.Cleaning, (int)CleaningActions.Movement,
MovementCancelled);
_inputService.BindActionPerformed((int)ActionMappings.Cleaning,
(int)CleaningActions.Jump, JumpPerformed);
_inputService.BindActionCancelled((int)ActionMappings.Cleaning,
(int)CleaningActions.Jump, JumpCancelled);
_inputService.BindActionPerformed((int)ActionMappings.Cleaning,
(int)CleaningActions.Walk, WalkPerformed);
_inputService.BindActionPerformed((int)ActionMappings.Cleaning,
(int)CleaningActions.Crouch, CrouchPerformed);
_inputService.BindActionCancelled((int)ActionMappings.Cleaning,
(int)CleaningActions.Crouch, CrouchCancelled);
}
private void CrouchPerformed(InputAction.CallbackContext obj) => _crouchDown = true;
private void CrouchCancelled(InputAction.CallbackContext obj) => _crouchDown = false;
private void WalkPerformed(InputAction.CallbackContext obj) => _walkOn = !_walkOn;
private void JumpCancelled(InputAction.CallbackContext obj) => _jumpDown = false;
private void JumpPerformed(InputAction.CallbackContext obj) => _jumpDown = true;
private void MovementCancelled(InputAction.CallbackContext obj) => _movementInput = Vector2.zero;
private void MovementPerformed(InputAction.CallbackContext obj) => _movementInput = obj.ReadValue<Vector2>();
private void Update()
{
if (!_cameraTransform) return;
var inputs = new PlayerCharacterInputs()
{
MoveAxisForward = _movementInput.y,
MoveAxisRight = _movementInput.x,
CameraRotation = _cameraTransform.rotation,
JumpDown = _jumpDown,
CrouchDown = _crouchDown,
CrouchUp = !_crouchDown,
WalkOn = _walkOn
};
playerMovementController.SetInputs(ref inputs);
}
private void OnDestroy()
{
_inputService.UnbindActionPerformed((int)ActionMappings.Cleaning, (int)CleaningActions.Movement,
MovementPerformed);
_inputService.UnbindActionCancelled((int)ActionMappings.Cleaning, (int)CleaningActions.Movement,
MovementCancelled);
_inputService.UnbindActionPerformed((int)ActionMappings.Cleaning,
(int)CleaningActions.Jump, JumpPerformed);
_inputService.UnbindActionCancelled((int)ActionMappings.Cleaning,
(int)CleaningActions.Jump, JumpCancelled);
_inputService.UnbindActionPerformed((int)ActionMappings.Cleaning,
(int)CleaningActions.Walk, WalkPerformed);
_inputService.UnbindActionPerformed((int)ActionMappings.Cleaning,
(int)CleaningActions.Crouch, CrouchPerformed);
_inputService.UnbindActionCancelled((int)ActionMappings.Cleaning,
(int)CleaningActions.Crouch, CrouchCancelled);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 75c0d109df244d6ebf8c0eb976553c6a
timeCreated: 1785846388
@@ -0,0 +1,114 @@
using Code.D.Cleaning;
using Code.D.Player;
using Code.Infrastructure.Camera;
using Code.Infrastructure.Input;
using Cysharp.Threading.Tasks;
using R3;
using Reflex.Attributes;
using UnityEngine;
using UnityEngine.InputSystem;
namespace Code.Game.Gameplay.Player
{
public class PlayerDetailMaskDrawer : MonoBehaviour
{
private IInputService _inputService;
private Transform _cameraTransform;
private bool _cleaning, _cleaningUnder;
private LayerMask _layerMask;
private TagHandle _surfaceTag;
private CleanableSurface _lastCleaned;
[SerializeField] private float cleaningDistance = 5f;
[SerializeField] private PlayerEquipment playerEquipment;
private void Awake()
{
_surfaceTag = TagHandle.GetExistingTag("CleanableSurface");
_layerMask = LayerMask.GetMask("Cleanable");
GetComponent<PlayerMovementController>().IsSliding.Subscribe(SlideToggle).AddTo(destroyCancellationToken);
}
[Inject]
private void Construct(IInputService inputService, CameraStateMachine cameraStateMachine)
{
_cameraTransform = cameraStateMachine.Camera.transform;
_inputService = inputService;
_inputService.BindActionPerformed((int)ActionMappings.Cleaning, (int)CleaningActions.Clean, DrawPerformed);
_inputService.BindActionCancelled((int)ActionMappings.Cleaning, (int)CleaningActions.Clean, DrawCancelled);
}
private void SlideToggle(bool isSliding) => _cleaningUnder = isSliding;
private void DrawPerformed(InputAction.CallbackContext obj) => _cleaning = true;
private void DrawCancelled(InputAction.CallbackContext obj) => _cleaning = false;
private void FixedUpdate()
{
if (_cleaning) CleanInFront();
if (_cleaningUnder) CleanUnder();
}
private void CleanUnder()
{
if (!Physics.Raycast(
_cameraTransform.position,
-transform.up,
out var info,
5f,
layerMask: _layerMask)) return;
if (info.collider.CompareTag(_surfaceTag))
CleanSurface(info);
}
private void CleanInFront()
{
if (!Physics.Raycast(
_cameraTransform.position,
_cameraTransform.forward,
out var info,
cleaningDistance,
layerMask: _layerMask)) return;
if (info.collider.CompareTag(_surfaceTag))
CleanSurface(info);
else
CleanAsset(info);
}
private void CleanAsset(RaycastHit info)
{
var cleanable = info.collider.GetComponent<CleanableAsset>();
cleanable.Clean();
}
private void CleanSurface(RaycastHit info)
{
CleanableSurface cleanable;
if (_lastCleaned is not null && info.collider.GetEntityId() == _lastCleaned.GetEntityId())
cleanable = _lastCleaned;
else
{
cleanable = info.collider.GetComponent<CleanableSurface>();
_lastCleaned = cleanable;
}
cleanable.Clean(info, playerEquipment.CurrentTool.Use(), _cameraTransform);
}
private void OnDestroy()
{
_inputService.UnbindActionPerformed((int)ActionMappings.Cleaning, (int)CleaningActions.Clean,
DrawPerformed);
_inputService.UnbindActionCancelled((int)ActionMappings.Cleaning, (int)CleaningActions.Clean,
DrawCancelled);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f1606bdb9e6840f5960df117e5298061
timeCreated: 1785923251
+105
View File
@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Code.D.Cleaning.CleanTools;
using R3;
using Reflex.Attributes;
using Unity.Burst;
using UnityEngine;
namespace Code.D.Player
{
public class PlayerEquipment : MonoBehaviour
{
[SerializeField] private Texture2D tryapkaBrushTexture;
public CleanTool CurrentTool { get; private set; }
private Dictionary<CleanToolType, CleanTool> _cleanTools = new();
public ReactiveProperty<CleanToolType> CurrentToolType { get; } = new();
private IDisposable _dirtinessDisposable;
[Inject]
private void Construct(List<CleanTool> cleanTools)
{
foreach (var cleanTool in cleanTools) _cleanTools.Add(cleanTool.ToolType, cleanTool);
var firstTool = _cleanTools.First();
CurrentToolType.Value = firstTool.Key;
CurrentTool = firstTool.Value;
}
private void Awake()
{
//CurrentTool = new Tryapka(0.0001f, 0.005f, tryapkaBrushTexture);
//
// DisposableBuilder builder = new DisposableBuilder();
// // Я безумец, здесь подписаться на все сразу
// builder.Add(CurrentTool.CurrentDirtiness.Subscribe(x => CurrentToolDirtiness.Value =
// new ToolDirtinessData
// {
// Dirtiness = x,
// ToolType = CleanToolType.Tryapka
// }
// ));
//
// _dirtinessDisposable = builder.Build();
}
private void OnDestroy()
{
_dirtinessDisposable?.Dispose();
}
}
public struct ToolDirtinessData : IEquatable<ToolDirtinessData>
{
public float Dirtiness;
public CleanToolType ToolType;
public bool Equals(ToolDirtinessData other)
{
return Mathf.Approximately(Dirtiness, other.Dirtiness) && ToolType == other.ToolType;
}
[BurstDiscard]
public override bool Equals(object obj)
{
return obj is ToolDirtinessData other && Equals(other);
}
[BurstDiscard]
public override int GetHashCode()
{
return HashCode.Combine(Dirtiness, ToolType);
}
public static bool operator ==(ToolDirtinessData left, ToolDirtinessData right) => left.Equals(right);
public static bool operator !=(ToolDirtinessData left, ToolDirtinessData right) => !left.Equals(right);
}
public struct ToolDirtyData : IEquatable<ToolDirtyData>
{
public CleanToolType ToolType;
public bool Equals(ToolDirtyData other)
{
return ToolType == other.ToolType;
}
[BurstDiscard]
public override bool Equals(object obj)
{
return obj is ToolDirtyData other && Equals(other);
}
[BurstDiscard]
public override int GetHashCode()
{
return ToolType.GetHashCode();
}
public static bool operator ==(ToolDirtyData left, ToolDirtyData right) => left.Equals(right);
public static bool operator !=(ToolDirtyData left, ToolDirtyData right) => !left.Equals(right);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ba4e659f17b147db992374aab735ed4c
timeCreated: 1785940207
+64
View File
@@ -0,0 +1,64 @@
using Code.D.Cleaning.CleanTools;
using Code.Infrastructure.Camera;
using Code.Infrastructure.Input;
using Reflex.Attributes;
using UnityEngine;
using UnityEngine.InputSystem;
namespace Code.D.Player
{
public class PlayerInteractor : MonoBehaviour
{
private IInputService _inputService;
private LayerMask _cleanToolInteractableMask;
private Transform _cameraTransform;
private CleanToolInteractable _lastCleanToolInteractable;
[SerializeField] private float interactionDistance = 5f;
[SerializeField] private PlayerEquipment playerEquipment;
private void Awake()
{
_cleanToolInteractableMask = LayerMask.GetMask("CleanToolInteractable");
}
[Inject]
private void Construct(IInputService inputService, CameraStateMachine cameraStateMachine)
{
_cameraTransform = cameraStateMachine.Camera.transform;
_inputService = inputService;
inputService.BindActionPerformed((int)ActionMappings.Cleaning, (int)CleaningActions.Interact, TryInteract);
}
private void OnDestroy()
{
_inputService.UnbindActionPerformed((int)ActionMappings.Cleaning, (int)CleaningActions.Interact,
TryInteract);
}
private void TryInteract(InputAction.CallbackContext obj)
{
if (!Physics.Raycast(
_cameraTransform.position,
_cameraTransform.forward,
out var info,
interactionDistance,
layerMask: _cleanToolInteractableMask)) return;
CleanToolInteractable interactable;
if (_lastCleanToolInteractable is not null &&
info.collider.GetEntityId() == _lastCleanToolInteractable.GetEntityId())
{
interactable = _lastCleanToolInteractable;
}
else
{
interactable = info.collider.gameObject.GetComponent<CleanToolInteractable>();
_lastCleanToolInteractable = interactable;
}
interactable.Use(playerEquipment.CurrentTool);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 997fa523ddda4b1ea3f4b0b1e1e7864a
timeCreated: 1785939712
@@ -0,0 +1,644 @@
using System.Collections.Generic;
using Alchemy.Inspector;
using KinematicCharacterController;
using LitMotion;
using LitMotion.Extensions;
using R3;
using UnityEngine;
namespace Code.Game.Gameplay.Player
{
public enum CharacterState
{
Default,
Sliding
}
public enum OrientationMethod
{
TowardsCamera,
TowardsMovement,
}
public struct PlayerCharacterInputs
{
public float MoveAxisForward;
public float MoveAxisRight;
public Quaternion CameraRotation;
public bool JumpDown;
public bool CrouchDown;
public bool CrouchUp;
public bool WalkOn;
}
public struct AICharacterInputs
{
public Vector3 MoveVector;
public Quaternion Look;
}
public enum BonusOrientationMethod
{
None,
TowardsGravity,
TowardsGroundSlopeAndGravity,
}
public class PlayerMovementController : MonoBehaviour, ICharacterController
{
public KinematicCharacterMotor Motor;
[TabGroup("Stable Movement")] public float MaxStableMoveSpeed = 10f;
[TabGroup("Stable Movement")] public float SprintingModifier = 2f;
[TabGroup("Stable Movement")] public float StableMovementSharpness = 15f;
[TabGroup("Stable Movement")] public float OrientationSharpness = 10f;
[TabGroup("Stable Movement")] public OrientationMethod OrientationMethod = OrientationMethod.TowardsCamera;
[TabGroup("Sliding")] public AnimationCurve SlideSpeedMultiplierCurve;
[TabGroup("Sliding")] public float SlideSpeedCurveStep = 0.1f;
[TabGroup("Sliding")] public float MaxCrouchMoveSpeed = 5f;
[TabGroup("Sliding")] public float InCrouchTime = 1f;
[TabGroup("Sliding")] public float OutCrouchTime = 1f;
[TabGroup("Sliding")] public float SlideStupidity = 0.1f;
[TabGroup("Air Movement")] public float MaxAirMoveSpeed = 15f;
[TabGroup("Air Movement")] public float AirAccelerationSpeed = 15f;
[TabGroup("Air Movement")] public float Drag = 0.1f;
[TabGroup("Jumping")] public bool AllowJumpingWhenSliding = false;
[TabGroup("Jumping")] public float JumpUpSpeed = 10f;
[TabGroup("Jumping")] public float JumpScalableForwardSpeed = 10f;
[TabGroup("Jumping")] public float JumpPreGroundingGraceTime = 0f;
[TabGroup("Jumping")] public float JumpPostGroundingGraceTime = 0f;
[TabGroup("Jumping")] public BonusOrientationMethod BonusOrientationMethod = BonusOrientationMethod.None;
[TabGroup("Jumping")] public float BonusOrientationSharpness = 10f;
[TabGroup("Jumping")] public float BonusOrientationGravitySharpness;
[TabGroup("Jumping")] public Vector3 Gravity = new Vector3(0, -30f, 0);
[TabGroup("Misc")] public List<Collider> IgnoredColliders = new List<Collider>();
[TabGroup("Misc")] public Transform cameraTransform;
[TabGroup("Misc")] public float CrouchedCapsuleHeight = 1f;
public CharacterState CurrentCharacterState { get; private set; }
public Vector3 CurrentVelocity => Motor.Velocity;
public Quaternion CurrentRotation => Motor.TransientRotation;
private Collider[] _probedColliders = new Collider[8];
private RaycastHit[] _probedHits = new RaycastHit[8];
private Vector3 _moveInputVector;
private Vector3 _lookInputVector;
private bool _jumpRequested = false;
private bool _jumpConsumed = false;
private bool _jumpedThisFrame = false;
private float _timeSinceJumpRequested = Mathf.Infinity;
private float _timeSinceLastAbleToJump = 0f;
private Vector3 _internalVelocityAdd = Vector3.zero;
private bool _shouldBeCrouching = false;
private bool _isCrouching = false;
public ReactiveProperty<bool> IsSliding { get; } = new();
private bool _isSliding = false;
private float _slideCurveProgress;
private bool _isSlideEnded = false;
private bool _isSprinting = false;
private Vector3 lastInnerNormal = Vector3.zero;
private Vector3 lastOuterNormal = Vector3.zero;
private void Awake()
{
// Handle initial state
TransitionToState(CharacterState.Default);
// Assign the characterController to the motor
Motor.CharacterController = this;
}
/// <summary>
/// Handles movement state transitions and enter/exit callbacks
/// </summary>
public void TransitionToState(CharacterState newState)
{
CharacterState tmpInitialState = CurrentCharacterState;
OnStateExit(tmpInitialState, newState);
CurrentCharacterState = newState;
OnStateEnter(newState, tmpInitialState);
}
/// <summary>
/// Event when entering a state
/// </summary>
public void OnStateEnter(CharacterState state, CharacterState fromState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
}
}
/// <summary>
/// Event when exiting a state
/// </summary>
public void OnStateExit(CharacterState state, CharacterState toState)
{
switch (state)
{
case CharacterState.Default:
{
break;
}
}
}
/// <summary>
/// This is called every frame by ExamplePlayer in order to tell the character what its inputs are
/// </summary>
public void SetInputs(ref PlayerCharacterInputs inputs)
{
// Clamp input
Vector3 moveInputVector =
Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0f, inputs.MoveAxisForward), 1f);
// Calculate camera direction and rotation on the character plane
Vector3 cameraPlanarDirection =
Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.forward, Motor.CharacterUp).normalized;
if (cameraPlanarDirection.sqrMagnitude == 0f)
{
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.CameraRotation * Vector3.up, Motor.CharacterUp)
.normalized;
}
Quaternion cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, Motor.CharacterUp);
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
if (_isSliding)
{
moveInputVector.x *= SlideStupidity;
}
// Move and look inputs
_moveInputVector = cameraPlanarRotation * moveInputVector;
switch (OrientationMethod)
{
case OrientationMethod.TowardsCamera:
_lookInputVector = cameraPlanarDirection;
break;
case OrientationMethod.TowardsMovement:
_lookInputVector = _moveInputVector.normalized;
break;
}
// Jumping input
if (inputs.JumpDown)
{
_timeSinceJumpRequested = 0f;
_jumpRequested = true;
}
_isSprinting = !inputs.WalkOn;
// Crouching input
if (inputs.CrouchDown)
{
_shouldBeCrouching = true;
if (!_isCrouching)
{
if(_isSprinting && !_isSlideEnded)
{
_isSliding = true;
IsSliding.Value = true;
}
else _isSlideEnded = true;
_isCrouching = true;
Motor.SetCapsuleDimensions(0.5f, CrouchedCapsuleHeight, CrouchedCapsuleHeight * 0.5f);
if(_crouchTransition.IsPlaying()) _crouchTransition.Cancel();
_crouchTransition = LMotion.Create(cameraTransform.localPosition.y, 0.85f, InCrouchTime)
.WithEase(Ease.InOutQuad)
.BindToLocalPositionY(cameraTransform);
}
}
else if (inputs.CrouchUp)
{
_shouldBeCrouching = false;
}
break;
}
}
}
/// <summary>
/// This is called every frame by the AI script in order to tell the character what its inputs are
/// </summary>
public void SetInputs(ref AICharacterInputs inputs)
{
// _moveInputVector = inputs.MoveVector;
// _lookInputVector = inputs.Look;
var cameraPlanarDirection =
Vector3.ProjectOnPlane(inputs.Look * Vector3.forward, Motor.CharacterUp).normalized;
if (cameraPlanarDirection.sqrMagnitude == 0f)
{
cameraPlanarDirection = Vector3.ProjectOnPlane(inputs.Look * Vector3.up, Motor.CharacterUp)
.normalized;
}
//var cameraPlanarRotation = Quaternion.LookRotation(cameraPlanarDirection, Motor.CharacterUp);
_moveInputVector = inputs.MoveVector; // cameraPlanarRotation *
_lookInputVector = cameraPlanarDirection;
}
private Quaternion _tmpTransientRot;
private MotionHandle _crouchTransition;
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called before the character begins its movement update
/// </summary>
public void BeforeCharacterUpdate(float deltaTime)
{
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its rotation should be right now.
/// This is the ONLY place where you should set the character's rotation
/// </summary>
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
if (_lookInputVector.sqrMagnitude > 0f && OrientationSharpness > 0f)
{
// Smoothly interpolate from current to target look direction
Vector3 smoothedLookInputDirection = Vector3.Slerp(Motor.CharacterForward, _lookInputVector,
1 - Mathf.Exp(-OrientationSharpness * deltaTime)).normalized;
// Set the current rotation (which will be used by the KinematicCharacterMotor)
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, Motor.CharacterUp);
}
Vector3 currentUp = (currentRotation * Vector3.up);
if (BonusOrientationMethod == BonusOrientationMethod.TowardsGravity)
{
// Rotate from current up to invert gravity
Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, -Gravity.normalized,
1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
currentRotation = Quaternion.FromToRotation(currentUp, smoothedGravityDir) * currentRotation;
}
else if (BonusOrientationMethod == BonusOrientationMethod.TowardsGroundSlopeAndGravity)
{
if (Motor.GroundingStatus.IsStableOnGround)
{
Vector3 initialCharacterBottomHemiCenter =
Motor.TransientPosition + (currentUp * Motor.Capsule.radius);
Vector3 smoothedGroundNormal = Vector3.Slerp(Motor.CharacterUp,
Motor.GroundingStatus.GroundNormal,
1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
currentRotation = Quaternion.FromToRotation(currentUp, smoothedGroundNormal) *
currentRotation;
// Move the position to create a rotation around the bottom hemi center instead of around the pivot
Motor.SetTransientPosition(initialCharacterBottomHemiCenter +
(currentRotation * Vector3.down * Motor.Capsule.radius));
}
else
{
BonusOrientationGravitySharpness = 0.01f;
Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, Motor.Velocity.normalized,
1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime *
BonusOrientationGravitySharpness));
// Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, -Gravity.normalized,
// 1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime * BonusOrientationGravitySharpness));
currentRotation = Quaternion.FromToRotation(currentUp, smoothedGravityDir) *
currentRotation;
}
}
else
{
Vector3 smoothedGravityDir = Vector3.Slerp(currentUp, Vector3.up,
1 - Mathf.Exp(-BonusOrientationSharpness * deltaTime));
currentRotation = Quaternion.FromToRotation(currentUp, smoothedGravityDir) * currentRotation;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is where you tell your character what its velocity should be right now.
/// This is the ONLY place where you can set the character's velocity
/// </summary>
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Ground movement
if (Motor.GroundingStatus.IsStableOnGround)
{
float currentVelocityMagnitude = currentVelocity.magnitude;
Vector3 effectiveGroundNormal = Motor.GroundingStatus.GroundNormal;
// Reorient velocity on slope
currentVelocity = Motor.GetDirectionTangentToSurface(currentVelocity, effectiveGroundNormal) *
currentVelocityMagnitude;
// Calculate target velocity
Vector3 inputRight = Vector3.Cross(_moveInputVector, Motor.CharacterUp);
Vector3 reorientedInput = Vector3.Cross(effectiveGroundNormal, inputRight).normalized *
_moveInputVector.magnitude;
Vector3 targetMovementVelocity;
switch (_isCrouching)
{
case true when _isSliding && !_isSlideEnded:
{
_slideCurveProgress += deltaTime;
var slideMultiplier = SlideSpeedMultiplierCurve
.Evaluate(_slideCurveProgress * SlideSpeedCurveStep);
targetMovementVelocity = reorientedInput * (MaxStableMoveSpeed * slideMultiplier);
if (_slideCurveProgress >= 1f) _isSlideEnded = true;
break;
}
case true:
targetMovementVelocity = reorientedInput * MaxCrouchMoveSpeed;
break;
default:
targetMovementVelocity = reorientedInput * MaxStableMoveSpeed;
break;
}
if (_isSprinting) targetMovementVelocity *= SprintingModifier;
// Smooth movement Velocity
currentVelocity = Vector3.Lerp(currentVelocity, targetMovementVelocity,
1f - Mathf.Exp(-StableMovementSharpness * deltaTime));
}
// Air movement
else
{
// Add move input
if (_moveInputVector.sqrMagnitude > 0f)
{
Vector3 addedVelocity = _moveInputVector * (AirAccelerationSpeed * deltaTime);
Vector3 currentVelocityOnInputsPlane =
Vector3.ProjectOnPlane(currentVelocity, Motor.CharacterUp);
// Limit air velocity from inputs
if (currentVelocityOnInputsPlane.magnitude < MaxAirMoveSpeed)
{
// clamp addedVel to make total vel not exceed max vel on inputs plane
Vector3 newTotal = Vector3.ClampMagnitude(currentVelocityOnInputsPlane + addedVelocity,
MaxAirMoveSpeed);
addedVelocity = newTotal - currentVelocityOnInputsPlane;
}
else
{
// Make sure added vel doesn't go in the direction of the already-exceeding velocity
if (Vector3.Dot(currentVelocityOnInputsPlane, addedVelocity) > 0f)
{
addedVelocity = Vector3.ProjectOnPlane(addedVelocity,
currentVelocityOnInputsPlane.normalized);
}
}
// Prevent air-climbing sloped walls
if (Motor.GroundingStatus.FoundAnyGround)
{
if (Vector3.Dot(currentVelocity + addedVelocity, addedVelocity) > 0f)
{
Vector3 perpenticularObstructionNormal = Vector3
.Cross(Vector3.Cross(Motor.CharacterUp, Motor.GroundingStatus.GroundNormal),
Motor.CharacterUp).normalized;
addedVelocity =
Vector3.ProjectOnPlane(addedVelocity, perpenticularObstructionNormal);
}
}
// Apply added velocity
currentVelocity += addedVelocity;
}
// Gravity
currentVelocity += Gravity * deltaTime;
// Drag
currentVelocity *= (1f / (1f + (Drag * deltaTime)));
}
// Handle jumping
_jumpedThisFrame = false;
_timeSinceJumpRequested += deltaTime;
if (_jumpRequested)
{
// See if we actually are allowed to jump
if (!_jumpConsumed &&
((AllowJumpingWhenSliding
? Motor.GroundingStatus.FoundAnyGround
: Motor.GroundingStatus.IsStableOnGround) ||
_timeSinceLastAbleToJump <= JumpPostGroundingGraceTime))
{
// Calculate jump direction before ungrounding
Vector3 jumpDirection = Motor.CharacterUp;
if (Motor.GroundingStatus.FoundAnyGround && !Motor.GroundingStatus.IsStableOnGround)
{
jumpDirection = Motor.GroundingStatus.GroundNormal;
}
// Makes the character skip ground probing/snapping on its next update.
// If this line weren't here, the character would remain snapped to the ground when trying to jump. Try commenting this line out and see.
Motor.ForceUnground();
// Add to the return velocity and reset jump state
currentVelocity += (jumpDirection * JumpUpSpeed) -
Vector3.Project(currentVelocity, Motor.CharacterUp);
currentVelocity += (_moveInputVector * JumpScalableForwardSpeed);
_jumpRequested = false;
_jumpConsumed = true;
_jumpedThisFrame = true;
}
}
// Take into account additive velocity
if (_internalVelocityAdd.sqrMagnitude > 0f)
{
currentVelocity += _internalVelocityAdd;
_internalVelocityAdd = Vector3.zero;
}
break;
}
}
}
/// <summary>
/// (Called by KinematicCharacterMotor during its update cycle)
/// This is called after the character has finished its movement update
/// </summary>
public void AfterCharacterUpdate(float deltaTime)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
// Handle jump-related values
{
// Handle jumping pre-ground grace period
if (_jumpRequested && _timeSinceJumpRequested > JumpPreGroundingGraceTime)
{
_jumpRequested = false;
}
if (AllowJumpingWhenSliding
? Motor.GroundingStatus.FoundAnyGround
: Motor.GroundingStatus.IsStableOnGround)
{
// If we're on a ground surface, reset jumping values
if (!_jumpedThisFrame)
{
_jumpConsumed = false;
}
_timeSinceLastAbleToJump = 0f;
}
else
{
// Keep track of time since we were last able to jump (for grace period)
_timeSinceLastAbleToJump += deltaTime;
}
}
if (_isSliding && _isSlideEnded)
{
_isSliding = false;
IsSliding.Value = false;
}
// Handle uncrouching
if (_isCrouching && !_shouldBeCrouching)
{
// Do an overlap test with the character's standing height to see if there are any obstructions
Motor.SetCapsuleDimensions(0.5f, 2f, 1f);
if (Motor.CharacterOverlap(
Motor.TransientPosition,
Motor.TransientRotation,
_probedColliders,
Motor.CollidableLayers,
QueryTriggerInteraction.Ignore) > 0)
{
// If obstructions, just stick to crouching dimensions
Motor.SetCapsuleDimensions(0.5f, CrouchedCapsuleHeight, CrouchedCapsuleHeight * 0.5f);
}
else
{
// If no obstructions, uncrouch
if(_crouchTransition.IsPlaying()) _crouchTransition.Cancel();
_crouchTransition = LMotion.Create(cameraTransform.localPosition.y, 1.71f, OutCrouchTime)
.WithEase(Ease.InOutQuad)
.BindToLocalPositionY(cameraTransform);
_isCrouching = false;
_isSliding = false;
IsSliding.Value = false;
_isSlideEnded = false;
_slideCurveProgress = 0f;
}
}
break;
}
}
}
public void PostGroundingUpdate(float deltaTime)
{
// Handle landing and leaving ground
if (Motor.GroundingStatus.IsStableOnGround && !Motor.LastGroundingStatus.IsStableOnGround)
{
OnLanded();
}
else if (!Motor.GroundingStatus.IsStableOnGround && Motor.LastGroundingStatus.IsStableOnGround)
{
OnLeaveStableGround();
}
}
public bool IsColliderValidForCollisions(Collider coll)
{
if (IgnoredColliders.Count == 0)
{
return true;
}
if (IgnoredColliders.Contains(coll))
{
return false;
}
return true;
}
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
ref HitStabilityReport hitStabilityReport)
{
}
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
ref HitStabilityReport hitStabilityReport)
{
}
public void AddVelocity(Vector3 velocity)
{
switch (CurrentCharacterState)
{
case CharacterState.Default:
{
_internalVelocityAdd += velocity;
break;
}
}
}
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
Vector3 atCharacterPosition, Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{
}
protected void OnLanded()
{
}
protected void OnLeaveStableGround()
{
}
public void OnDiscreteCollisionDetected(Collider hitCollider)
{
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 45d9830d22b54832ade96a92f599643a
timeCreated: 1785846388
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e882a0f0fb5d41f5a1d3b2c41d1f4097
timeCreated: 1785874067
+20
View File
@@ -0,0 +1,20 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
[InitializeOnLoad]
public class StartFromBootstrap
{
static StartFromBootstrap()
{
var scenePath = "Assets/Infrastructure/Bootstrap.unity";
var sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath);
if (sceneAsset != null)
EditorSceneManager.playModeStartScene = sceneAsset;
else
Debug.LogError("Could not find scene at " + scenePath);
}
}
#endif
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5c1b8ca63d9a4ba19fb70cb4ec7b7609
timeCreated: 1785874078
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c030bebb626a43c7acf7f3a48acc09eb
timeCreated: 1785847329
@@ -0,0 +1,51 @@
using System;
using Code.Game.Gameplay.Player.Factory;
using Code.Infrastructure.SceneLoader;
using Cysharp.Threading.Tasks;
using R3;
using Reflex.Core;
using UnityEngine;
namespace Code.Game.TestingLevel
{
[Serializable]
public struct TestingLevelData
{
public Transform playerSpawnPoint;
}
public class TestingLevelSceneInstaller: MonoBehaviour, IInstaller, IDisposable
{
private IDisposable _onContainerBuildDisposable;
[SerializeField] private TestingLevelData testingLevelData;
public void InstallBindings(ContainerBuilder containerBuilder)
{
containerBuilder.RegisterValue(testingLevelData);
_onContainerBuildDisposable = Observable
.FromEvent<Container>(
h => containerBuilder.OnContainerBuilt += h,
h => containerBuilder.OnContainerBuilt -= h)
.Subscribe(x => OnContainerBuilt(x).Forget());
}
private ISceneContainerManager _sceneContainerManager;
private async UniTaskVoid OnContainerBuilt(Container container)
{
await UniTask.Yield();
container.Resolve<IPlayerFactory>().InjectPlayer(container);
_onContainerBuildDisposable.Dispose();
_sceneContainerManager = container.Resolve<ISceneContainerManager>();
_sceneContainerManager.ScenesContainers.Add(SceneId.Testing, container);
}
public void Dispose()
{
_sceneContainerManager.ScenesContainers.Remove(SceneId.Testing);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: db514476e4864e78b2ab1734a44b450c
timeCreated: 1785847631
@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Code.D.Cleaning.CleanTools;
using Code.D.Cleaning.UI;
using Code.D.Player;
using Code.Game.Gameplay.Player;
using Code.Game.Gameplay.Player.Factory;
using Code.Infrastructure.App;
using Code.Infrastructure.Camera;
using Code.Infrastructure.Generators;
using Code.Infrastructure.Input;
using Code.Infrastructure.SceneLoader;
using Code.Infrastructure.UI;
using Code.Infrastructure.Utils;
using Cysharp.Threading.Tasks;
using KinematicCharacterController;
using ObservableCollections;
using R3;
using Reflex.Core;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Code.Game.TestingLevel
{
[State(typeof(AppStates), typeof(AppStateMachineContext))]
public partial class TestingState
{
public TestingState(AppStateMachineContext context) : base(context)
{
}
public override AppStates MyState => AppStates.Testing;
public override async UniTask EnterState()
{
Cursor.lockState = CursorLockMode.Locked;
Context.InputService.EnableMap(ActionMappings.Cleaning);
Context.AudioService.SetAudioListener(Context.Camera.Camera.GetComponent<AudioListener>());
await Context.Camera.SwitchState(CameraMode.Cleaner);
var waitForContainer =
Context.SceneContainerManager.ScenesContainers
.ObserveDictionaryAdd()
.Where(x => x.Key == SceneId.Testing)
.Take(1)
.WaitAsync()
.AsUniTask();
var cleaningPanel = (CleaningPanel) await Context.UIFactory.LoadPanel(UIPanelType.Cleaning,true);
var factoryConfig = new SingleConfig<PlayerFactorySettings>();
var cleanToolsConfigs = new MultipleConfig<CleanToolConfig>();
await UniTask.WhenAll(
factoryConfig.LoadConfig(),
cleanToolsConfigs.LoadConfigs()
);
var factory = new PlayerFactory(factoryConfig);
var player = await factory.CreatePlayer();
var playerKinematicCharacterMotor = player.GetComponent<KinematicCharacterMotor>();
var playerMovementController = player.GetComponent<PlayerMovementController>();
var playerEquipment = player.GetComponent<PlayerEquipment>();
CleanToolConfig[] toolsConfigs = cleanToolsConfigs.Configs.ToArray();
List<CleanTool> cleanTools = new();
// TODO Сделать сервис снабжения игрока
foreach (var toolConfig in toolsConfigs)
{
switch (toolConfig.ToolType)
{
case CleanToolType.Tryapka:
cleanTools.Add(new Tryapka(toolConfig.DurabilityUsageStep, toolConfig.CleanRadius, toolConfig.BrushTexture));
break;
}
}
void InstallExtra(Scene scene, ContainerBuilder builder)
{
builder.RegisterValue(factoryConfig);
builder.RegisterValue(factory, new []{typeof(IPlayerFactory)});
builder.RegisterValue(playerKinematicCharacterMotor);
builder.RegisterValue(playerMovementController);
builder.RegisterValue(playerEquipment);
builder.RegisterValue(cleanTools);
builder.RegisterValue(cleaningPanel);
}
await UniTask.WhenAll(
Context.SceneLoader
.LoadSceneAsync(
SceneId.Testing,
LoadSceneMode.Single,
true,
InstallExtra),
waitForContainer
);
var container = Context.SceneContainerManager.ScenesContainers[SceneId.Testing];
container.Resolve<KinematicCharacterMotor>()
.SetPositionAndRotation(
container.Resolve<TestingLevelData>().playerSpawnPoint.position,
container.Resolve<TestingLevelData>().playerSpawnPoint.rotation);
((CleanerCameraState)Context.Camera.CurrentBaseState)
.SetPlayer(container.Resolve<PlayerMovementController>());
}
public override async UniTask ExitState()
{
Context.InputService.DisableMap(ActionMappings.Cleaning);
await Context.SceneLoader.UnloadSceneAsync(SceneId.Testing);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7c6095424c534587b8b6ed7c758375e5
timeCreated: 1785847339
+74
View File
@@ -0,0 +1,74 @@
Shader "Custom/UVRegionHighlighter"
{
Properties
{
_HighlightMin ("Highlight Min (U, V)", Vector) = (0.2, 0.2, 0, 0)
_HighlightMax ("Highlight Max (U, V)", Vector) = (0.5, 0.5, 0, 0)
_GridCount ("Grid Count", Float) = 10.0
_HighlightColor ("Highlight Color", Color) = (1, 0, 0, 1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD1;
};
struct v2f
{
float2 uv : TEXCOORD1;
float4 vertex : SV_POSITION;
};
float4 _HighlightMin;
float4 _HighlightMax;
float _GridCount;
fixed4 _HighlightColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 uv = i.uv;
// Сетка (линии разметки)
float2 grid = frac(uv * _GridCount);
float lineMask = step(0.92, grid.x) + step(0.92, grid.y);
lineMask = saturate(lineMask);
// Проверка: находится ли пиксель в выбранных UV-координатах
bool inRegion = (uv.x >= _HighlightMin.x && uv.x <= _HighlightMax.x &&
uv.y >= _HighlightMin.y && uv.y <= _HighlightMax.y);
// Базовый цвет UV + черная сетка
fixed4 baseColor = lerp(fixed4(uv.x, uv.y, 0.0, 1.0), fixed4(0, 0, 0, 1), lineMask);
// Если пиксель внутри выделенного участка UV — подсвечиваем его
if (inRegion)
{
return lerp(_HighlightColor, fixed4(1, 1, 1, 1), lineMask * 0.5);
}
return baseColor;
}
ENDCG
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fa803291ec594773ad1056019437aa30
timeCreated: 1786007069
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 67a3491d6e6742faa2f703b763446706
timeCreated: 1785766160
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 03ec58f5292de68cb94cf053c242d44a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
using Code.Infrastructure.Audio;
using Code.Infrastructure.Camera;
using Code.Infrastructure.Input;
using Code.Infrastructure.Particles;
using Code.Infrastructure.SceneLoader;
using Code.Infrastructure.UI;
using Code.Infrastructure.Volume;
using Reflex.Attributes;
namespace Code.Infrastructure.App
{
public class AppStateMachineContext
{
[Inject] public ISceneContainerManager SceneContainerManager { get; set; }
[Inject] public ISceneLoader SceneLoader { get; set; }
[Inject] public IUIFactory UIFactory { get; set; }
[Inject] public CameraStateMachine Camera { get; set; }
[Inject] public ICameraFactory CameraFactory { get; set; }
[Inject] public IAudioService AudioService { get; set; }
[Inject] public IAudioFactory AudioFactory { get; set; }
[Inject] public IInputService InputService { get; set; }
[Inject] public IVolumeService VolumeService { get; set; }
[Inject] public IParticleLoader ParticleLoader { get; set; }
public AppStateMachine StateMachine { get; set; }
}
[Generators.StateMachine(typeof(AppStateMachineContext),typeof(AppStates))]
public partial class AppStateMachine
{
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5627248405434f028be921cb7916f823
timeCreated: 1765572329
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cc39950ba97e90dc09676d793e810207
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using Code.Infrastructure.Configs;
using Code.Infrastructure.Utils;
using Cysharp.Threading.Tasks;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace Code.Infrastructure.Audio
{
public interface IAudioFactory
{
void UnloadAudioClips(AudioPartition groupPartition);
UniTask LoadAudioClips(AudioPartition groupPartition);
public Dictionary<AudioPartition, Dictionary<int, AudioReference>> ReferenceDictionary { get; }
}
public interface IAudioFactoryInitializer
{
UniTask LoadConfigs();
}
public class AudioFactory : IAudioFactory, IDisposable, IAudioFactoryInitializer
{
private string _label;
private AsyncOperationHandle<IList<AudioInfoConfig>> _configsHandle;
public Dictionary<AudioPartition, Dictionary<int, AudioReference>> ReferenceDictionary { get; } = new();
public AudioFactory(IConfigLocator configLocator)
{
_label = configLocator.TryGetManyConfigAssetReferences<AudioInfoConfig>();
}
public async UniTask LoadConfigs()
{
if(!await AddressablesUtilities.CheckLabelExists(_label)) return;
_configsHandle = Addressables.LoadAssetsAsync<AudioInfoConfig>(_label);
await _configsHandle.ToUniTask();
foreach (AudioInfoConfig config in _configsHandle.Result)
{
if(!ReferenceDictionary.ContainsKey(config.GroupPartition))
ReferenceDictionary.Add(config.GroupPartition, new());
ReferenceDictionary[config.GroupPartition].Add(config.id, config.Reference);
}
}
public async UniTask LoadAudioClips(AudioPartition groupPartition)
{
foreach (var reference in ReferenceDictionary[groupPartition])
{
var load = reference.Value.Load();
if (load.Status == UniTaskStatus.Pending)
await load;
}
}
public void UnloadAudioClips(AudioPartition groupPartition)
{
foreach (var reference in ReferenceDictionary[groupPartition])
{
reference.Value.Unload();
}
}
public void Dispose()
{
foreach (var partition in ReferenceDictionary)
{
foreach (var reference in partition.Value)
{
reference.Value.Unload();
}
}
if(_configsHandle.IsValid()) _configsHandle.Release();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cc45c23918ed446681564b467668304b
timeCreated: 1769344269
@@ -0,0 +1,131 @@
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Random = UnityEngine.Random;
namespace Code.Infrastructure.Audio
{
[Serializable]
public abstract class AudioReference
{
public abstract UniTask Load();
public abstract void Unload();
}
[Serializable]
public class AudioReferenceLODed : AudioReference
{
private AsyncOperationHandle<AudioClip> _interiorClipHandle;
private AsyncOperationHandle<AudioClip> _closeClipHandle;
private AsyncOperationHandle<AudioClip> _distantClipHandle;
private AsyncOperationHandle<AudioClip> _farClipHandle;
public override async UniTask Load()
{
if (_interiorClipHandle.IsValid()) return;
_interiorClipHandle = Addressables.LoadAssetAsync<AudioClip>(interior);
_closeClipHandle = Addressables.LoadAssetAsync<AudioClip>(close);
_distantClipHandle = Addressables.LoadAssetAsync<AudioClip>(distant);
_farClipHandle = Addressables.LoadAssetAsync<AudioClip>(far);
await UniTask.WhenAll(
_interiorClipHandle.ToUniTask(),
_closeClipHandle.ToUniTask(),
_distantClipHandle.ToUniTask(),
_farClipHandle.ToUniTask()
);
Interior = _interiorClipHandle.Result;
Close = _closeClipHandle.Result;
Distant = _distantClipHandle.Result;
Far = _farClipHandle.Result;
}
public override void Unload()
{
if (!_interiorClipHandle.IsValid()) return;
_interiorClipHandle.Release();
_closeClipHandle.Release();
_distantClipHandle.Release();
_farClipHandle.Release();
Interior = null;
Close = null;
Distant = null;
Far = null;
}
[SerializeField] private AssetReference interior;
[SerializeField] private AssetReference close;
[SerializeField] private AssetReference distant;
[SerializeField] private AssetReference far;
public AudioClip Interior { get; private set; }
public AudioClip Close { get; private set; }
public AudioClip Distant { get; private set; }
public AudioClip Far { get; private set; }
}
[Serializable]
public class AudioReferenceSingle : AudioReference
{
private AsyncOperationHandle<AudioClip> _clipHandle;
public override async UniTask Load()
{
if (_clipHandle.IsValid()) return;
_clipHandle = Addressables.LoadAssetAsync<AudioClip>(clip);
await _clipHandle.ToUniTask();
Clip = _clipHandle.Result;
}
public override void Unload()
{
if (!_clipHandle.IsValid()) return;
_clipHandle.Release();
Clip = null;
}
[SerializeField] private AssetReference clip;
public AudioClip Clip { get; private set; }
}
[Serializable]
public class AudioReferenceRandom : AudioReference
{
private AsyncOperationHandle<AudioClip>[] _clipHandles;
public override async UniTask Load()
{
if (_clipHandles != null) return;
_clipHandles = new AsyncOperationHandle<AudioClip>[clips.Length];
for (var i = 0; i < clips.Length; i++)
{
_clipHandles[i] = Addressables.LoadAssetAsync<AudioClip>(clips[i]);
await _clipHandles[i].ToUniTask();
}
}
public override void Unload()
{
if (_clipHandles == null) return;
for (var i = 0; i < clips.Length; i++) Addressables.Release(_clipHandles[i]);
}
[SerializeField] private AssetReference[] clips;
public AudioClip Clip => _clipHandles[Random.Range(0, _clipHandles.Length)].Result;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e4423af0d471f60ef89f8df279e82af2
@@ -0,0 +1,14 @@
using Code.Infrastructure.Generators;
using UnityEngine;
namespace Code.Infrastructure.Audio
{
[Config("Configs/Audio/AudioInfo", true, true)]
public abstract class AudioInfoConfig : ScriptableObject
{
public AudioPartition GroupPartition;
public int id;
public abstract AudioReference Reference { get; }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 24ed33a7f788429eb32b93b79fcb84bd
timeCreated: 1785871082
@@ -0,0 +1,11 @@
using UnityEngine;
namespace Code.Infrastructure.Audio
{
[CreateAssetMenu(fileName = "AudioInfoLODed", menuName = "Configs/Audio/AudioInfoLODed")]
public class AudioInfoLODedConfig : AudioInfoConfig
{
public AudioReferenceLODed reference;
public override AudioReference Reference => reference;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 97e55393d63f871ac8472894d1d9f627
@@ -0,0 +1,11 @@
using UnityEngine;
namespace Code.Infrastructure.Audio
{
[CreateAssetMenu(fileName = "AudioInfoRandom", menuName = "Configs/Audio/AudioInfoRandom")]
public class AudioInfoRandomConfig : AudioInfoConfig
{
public AudioReferenceRandom reference;
public override AudioReference Reference => reference;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fb3ca31ee67afb7818583cdd1dc6a43d
@@ -0,0 +1,11 @@
using UnityEngine;
namespace Code.Infrastructure.Audio
{
[CreateAssetMenu(fileName = "AudioInfoSingle", menuName = "Configs/Audio/AudioInfoSingle")]
public class AudioInfoSingleConfig : AudioInfoConfig
{
public AudioReferenceSingle reference;
public override AudioReference Reference => reference;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c3a6a54ee0524ae96905e8f5293922fd
@@ -0,0 +1,13 @@
using Code.Infrastructure.Generators;
using UnityEngine;
using UnityEngine.Audio;
namespace Code.Infrastructure.Audio
{
[Config("Configs/Audio/AudioMixer")]
[CreateAssetMenu(fileName = "AudioMixerSettings", menuName = "Configs/Audio/AudioMixer")]
public class AudioMixerSettings : ScriptableObject
{
public AudioMixer mixerAsset;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 89e2ff4d31a24b78b40d9c4e20e1f1bc
timeCreated: 1785871073
@@ -0,0 +1,366 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Code.Infrastructure.Configs;
using Cysharp.Threading.Tasks;
using LitMotion;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Audio;
namespace Code.Infrastructure.Audio
{
public interface IAudioService
{
UniTaskVoid PlayOneShot(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
Vector3 position = new Vector3(),
AudioSourceType sourceType = AudioSourceType.NoBlend,
AudioSource audioSource = null, bool isLOD = false);
UniTaskVoid PlayRepeatedly(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
Vector3 position = new Vector3(),
AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null,
CancellationTokenSource cancellationTokenSource = null);
void CancelMusic(AudioPartition groupPartition, int id);
void ReleaseCancellationSource(AudioPartition groupPartition, int id);
UniTask ApplyParameter(string effectName, float value, float time, Action callback = null,
CancellationTokenSource cts = null);
UniTask ChangeGroupVolume(AudioGroupType groupType, float value, float time, Action callback = null,
CancellationTokenSource cts = null);
void SetAudioListener(AudioListener listener);
UniTaskVoid PlayRepeatedly(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
Transform parent,
AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null,
CancellationTokenSource cancellationTokenSource = null);
UniTaskVoid PlayOneShot(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
Transform parent,
AudioSourceType sourceType = AudioSourceType.NoBlend,
AudioSource audioSource = null);
}
public interface IAudioServiceInitializer
{
UniTask LoadMixerAsset();
}
public partial class AudioService : IAudioService, IAudioServiceInitializer
{
private IAudioFactory _audioFactory;
private IAudioSourcePool _pool;
private AudioMixer _mixer;
private AudioListener _listener;
private Dictionary<AudioGroupType, AudioMixerGroup> _audioMixerGroups = new();
public AudioService(IAudioFactory audioFactory, IAudioSourcePool pool,
IConfigLocator configLocator)
{
_audioFactory = audioFactory;
_pool = pool;
_mixerConfig = configLocator.TryGetConfigAssetReference<AudioMixerSettings>();
}
public void SetAudioListener(AudioListener listener)
{
if(_listener)
_listener.enabled = false;
_listener = listener;
_listener.enabled = true;
}
private partial void InitializeMixer();
public async UniTask LoadMixerAsset()
{
var audioMixer = await Addressables.LoadAssetAsync<AudioMixerSettings>(_mixerConfig);
_mixer = audioMixer.mixerAsset;
InitializeMixer();
}
public async UniTaskVoid PlayOneShot(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
Vector3 position = new Vector3(),
AudioSourceType sourceType = AudioSourceType.NoBlend,
AudioSource audioSource = null, bool isLOD = false)
{
if (!CheckClip(groupPartition, id)) return;
if (!audioSource) audioSource = _pool.GetSource(sourceType);
audioSource.transform.position = position;
audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType];
if (sourceType == AudioSourceType.NoBlend)
audioSource.PlayOneShot(GetSingleClip(groupPartition, id));
if (sourceType == AudioSourceType.Full3D)
{
audioSource.PlayOneShot(isLOD
? GetLODClip(groupPartition, id, GetLODLevelOnPosition(position))
: GetSingleClip(groupPartition, id));
}
await UniTask.NextFrame();
await UniTask.WaitUntilValueChanged(audioSource, _ => !audioSource.isPlaying);
_pool.ReleaseSource(audioSource);
}
public async UniTaskVoid PlayOneShot(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
Transform parent,
AudioSourceType sourceType = AudioSourceType.NoBlend,
AudioSource audioSource = null)
{
if (!CheckClip(groupPartition, id)) return;
if (!audioSource) audioSource = _pool.GetSource(sourceType);
audioSource.GetComponent<AudioSourceFollower>().FollowTarget = parent;
audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType];
if (sourceType == AudioSourceType.NoBlend)
audioSource.PlayOneShot(GetSingleClip(groupPartition, id));
if (sourceType == AudioSourceType.Full3D)
audioSource.PlayOneShot(GetLODClip(groupPartition, id, GetLODLevelOnPosition(parent.position)));
await UniTask.NextFrame();
await UniTask.WaitUntilValueChanged(audioSource, _ => !audioSource.isPlaying);
_pool.ReleaseSource(audioSource);
}
private Dictionary<AudioPartitionIdPair, CancellationTokenSource> _cancellationTokenSources = new();
private AssetReference _mixerConfig;
public void CancelMusic(AudioPartition groupPartition, int id)
{
var key = new AudioPartitionIdPair(groupPartition, id);
if (!_cancellationTokenSources.ContainsKey(key)) return;
_cancellationTokenSources[key].Cancel();
_cancellationTokenSources[key].Dispose();
_cancellationTokenSources.Remove(key);
}
public void ReleaseCancellationSource(AudioPartition groupPartition, int id)
{
_cancellationTokenSources.Remove(new AudioPartitionIdPair(groupPartition, id));
}
public async UniTaskVoid PlayRepeatedly(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
Vector3 position = new(),
AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null,
CancellationTokenSource cancellationTokenSource = null)
{
if (!CheckClip(groupPartition, id)) return;
if (cancellationTokenSource == null)
{
cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSources[new AudioPartitionIdPair(groupPartition, id)] = cancellationTokenSource;
}
if (!audioSource) audioSource = _pool.GetSource(sourceType);
if (!audioSource.resource)
{
if (sourceType == AudioSourceType.NoBlend)
audioSource.resource = GetSingleClip(groupPartition, id);
if (sourceType == AudioSourceType.Full3D)
audioSource.resource = GetLODClip(groupPartition, id, GetLODLevelOnPosition(position));
}
audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType];
audioSource.Play();
var (isCanceled, _) = await UniTask.WaitUntilValueChanged(audioSource,
_ => !audioSource.isPlaying,
cancellationToken: cancellationTokenSource.Token).SuppressCancellationThrow();
if (isCanceled)
{
cancellationTokenSource.Dispose();
_pool.ReleaseSource(audioSource);
ReleaseCancellationSource(groupPartition, id);
return;
}
PlayRepeatedly(groupPartition, id, AudioGroupType.Music, position, sourceType, audioSource,
cancellationTokenSource)
.Forget();
}
public async UniTaskVoid PlayRepeatedly(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
Transform parent,
AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null,
CancellationTokenSource cancellationTokenSource = null)
{
if (!CheckClip(groupPartition, id)) return;
if (cancellationTokenSource == null)
{
cancellationTokenSource = new CancellationTokenSource();
_cancellationTokenSources[new AudioPartitionIdPair(groupPartition, id)] = cancellationTokenSource;
}
if (!audioSource)
{
audioSource = _pool.GetSource(sourceType);
audioSource.GetComponent<AudioSourceFollower>().FollowTarget = parent;
}
if (!audioSource.resource)
{
if (sourceType == AudioSourceType.NoBlend)
audioSource.resource = GetSingleClip(groupPartition, id);
if (sourceType == AudioSourceType.Full3D)
audioSource.resource = GetLODClip(groupPartition, id, GetLODLevelOnPosition(parent.position));
}
audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType];
audioSource.Play();
var (isCanceled, _) = await UniTask.WaitUntilValueChanged(audioSource,
_ => !audioSource.isPlaying,
cancellationToken: cancellationTokenSource.Token).SuppressCancellationThrow();
if (isCanceled)
{
cancellationTokenSource.Dispose();
_pool.ReleaseSource(audioSource);
ReleaseCancellationSource(groupPartition, id);
return;
}
PlayRepeatedly(groupPartition, id, AudioGroupType.Music, parent, sourceType, audioSource,
cancellationTokenSource)
.Forget();
}
private AudioLODLevel GetLODLevelOnPosition(Vector3 position)
{
var distance = Vector3.Distance(position, _listener.transform.position);
AudioLODLevel lodLevel;
if (distance < 5f) lodLevel = AudioLODLevel.Interior;
else if (distance < 20f) lodLevel = AudioLODLevel.Close;
else if (distance < 50f) lodLevel = AudioLODLevel.Distant;
else lodLevel = AudioLODLevel.Far;
return lodLevel;
}
public AudioClip GetSingleClip(AudioPartition groupPartition, int id)
{
var reference = _audioFactory.ReferenceDictionary[groupPartition][id];
var asSingle = reference as AudioReferenceSingle;
if(asSingle != null) return asSingle.Clip;
return ((AudioReferenceRandom)reference).Clip;
}
public AudioClip GetLODClip(AudioPartition groupPartition, int id, AudioLODLevel lodLevel)
{
switch (lodLevel)
{
case AudioLODLevel.Interior:
return ((AudioReferenceLODed)_audioFactory.ReferenceDictionary[groupPartition][id]).Interior;
case AudioLODLevel.Close:
return ((AudioReferenceLODed)_audioFactory.ReferenceDictionary[groupPartition][id]).Close;
case AudioLODLevel.Distant:
return ((AudioReferenceLODed)_audioFactory.ReferenceDictionary[groupPartition][id]).Distant;
case AudioLODLevel.Far:
return ((AudioReferenceLODed)_audioFactory.ReferenceDictionary[groupPartition][id]).Far;
}
return null;
}
public bool CheckClip(AudioPartition groupPartition, int id)
{
return _audioFactory.ReferenceDictionary.ContainsKey(groupPartition)
&& _audioFactory.ReferenceDictionary[groupPartition].ContainsKey(id);
}
public async UniTask ApplyParameter(string effectName,
float value,
float time,
Action callback = null,
CancellationTokenSource cts = null)
{
_mixer.GetFloat(effectName, out float startValue);
if (time == 0f) _mixer.SetFloat(effectName, value);
else
{
// TODO Проверить отмену
if (cts != null)
await LMotion.Create(startValue, value, time)
.WithOnComplete(callback)
.WithOnCancel(() => _mixer.SetFloat(effectName, value))
.Bind(x => _mixer.SetFloat(effectName, x))
.ToUniTask(cts.Token);
else
await LMotion.Create(startValue, value, time)
.WithOnComplete(callback)
.WithOnCancel(() => _mixer.SetFloat(effectName, value))
.Bind(x => _mixer.SetFloat(effectName, x))
.ToUniTask();
}
}
private Dictionary<AudioGroupType, CancellationTokenSource> _volumeCancellationSources = new();
// TODO Очередь принятия параметров
public async UniTask ChangeGroupVolume(
AudioGroupType groupType,
float value,
float time,
Action callback = null,
CancellationTokenSource cts = null)
{
if (_volumeCancellationSources.ContainsKey(groupType)) _volumeCancellationSources[groupType].Cancel();
_volumeCancellationSources[groupType] = cts ?? new CancellationTokenSource();
await ApplyParameter(_audioMixerGroups[groupType].name, Mathf.Log10(value == 0f ? 0.001f : value) * 20,
time,
callback, cts);
}
}
public struct AudioPartitionIdPair : IEquatable<AudioPartitionIdPair>
{
public AudioPartition AudioPartition;
public int ID;
public AudioPartitionIdPair(AudioPartition audioPartition, int id)
{
AudioPartition = audioPartition;
ID = id;
}
public bool Equals(AudioPartitionIdPair other)
{
return AudioPartition == other.AudioPartition && ID == other.ID;
}
public override bool Equals(object obj)
{
return obj is AudioPartitionIdPair other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine((int)AudioPartition, ID);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e8426c9e925a431aa73069c49d6a71c0
timeCreated: 1769446252
@@ -0,0 +1,15 @@
using System;
using UnityEngine;
namespace Code.Infrastructure.Audio
{
public class AudioSourceFollower : MonoBehaviour
{
[NonSerialized]
public Transform FollowTarget;
private void Update()
{
if(FollowTarget) transform.SetPositionAndRotation(FollowTarget.position, FollowTarget.rotation);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7f6debe328864a5a88509519354fcfc7
timeCreated: 1776704631
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Code.Infrastructure.Configs;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Pool;
using UnityEngine.ResourceManagement.AsyncOperations;
using Object = UnityEngine.Object;
namespace Code.Infrastructure.Audio
{
public interface IAudioSourcePoolInitializer
{
UniTask LoadConfigs();
}
public interface IAudioSourcePool
{
AudioSource GetSource(AudioSourceType sourceType);
void ReleaseSource(AudioSource source);
}
public class AudioSourcePool : IAudioSourcePool, IAudioSourcePoolInitializer, IDisposable
{
// The pool holds plain GameObjects (you can swap this for any component type).
private IObjectPool<AudioSource> _pool;
private GameObject _sourcePrefab;
private Dictionary<AudioSourceType, AudioSourceSettings> _settings;
private string _configsLabel;
private AsyncOperationHandle<IList<AudioSourceSettings>> _handles;
private AssetReference _sourcePrefabConfigReference;
private AsyncOperationHandle<AudioSourcePrefab> _prefabConfigHandle;
private AsyncOperationHandle<GameObject> _prefabHandle;
public AudioSourcePool(IConfigLocator configLocator)
{
_configsLabel = configLocator.TryGetManyConfigAssetReferences<AudioSourceSettings>();
_sourcePrefabConfigReference = configLocator.TryGetConfigAssetReference<AudioSourcePrefab>();
}
public async UniTask LoadConfigs()
{
_prefabConfigHandle = Addressables.LoadAssetAsync<AudioSourcePrefab>(_sourcePrefabConfigReference);
_handles = Addressables.LoadAssetsAsync<AudioSourceSettings>(_configsLabel);
await UniTask.WhenAll(
_prefabConfigHandle.ToUniTask(),
_handles.ToUniTask());
_prefabHandle = Addressables.LoadAssetAsync<GameObject>(_prefabConfigHandle.Result.sourcePrefab);
_settings = _handles.Result.ToDictionary(x => x.sourceType);
await _prefabHandle.ToUniTask();
_pool = new ObjectPool<AudioSource>(
createFunc: CreateItem,
// actionOnGet: OnGet,
actionOnRelease: OnRelease,
actionOnDestroy: OnDestroyItem,
collectionCheck: true, // helps catch double-release mistakes
defaultCapacity: 10,
maxSize: 20
);
}
// Creates a new pooled GameObject the first time (and whenever the pool needs more).
private AudioSource CreateItem()
{
var audioSource = Object.Instantiate(_prefabHandle.Result).GetComponent<AudioSource>();
audioSource.name = "PooledAudioSource";
Object.DontDestroyOnLoad(audioSource);
return audioSource;
}
// Called when an item is taken from the pool.
private void OnGet(AudioSource source)
{
//source.gameObject.SetActive(true);
}
// Called when an item is returned to the pool.
private void OnRelease(AudioSource source)
{
if (!source) return;
source.Stop();
source.resource = null;
source.GetComponent<AudioSourceFollower>().FollowTarget = null;
//source.gameObject.SetActive(false);
}
// Called when the pool decides to destroy an item (e.g., above max size).
private void OnDestroyItem(AudioSource source)
{
Object.Destroy(source.gameObject);
}
public AudioSource GetSource(AudioSourceType sourceType)
{
var source = _pool.Get();
source.spatialBlend = _settings[sourceType].spatialBlend;
return source;
}
public void ReleaseSource(AudioSource source) => _pool.Release(source);
public void Dispose()
{
Addressables.Release(_handles);
Addressables.Release(_prefabConfigHandle);
if (_sourcePrefab) Addressables.Release(_sourcePrefab);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 246ae406fabc4ca791673d3f38cfe1c5
timeCreated: 1769448041
@@ -0,0 +1,13 @@
using Code.Infrastructure.Generators;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Infrastructure.Audio
{
[Config("Configs/Audio/AudioSourcePrefab")]
[CreateAssetMenu(fileName = "AudioSourcePrefab", menuName = "Configs/Audio/AudioSourcePrefab")]
public partial class AudioSourcePrefab : ScriptableObject
{
public AssetReference sourcePrefab;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9e812cfdf0544504b47cfe48b621ac4e
timeCreated: 1785852790
@@ -0,0 +1,15 @@
using Code.Infrastructure.Generators;
using UnityEngine;
namespace Code.Infrastructure.Audio
{
[CreateAssetMenu(fileName = "AudioSourceSettings", menuName = "Configs/Audio/AudioSourceSettings")]
[Config("Configs/Audio/AudioSourceSettings", true)]
public class AudioSourceSettings : ScriptableObject
{
// Дополнять по мере необходимости
public float spatialBlend;
public AudioSourceType sourceType;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fdbfd61d34d228d088f8be38588cd571
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5f30c588256ece023ad9a01d0f51d747
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,68 @@
using System;
using Code.Infrastructure.Configs;
using Cysharp.Threading.Tasks;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Rendering.Universal;
using UnityEngine.ResourceManagement.AsyncOperations;
using Object = UnityEngine.Object;
namespace Code.Infrastructure.Camera
{
public interface ICameraFactory
{
UniTask<CameraStateMachineContext.CameraLoadedData> CreateCameraStateMachine();
void CreateCameraStateMachineRunner(CameraStateMachine cameraStateMachine);
}
public class CameraFactory : IDisposable, ICameraFactory
{
private AsyncOperationHandle<GameCamera> _configHandle;
private GameCamera _config;
private AssetReference _gameCameraConfigReference;
private UnityEngine.Camera _camera;
public CameraFactory(IConfigLocator configLocator)
{
_gameCameraConfigReference = configLocator.TryGetConfigAssetReference<GameCamera>();
}
public async UniTask<CameraStateMachineContext.CameraLoadedData> CreateCameraStateMachine()
{
_configHandle = Addressables.LoadAssetAsync<GameCamera>(_gameCameraConfigReference);
await _configHandle.ToUniTask();
_config = _configHandle.Result;
var cameraObject = new GameObject { name = "Camera" };
cameraObject.AddComponent<AudioListener>();
_camera = cameraObject.AddComponent<UnityEngine.Camera>();
var universalCameraData = _camera.GetUniversalAdditionalCameraData();
universalCameraData.renderPostProcessing = _config.PostProcessing;
_camera.fieldOfView = _config.FieldOfView;
_camera.nearClipPlane = _config.NearClip;
_camera.farClipPlane = _config.FarClip;
_camera.clearFlags = _config.ClearFlags;
Object.DontDestroyOnLoad(cameraObject);
return new CameraStateMachineContext.CameraLoadedData(_camera);
}
public void CreateCameraStateMachineRunner(CameraStateMachine cameraStateMachine)
{
var runner = _camera.AddComponent<CameraStateMachineRunner>();
runner.SetStateMachine(cameraStateMachine);
}
public void Dispose()
{
Addressables.Release(_configHandle);
Addressables.ReleaseInstance(_camera.gameObject);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4c05e01628b742be94dfd0bbbdfb87fa
timeCreated: 1767302222
@@ -0,0 +1,36 @@
using Code.Infrastructure.Camera.CameraStates;
using Code.Infrastructure.Input;
using Reflex.Attributes;
namespace Code.Infrastructure.Camera
{
public enum CameraMode
{
None,
Cleaner
}
public class CameraStateMachineContext
{
public class CameraLoadedData
{
public UnityEngine.Camera Camera;
public CameraLoadedData(UnityEngine.Camera camera)
{
Camera = camera;
}
}
public CameraLoadedData cameraLoadedData;
[Inject] public IInputService InputService;
}
[Generators.StateMachine(typeof(CameraStateMachineContext), typeof(CameraMode))]
public partial class CameraStateMachine
{
public UnityEngine.Camera Camera => StateMachineContext.cameraLoadedData.Camera;
public void Update() => ((BaseCameraState)CurrentBaseState).Update();
public void LateUpdate() => ((BaseCameraState)CurrentBaseState).LateUpdate();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b220b02510c34ea08257cc951d949fd7
timeCreated: 1761671720
@@ -0,0 +1,15 @@
using UnityEngine;
namespace Code.Infrastructure.Camera
{
public class CameraStateMachineRunner : MonoBehaviour
{
private CameraStateMachine _cameraStateMachine;
public void SetStateMachine(CameraStateMachine cameraStateMachine) => _cameraStateMachine = cameraStateMachine;
private void Update() => _cameraStateMachine.Update();
private void LateUpdate() => _cameraStateMachine.LateUpdate();
}
}
Loaded 100 of 999 files, more files were not shown because too many files have changed in this diff. Show more