Skip to content

Commit 77bc0aa

Browse files
committed
Оптимизация
1 parent a22d389 commit 77bc0aa

10 files changed

Lines changed: 790 additions & 288 deletions

BulletShrapnelLogic.cs

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,28 @@ namespace ScavShrapnelMod
77
/// <summary>
88
/// Осколки от пуль при попадании в металлический блок.
99
///
10+
/// Все числовые параметры читаются из <see cref="ShrapnelConfig"/> (секция Bullets).
11+
///
1012
/// Механика:
1113
/// 1. Пуля попадает в блок (TurretScript.Shoot → Postfix)
12-
/// 2. Делаем raycast по направлению выстрела
14+
/// 2. Raycast по направлению выстрела
1315
/// 3. Если попали в металлический блок → 1-3 мелких осколка
1416
/// 4. Осколки летят от точки попадания + рандомный разброс
1517
///
1618
/// Ограничения производительности:
17-
/// - Макс 1 спавн за MinFramesBetweenSpawns кадров
18-
/// - Макс 3 осколка за спавн
19+
/// - Throttle по кадрам (конфигурируемый)
20+
/// - Макс осколков за спавн (конфигурируемый)
1921
/// - Только metallic блоки
20-
/// - Raycast макс RaycastDistance единиц
22+
/// - Raycast макс 200 единиц
2123
/// </summary>
2224
public static class BulletShrapnelLogic
2325
{
24-
// Константы
25-
2626
/// <summary>Дистанция raycast. Совпадает с TurretScript.Shoot (200f).</summary>
2727
private const float RaycastDistance = 200f;
2828

2929
/// <summary>Имя слоя Ground в Unity.</summary>
3030
private const string GroundLayerName = "Ground";
3131

32-
/// <summary>Минимальный интервал между спавнами (кадры).</summary>
33-
private const int MinFramesBetweenSpawns = 3;
34-
35-
/// <summary>Базовая скорость осколков от пуль (м/с).</summary>
36-
private const float BaseSpeed = 25;
37-
38-
/// <summary>Количество осколков: min inclusive.</summary>
39-
private const int MinFragments = 1;
40-
41-
/// <summary>Количество осколков: max exclusive.</summary>
42-
private const int MaxFragmentsExclusive = 4;
43-
44-
/// <summary>Искры: min inclusive.</summary>
45-
private const int MinSparks = 4;
46-
47-
/// <summary>Искры: max exclusive.</summary>
48-
private const int MaxSparksExclusive = 8;
49-
50-
/// <summary>Множитель масштаба (мельче чем от взрыва).</summary>
51-
private const float ScaleMultiplier = 0.72f;
52-
53-
/// <summary>Множитель нагрева (менее горячие).</summary>
54-
private const float HeatMultiplier = 0.5f;
55-
5632
/// <summary>Шанс что осколок Hot vs Medium.</summary>
5733
private const float HotWeightChance = 0.6f;
5834

@@ -72,23 +48,22 @@ private static int GroundMask
7248

7349
/// <summary>
7450
/// Точка входа. Вызывается из Postfix патча TurretScript.Shoot.
51+
/// Параметры читаются из конфига.
7552
/// </summary>
7653
public static void TrySpawnFromBullet(FireInfo info)
7754
{
7855
try
7956
{
80-
// Throttle
8157
int frame = Time.frameCount;
82-
if (frame - _lastSpawnFrame < MinFramesBetweenSpawns) return;
58+
if (frame - _lastSpawnFrame < ShrapnelConfig.BulletMinFramesBetweenSpawns.Value)
59+
return;
8360

8461
Vector2 origin = info.pos;
8562
Vector2 direction = info.dir;
8663

87-
// Raycast по Ground
8864
RaycastHit2D hit = Physics2D.Raycast(origin, direction, RaycastDistance, GroundMask);
8965
if (!hit.collider) return;
9066

91-
// Проверка металлического блока
9267
Vector2 blockSamplePos = hit.point + direction * 0.1f;
9368
Vector2Int blockPos;
9469
try
@@ -105,18 +80,22 @@ public static void TrySpawnFromBullet(FireInfo info)
10580

10681
_lastSpawnFrame = frame;
10782

108-
// Детерминированный RNG
10983
int seed = unchecked(
11084
(int)(hit.point.x * 10000f) * 397 ^
11185
(int)(hit.point.y * 10000f) ^
11286
frame);
11387
System.Random rng = new System.Random(seed);
11488

115-
int fragmentCount = rng.Range(MinFragments, MaxFragmentsExclusive);
89+
int fragmentCount = rng.Range(
90+
ShrapnelConfig.BulletFragmentsMin.Value,
91+
ShrapnelConfig.BulletFragmentsMax.Value);
92+
93+
// Применяем глобальный множитель количества
94+
fragmentCount = Mathf.Max(1,
95+
Mathf.RoundToInt(fragmentCount * ShrapnelConfig.SpawnCountMultiplier.Value));
96+
11697
for (int i = 0; i < fragmentCount; i++)
117-
{
11898
SpawnBulletFragment(hit.point, hit.normal, rng, i);
119-
}
12099

121100
SpawnBulletSparks(hit.point, hit.normal, rng);
122101
}
@@ -128,13 +107,19 @@ public static void TrySpawnFromBullet(FireInfo info)
128107

129108
/// <summary>
130109
/// Спавнит один мелкий осколок от попадания пули.
110+
/// Регистрирует в <see cref="DebrisTracker"/>.
131111
/// </summary>
132112
private static void SpawnBulletFragment(Vector2 hitPoint, Vector2 hitNormal,
133113
System.Random rng, int index)
134114
{
135115
Material litMat = ShrapnelVisuals.LitMaterial;
136116
if (litMat == null) return;
137117

118+
float scaleMultiplier = ShrapnelConfig.BulletScaleMultiplier.Value;
119+
float heatMultiplier = ShrapnelConfig.BulletHeatMultiplier.Value;
120+
float baseSpeed = ShrapnelConfig.BulletBaseSpeed.Value;
121+
float globalMaxSpeed = ShrapnelConfig.GlobalMaxSpeed.Value;
122+
138123
ShrapnelWeight weight = rng.NextFloat() < HotWeightChance
139124
? ShrapnelWeight.Hot
140125
: ShrapnelWeight.Medium;
@@ -149,15 +134,15 @@ private static void SpawnBulletFragment(Vector2 hitPoint, Vector2 hitNormal,
149134
obj.transform.position = hitPoint + rng.InsideUnitCircle() * 0.1f;
150135
obj.layer = 0;
151136

152-
float scale = ShrapnelFactory.ScaleForWeight(weight, rng) * ScaleMultiplier;
137+
float scale = ShrapnelFactory.ScaleForWeight(weight, rng) * scaleMultiplier;
153138
obj.transform.localScale = Vector3.one * scale;
154139

155140
SpriteRenderer sr = obj.AddComponent<SpriteRenderer>();
156141
sr.sprite = sprite;
157142
sr.sortingOrder = 10;
158143
sr.sharedMaterial = litMat;
159144

160-
float heat = ShrapnelFactory.HeatForWeight(weight) * HeatMultiplier;
145+
float heat = ShrapnelFactory.HeatForWeight(weight) * heatMultiplier;
161146
sr.color = Color.Lerp(
162147
ShrapnelVisuals.GetColdColor(ShrapnelProjectile.ShrapnelType.Metal),
163148
ShrapnelVisuals.GetHotColor(), heat);
@@ -185,19 +170,24 @@ private static void SpawnBulletFragment(Vector2 hitPoint, Vector2 hitNormal,
185170
dir.Normalize();
186171

187172
float speed = Mathf.Min(
188-
BaseSpeed * rng.Range(0.5f, 1.5f),
189-
ShrapnelSpawnLogic.GlobalMaxSpeed);
173+
baseSpeed * rng.Range(0.5f, 1.5f),
174+
globalMaxSpeed);
190175

191176
rb.AddForce(dir * speed * rb.mass, ForceMode2D.Impulse);
192177
rb.AddTorque(rng.Range(-200f, 200f));
178+
179+
DebrisTracker.Register(obj);
193180
}
194181

195182
/// <summary>
196183
/// Спавнит визуальные искры при попадании пули в металл.
184+
/// Количество берётся из конфига.
197185
/// </summary>
198186
private static void SpawnBulletSparks(Vector2 hitPoint, Vector2 hitNormal, System.Random rng)
199187
{
200-
int sparkCount = rng.Range(MinSparks, MaxSparksExclusive);
188+
int sparkCount = rng.Range(
189+
ShrapnelConfig.BulletSparksMin.Value,
190+
ShrapnelConfig.BulletSparksMax.Value);
201191

202192
for (int i = 0; i < sparkCount; i++)
203193
{

DebrisTracker.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System.Collections.Generic;
2+
using UnityEngine;
3+
4+
namespace ScavShrapnelMod
5+
{
6+
/// <summary>
7+
/// Глобальный трекер живых debris/stuck объектов.
8+
///
9+
/// При превышении лимита (<see cref="ShrapnelConfig.MaxAliveDebris"/>)
10+
/// удаляет старейшие объекты (FIFO).
11+
///
12+
/// Предотвращает неограниченный рост GameObject'ов при серии взрывов.
13+
///
14+
/// Реализация: List с удалением с начала.
15+
/// Для 800 элементов O(n) RemoveAt(0) приемлемо (~0.01ms).
16+
/// </summary>
17+
public static class DebrisTracker
18+
{
19+
/// <summary>
20+
/// Список живых debris. Новые добавляются в конец, старые удаляются с начала.
21+
/// Null-элементы (уничтоженные Unity) очищаются периодически.
22+
/// </summary>
23+
private static readonly List<GameObject> _alive = new List<GameObject>();
24+
25+
/// <summary>Счётчик для периодической очистки null-ов.</summary>
26+
private static int _cleanupCounter;
27+
28+
/// <summary>Очистка null каждые N регистраций.</summary>
29+
private const int CleanupInterval = 50;
30+
31+
/// <summary>
32+
/// Регистрирует новый debris-объект.
33+
/// Если лимит превышен — удаляет старейший.
34+
/// </summary>
35+
/// <param name="obj">GameObject осколка/debris.</param>
36+
public static void Register(GameObject obj)
37+
{
38+
if (obj == null) return;
39+
40+
_alive.Add(obj);
41+
_cleanupCounter++;
42+
43+
// Периодическая очистка мёртвых ссылок
44+
if (_cleanupCounter >= CleanupInterval)
45+
{
46+
_cleanupCounter = 0;
47+
PurgeNulls();
48+
}
49+
50+
int max = ShrapnelConfig.MaxAliveDebris.Value;
51+
while (_alive.Count > max)
52+
{
53+
// Удаляем старейший (с начала списка)
54+
GameObject oldest = _alive[0];
55+
_alive.RemoveAt(0);
56+
57+
// Unity может уже уничтожить объект
58+
if (oldest != null)
59+
Object.Destroy(oldest);
60+
}
61+
}
62+
63+
/// <summary>
64+
/// Удаляет все null-ссылки из списка.
65+
/// Unity уничтожает GameObject, но наша ссылка остаётся.
66+
///
67+
/// Итерация с конца для безопасного удаления.
68+
/// </summary>
69+
private static void PurgeNulls()
70+
{
71+
for (int i = _alive.Count - 1; i >= 0; i--)
72+
{
73+
// Unity overloads == null for destroyed objects
74+
if (_alive[i] == null)
75+
_alive.RemoveAt(i);
76+
}
77+
}
78+
79+
/// <summary>
80+
/// Текущее количество отслеживаемых объектов (включая потенциальные null).
81+
/// </summary>
82+
public static int Count => _alive.Count;
83+
84+
/// <summary>
85+
/// Принудительно очищает все отслеживаемые объекты.
86+
/// Полезно при перезагрузке сцены.
87+
/// </summary>
88+
public static void Clear()
89+
{
90+
for (int i = 0; i < _alive.Count; i++)
91+
{
92+
if (_alive[i] != null)
93+
Object.Destroy(_alive[i]);
94+
}
95+
_alive.Clear();
96+
_cleanupCounter = 0;
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)