Add new materials for Lava and Laser; update Floor Tile Rule and Main Palette; enhance LightDimmer and PickableProjectile scripts with additional comments and logic

This commit is contained in:
Данил Ярош 2025-10-20 16:59:23 +03:00
parent 505da61069
commit 46b8ed61cb
13 changed files with 4935 additions and 2525 deletions

View File

@ -215,8 +215,65 @@ MonoBehaviour:
m_SourceFontFilePath:
m_AtlasPopulationMode: 1
InternalDynamicOS: 0
m_GlyphTable: []
m_CharacterTable: []
m_GlyphTable:
- m_Index: 2102
m_Metrics:
m_Width: 52
m_Height: 22
m_HorizontalBearingX: 17
m_HorizontalBearingY: 25
m_HorizontalAdvance: 86
m_GlyphRect:
m_X: 10
m_Y: 10
m_Width: 52
m_Height: 22
m_Scale: 1
m_AtlasIndex: 0
m_ClassDefinitionType: 0
- m_Index: 2104
m_Metrics:
m_Width: 52
m_Height: 22
m_HorizontalBearingX: 17
m_HorizontalBearingY: 25
m_HorizontalAdvance: 86
m_GlyphRect:
m_X: 81
m_Y: 10
m_Width: 52
m_Height: 22
m_Scale: 1
m_AtlasIndex: 0
m_ClassDefinitionType: 0
- m_Index: 2103
m_Metrics:
m_Width: 21
m_Height: 51
m_HorizontalBearingX: 11
m_HorizontalBearingY: 48
m_HorizontalAdvance: 43
m_GlyphRect:
m_X: 152
m_Y: 10
m_Width: 21
m_Height: 51
m_Scale: 1
m_AtlasIndex: 0
m_ClassDefinitionType: 0
m_CharacterTable:
- m_ElementType: 1
m_Unicode: 8592
m_GlyphIndex: 2102
m_Scale: 1
- m_ElementType: 1
m_Unicode: 8594
m_GlyphIndex: 2104
m_Scale: 1
- m_ElementType: 1
m_Unicode: 8593
m_GlyphIndex: 2103
m_Scale: 1
m_AtlasTextures:
- {fileID: 28268798066460806}
m_AtlasTextureIndex: 0
@ -227,11 +284,31 @@ MonoBehaviour:
m_AtlasHeight: 512
m_AtlasPadding: 9
m_AtlasRenderMode: 4169
m_UsedGlyphRects: []
m_FreeGlyphRects:
m_UsedGlyphRects:
- m_X: 0
m_Y: 0
m_Width: 71
m_Height: 41
- m_X: 71
m_Y: 0
m_Width: 71
m_Height: 41
- m_X: 142
m_Y: 0
m_Width: 40
m_Height: 70
m_FreeGlyphRects:
- m_X: 0
m_Y: 70
m_Width: 511
m_Height: 441
- m_X: 0
m_Y: 41
m_Width: 142
m_Height: 470
- m_X: 182
m_Y: 0
m_Width: 329
m_Height: 511
m_FontFeatureTable:
m_MultipleSubstitutionRecords: []

View File

@ -38,12 +38,19 @@ Material:
m_Ints: []
m_Floats:
- _CellsDensity: 5.5
- _CellsInnerSpeed: 0.4
- _CellsInnerSpeed: 2
- _DarkSpotsScale: 1
m_Colors:
- _CellsSpeed: {r: 0.1, g: 0, b: 0, a: 0}
- _CellsVisibility: {r: 0.3, g: 1, b: 0, a: 0}
- _CellsSpeed: {r: 0.05, g: 0, b: 0, a: 0}
- _CellsVisibility: {r: 0.5, g: 1, b: 0, a: 0}
- _DarkSpotsSpeed: {r: 0.05, g: 0, b: 0, a: 0}
- _DarkenSpeed: {r: 0.3, g: 0, b: 0, a: 0}
- _LightColor: {r: 1, g: 0.6877387, b: 0, a: 0}
- _LightZoneEdges: {r: 0.8, g: 0.95, b: 0, a: 0}
- _MainColor: {r: 1.3733709, g: 0.040577125, b: 0, a: 0}
- _SecondaryColor: {r: 1.7099856, g: 0.14072022, b: 0.038764723, a: 0}
- _TransitionColor: {r: 0.7264151, g: 0.32461706, b: 0.0651032, a: 0}
- _TransitionZoneEdges: {r: 0.4, g: 0.95, b: 0, a: 0}
m_BuildTextureStacks: []
m_AllowLocking: 1
--- !u!114 &9002410087788318591

View File

@ -241,7 +241,7 @@ MonoBehaviour:
m_RuleTransform: 0
- m_Id: 12
m_Sprites:
- {fileID: -37180730, guid: 50f8da9385097bd4a8661f2c880c3f38, type: 3}
- {fileID: 347253214, guid: 50f8da9385097bd4a8661f2c880c3f38, type: 3}
m_GameObject: {fileID: 0}
m_MinAnimationSpeed: 1
m_MaxAnimationSpeed: 1

View File

@ -31,10 +31,8 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1463772777571496698}
- {fileID: 3854683455129589988}
- {fileID: 5524374772126104666}
- {fileID: 5107309739854095639}
- {fileID: 2452427053371811879}
m_Father: {fileID: 3900935777108218119}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1839735485 &4143237299023859678
@ -767,76 +765,6 @@ Grid:
m_CellGap: {x: 0, y: 0, z: 0}
m_CellLayout: 0
m_CellSwizzle: 0
--- !u!1001 &763657809995300296
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 2201879526763279960}
m_Modifications:
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.x
value: 9.5
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.y
value: -0.5
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7095279146655049368, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_DynamicOccludee
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7095279146655049368, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LightProbeUsage
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7961247480686211061, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_Name
value: Lava
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
--- !u!4 &2452427053371811879 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
m_PrefabInstance: {fileID: 763657809995300296}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &2157046099685346571
PrefabInstance:
m_ObjectHideFlags: 0
@ -847,11 +775,11 @@ PrefabInstance:
m_Modifications:
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.x
value: 9.5
value: 14.5
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.y
value: -0.5
value: 1.5
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.z
@ -889,10 +817,6 @@ PrefabInstance:
propertyPath: m_DynamicOccludee
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7095279146655049368, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LightProbeUsage
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7961247480686211061, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_Name
value: Lava
@ -907,7 +831,7 @@ Transform:
m_CorrespondingSourceObject: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
m_PrefabInstance: {fileID: 2157046099685346571}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &7222452497720584629
--- !u!1001 &2469726870544465209
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
@ -915,139 +839,65 @@ PrefabInstance:
serializedVersion: 3
m_TransformParent: {fileID: 2201879526763279960}
m_Modifications:
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.x
value: 9.5
- target: {fileID: 351781492710161714, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_Name
value: Coin
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.y
value: -0.5
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7095279146655049368, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
- target: {fileID: 787779534025234403, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_DynamicOccludee
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7095279146655049368, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LightProbeUsage
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalPosition.x
value: 13.5
objectReference: {fileID: 0}
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalPosition.y
value: 1.5
objectReference: {fileID: 0}
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7961247480686211061, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_Name
value: Lava
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
--- !u!4 &5524374772126104666 stripped
m_SourcePrefab: {fileID: 100100000, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
--- !u!4 &1463772777571496698 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
m_PrefabInstance: {fileID: 7222452497720584629}
m_CorrespondingSourceObject: {fileID: 3897414768386052035, guid: fd2391a459d3e9d4bba8faa914f8e433, type: 3}
m_PrefabInstance: {fileID: 2469726870544465209}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &7958147463206236408
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 2201879526763279960}
m_Modifications:
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.x
value: 9.5
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.y
value: -0.5
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7095279146655049368, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_DynamicOccludee
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7095279146655049368, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_LightProbeUsage
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7961247480686211061, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
propertyPath: m_Name
value: Lava
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
--- !u!4 &5107309739854095639 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 2923341736873825775, guid: e5783e81b8b0fd648974f0ec7ebde930, type: 3}
m_PrefabInstance: {fileID: 7958147463206236408}
m_PrefabAsset: {fileID: 0}
--- !u!114 &885378251313906476
--- !u!114 &6968530127023822361
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}

File diff suppressed because it is too large Load Diff

View File

@ -49,7 +49,7 @@ namespace RR.Game2D.Behaviour
private float _fallSpeedYDampingChangeThreshold;
// Аниматор
private static readonly int Jump = Animator.StringToHash("Jump");
private static readonly int Speed = Animator.StringToHash("Speed");
@ -198,4 +198,4 @@ namespace RR.Game2D.Behaviour
return isGrounded;
}
}
}
}

View File

@ -37,6 +37,10 @@ public class LightDimmer : MonoBehaviour
private float boundsMax;
private Volume[] _volumes;
/// <summary>
/// Валидация в редакторе — обновляем границы, если указан collider, и подставляем тег игрока по умолчанию.
/// Вызвается в редакторе при изменении полей в инспекторе.
/// </summary>
private void OnValidate()
{
if (targetCollider != null)
@ -47,6 +51,10 @@ public class LightDimmer : MonoBehaviour
if (string.IsNullOrEmpty(playerTag)) playerTag = "Player";
}
/// <summary>
/// Инициализация при включении объекта: подставляем collider если не указан, находим игрока и кэшируем Volume для каждого света.
/// Важно: здесь мы не создаём новых Volume, а только берём существующие компоненты в родительских объектах света.
/// </summary>
private void OnEnable()
{
if (targetCollider == null)
@ -57,20 +65,33 @@ public class LightDimmer : MonoBehaviour
UpdateBounds();
}
// Подготавливаем массив volumes той же длины, что и массив lights.
// Это кэш для ускорения доступа к Bloom в Update, чтобы не делать GetComponent каждый кадр.
_volumes = new Volume[lights.Length];
if (string.IsNullOrEmpty(playerTag)) playerTag = "Player";
// Пытаемся найти игрока на сцене для удобства — это позволит скрипту работать, даже если playerTransform не задан вручную.
var playerObj = GameObject.FindGameObjectWithTag(playerTag);
if (playerObj != null)
playerTransform = playerObj.transform;
// Кэшируем Volume (если есть) для каждого источника света. Volume может находиться на родителе или выше.
for (int i = 0; i < lights.Length; i++)
{
Light2D light2D = lights[i];
// Возможна ситуация, когда элемент массива пустой (null) — защищаемся от этого.
if (light2D == null)
{
_volumes[i] = null;
continue;
}
// Ищем ближайший родительский Volume (он может быть на том же GameObject или выше в иерархии).
Volume volume = light2D.GetComponentInParent<Volume>();
if (volume != null)
{
// Включаем volume, чтобы настроенные эффекты (Bloom и т.п.) применялись во время работы.
volume.enabled = true;
}
@ -78,6 +99,9 @@ public class LightDimmer : MonoBehaviour
}
}
/// <summary>
/// Start — дополнительная страховка: подставляем collider и обновляем границы на старте сцены.
/// </summary>
private void Start()
{
if (targetCollider == null)
@ -90,48 +114,72 @@ public class LightDimmer : MonoBehaviour
if (string.IsNullOrEmpty(playerTag)) playerTag = "Player";
}
/// <summary>
/// Основной цикл: если игрок находится внутри триггера, вычисляем прогресс по выбранной оси и плавно интерполируем интенсивность света
/// и, при необходимости, значение bloom в соответствующем Volume.
/// </summary>
private void Update()
{
// Нужна ссылка на игрока и хотя бы один свет и collider
// Требуем наличие: playerTransform, массив lights и targetCollider
if (!playerTransform || lights == null || lights.Length == 0 || targetCollider == null)
return;
// Проверяем, что игрок действительно внутри триггера (с учётом возможной множественной геометрии)
// Проверяем, что игрок действительно внутри триггера, учитываем все коллайдеры (на случай CompositeCollider/мультиколлайдеров)
bool playerInside = playerColliders.Count > 0;
if (!playerInside)
return;
Vector3 playerPos = playerTransform.position;
// Дополнительная страховка: проверяем AABB bounds
// Дополнительная проверка: AABB bounds коллайдера (быстрая фильтрация), чтобы избежать лишних вычислений
if (!targetCollider.bounds.Contains(playerPos))
return;
// Выбираем координату для прогресса: X или Y
float playerCurrent = useYProgress ? playerPos.y : playerPos.x;
float t = 0f;
// Если boundsMin==boundsMax — делить нельзя, поэтому проверяем приблизительное равенство
if (!Mathf.Approximately(boundsMax, boundsMin))
{
// Нормализуем позицию игрока по отрезку [boundsMin, boundsMax]
t = Mathf.InverseLerp(boundsMin, boundsMax, playerCurrent);
t = Mathf.Clamp01(t);
}
// Интерполируем интенсивность между startXIntensity и endXIntensity
// Интерполируем целевые значения интенсивности и bloom в зависимости от нормализованной позиции t
float desiredForPosition = Mathf.Lerp(startIntensity, endIntensity, t);
float desiredForBloom = Mathf.Lerp(startBloomIntensity, targetBloomIntensity, t);
// Проходим по всем светам и плавно (или мгновенно, если smoothing<=0) приводим интенсивность к целевой
for (int i = 0; i < lights.Length; i++)
{
var light2D = lights[i];
_volumes[i].profile.TryGet(out Bloom bloom);
if (!light2D) continue;
// Безопасность: если света нет, пропускаем
if (!light2D)
continue;
// Получаем заранее закэшированный Volume (может быть null)
var vol = _volumes != null && i < _volumes.Length ? _volumes[i] : null;
// Пытаемся извлечь компонент Bloom из профиля, если volume и профиль присутствуют
Bloom bloom = null;
if (applyBloom && vol != null && vol.profile != null)
{
// TryGet возвращает true если компонент есть, и мы получаем ссылку на него
vol.profile.TryGet(out bloom);
}
if (smoothing <= 0f)
{
// Мгновенное изменение — полезно для теста или резких переключателей
light2D.intensity = desiredForPosition;
if (applyBloom && bloom != null) bloom.intensity.value = desiredForBloom;
}
else
{
// Плавное изменение — используем MoveTowards для стабильного изменения без overshoot
light2D.intensity = Mathf.MoveTowards(light2D.intensity, desiredForPosition, smoothing * Time.deltaTime);
if (applyBloom && bloom != null)
bloom.intensity.value = Mathf.MoveTowards(bloom.intensity.value, desiredForBloom, smoothing * Time.deltaTime);
@ -139,6 +187,11 @@ public class LightDimmer : MonoBehaviour
}
}
/// <summary>
/// Триггер: запоминаем коллайдер игрока, когда любой из его коллайдеров входит в область.
/// Используем HashSet, чтобы корректно работать с объектом, у которого несколько Collider2D (включая CompositeCollider2D).
/// </summary>
/// <param name="other">Коллайдер, вошедший в триггер</param>
private void OnTriggerEnter2D(Collider2D other)
{
if (!other.CompareTag(playerTag)) return;
@ -151,6 +204,10 @@ public class LightDimmer : MonoBehaviour
playerTransform = other.transform;
}
/// <summary>
/// Триггер выхода: убираем соответствующий коллайдер из набора; если набор пуст — игрок полностью вышел.
/// </summary>
/// <param name="other">Коллайдер, вышедший из триггера</param>
private void OnTriggerExit2D(Collider2D other)
{
if (!other.CompareTag(playerTag)) return;
@ -159,6 +216,10 @@ public class LightDimmer : MonoBehaviour
playerColliders.Remove(other);
}
/// <summary>
/// Обновляет внутренние границы отслеживаемого Collider2D.
/// При useYProgress==false возвращает min/max по X, иначе по Y.
/// </summary>
private void UpdateBounds()
{
var b = targetCollider.bounds;
@ -172,6 +233,9 @@ public class LightDimmer : MonoBehaviour
}
}
/// <summary>
/// Рисует в редакторе границу collider'а и позицию игрока для удобства отладки.
/// </summary>
void OnDrawGizmosSelected()
{
if (targetCollider == null) return;

View File

@ -6,6 +6,9 @@ using RR.Game2D.Core.Interfaces;
namespace RR.Game2D.Entities.Projectiles
{
/// <summary>
/// Подбираемый снаряд с возможностью отскока, возврата к владельцу и взаимодействия с игровыми сущностями.
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(Rigidbody2D),
typeof(CapsuleCollider2D))]
@ -61,13 +64,20 @@ namespace RR.Game2D.Entities.Projectiles
//---------------------------
// Singleton
//---------------------------
/// <summary>
/// Инициализация компонентов и создание вспомогательной прыжковой платформы (jumpPad).
/// Реализуется как Singleton: если экземпляр уже существует — уничтожаем дубликат.
/// </summary>
protected override void Awake()
{
// Получение ссылок на компоненты Rigidbody2D и Collider
rb = GetComponent<Rigidbody2D>();
col = GetComponent<CapsuleCollider2D>();
// Пока в Awake оставляем коллайдер в режиме триггера — это переставим в Start/другие состояния по необходимости
col.isTrigger = true;
// Простая реализация singleton-а — предотвращаем дублирование снаряда, если это ожидаемое поведение проекта
if (Instance != null && Instance != this)
{
Destroy(gameObject);
@ -76,17 +86,24 @@ namespace RR.Game2D.Entities.Projectiles
Instance = this;
// Создаём вспомогательный объект "jumpPad" программно. Он используется для реакции игрока на то, что снаряд лежит на земле.
// Создание через код даёт гибкость при отсутствии заранее подготовленного префаба.
jumpPad = new GameObject("Projectile Jump Pad");
jumpPad.AddComponent<BoxCollider2D>();
jumpPad.AddComponent<JumpPad2D>();
jumpPad.GetComponent<BoxCollider2D>().isTrigger = true;
Vector3 jumpPadScale = jumpPad.transform.localScale;
// Уменьшаем по высоте — платформа тонкая
jumpPadScale.y = 0.1f;
jumpPad.transform.localScale = jumpPadScale;
jumpPad.SetActive(false);
// Подписываемся на событие прыжка с платформы
jumpPad.GetComponent<JumpPad2D>().PlayerJumped += OnPlayerJumped;
}
/// <summary>
/// Очистка при уничтожении — отписка и уничтожение вспомогательного объекта.
/// </summary>
private void OnDestroy()
{
if (Instance == this) Instance = null;
@ -96,6 +113,9 @@ namespace RR.Game2D.Entities.Projectiles
//
//---------------------------
/// <summary>
/// Устанавливаем параметры физики и первоначальное состояние снаряда.
/// </summary>
private void Start()
{
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
@ -105,28 +125,40 @@ namespace RR.Game2D.Entities.Projectiles
BecomeGrounded();
}
/// <summary>
/// Фиксированный апдейт: обработка смены состояний, поворот в направлении полёта и инициирование возврата.
/// </summary>
protected override void FixedUpdate()
{
base.FixedUpdate();
// Если не нужно блокировать при приземлении — проверяем скорость и переводим в состояние 'Grounded'
if (!lockWhenLanded && CurrentState == State.Airborne)
{
if (rb.linearVelocity.sqrMagnitude < (groundVelocity * groundVelocity))
BecomeGrounded();
}
// В полёте и при возврате снаряд ориентируется по вектору скорости
if ((CurrentState == State.Airborne || CurrentState == State.Returning) && rb.linearVelocity.sqrMagnitude > 0.0001f)
transform.up = rb.linearVelocity.normalized;
// Дополнительная страховка перехода в grounded по скорости
if (CurrentState == State.Airborne && rb.linearVelocity.magnitude < groundVelocity)
BecomeGrounded();
// Если снаряд когда-то был в руках — он может начать возвращаться автоматически
if (hadBeenHeld && CurrentState != State.Held)
Recall();
}
/// <summary>
/// Обработка столкновения. Пытаемся подобрать при касании игрока, наносим урон врагам, обрабатываем отскоки и приземление.
/// </summary>
/// <param name="collision">Данные столкновения</param>
protected override void OnCollisionEnter2D(Collision2D collision)
{
// Нахождение игрового объекта, с которым мы столкнулись. Учтём разные варианты: rigidbody другого коллайдера или сам объект.
var otherRb = collision.rigidbody ? collision.rigidbody : collision.otherRigidbody;
var otherGO = (collision.collider ? collision.collider.attachedRigidbody?.gameObject : null)
?? otherRb?.gameObject
@ -135,10 +167,12 @@ namespace RR.Game2D.Entities.Projectiles
{
if (otherGO.CompareTag("Player") && CurrentState != State.Held)
{
// Попытка подобрать снаряд игроком
if (TryPickup(otherGO)) return;
}
else if (otherGO.CompareTag("Enemy"))
{
// Наносим урон врагу через его контроллер, если он есть
EnemyController enemyController = otherGO.GetComponent<EnemyController>();
if (enemyController != null)
{
@ -147,10 +181,14 @@ namespace RR.Game2D.Entities.Projectiles
}
}
// Если слой не предназначен для нанесения урона/эффектов — выходим
if (!IsInHitLayer(collision.collider.gameObject.layer)) return;
// Точка контакта для эффектов
Vector2 point = collision.GetContact(0).point;
OnHit(collision, point, collision.relativeVelocity);
// Логика отскока и приземления
if (CurrentState == State.Airborne)
{
if (TryBounce(collision))
@ -166,6 +204,10 @@ namespace RR.Game2D.Entities.Projectiles
lastGroundTouchTime = Time.time;
}
/// <summary>
/// Контролирует длительность скольжения по земле — если прошло слишком много времени, переводим в grounded.
/// </summary>
/// <param name="collision">Данные столкновения</param>
private void OnCollisionStay2D(Collision2D collision)
{
if (collision.collider.CompareTag("Ground"))
@ -176,6 +218,10 @@ namespace RR.Game2D.Entities.Projectiles
}
/// <summary>
/// Обработка триггерного входа: подбор игроком, нанесение урона врагам или делегирование базовому методу.
/// </summary>
/// <param name="other">Коллайдер, вошедший в триггер</param>
protected override void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
@ -192,6 +238,7 @@ namespace RR.Game2D.Entities.Projectiles
base.OnTriggerEnter2D(other);
}
}
//
//
// -------------------------------------------
@ -204,6 +251,10 @@ namespace RR.Game2D.Entities.Projectiles
#region Parent Methods
/// <summary>
/// Обработка попадания при столкновении (вызывается из OnCollisionEnter2D).
/// По-умолчанию проигрывает эффекты и вызывает финальную обработку удара.
/// </summary>
protected override void OnHit(Collision2D collision, Vector2 point, Vector2 relativeVelocity)
{
//TryDealDamage(collision.collider, point, relativeVelocity);
@ -212,12 +263,22 @@ namespace RR.Game2D.Entities.Projectiles
OnImpactFinal(collision);
}
/// <summary>
/// Финальная обработка удара при наличии Collision2D.
/// Пытаемся отскочить; если отскок невозможен — считаем, что снаряд приземлился.
/// </summary>
/// <param name="collision">Данные столкнов<D0BE><D0B2>ния</param>
protected override void OnImpactFinal(Collision2D collision)
{
if (!TryBounce(collision) && CurrentState == State.Airborne)
BecomeGrounded();
}
/// <summary>
/// Финальная обработка удара при наличии Collider2D (например при триггерах).
/// Если прошло достаточно времени с последнего удара — приземляемся.
/// </summary>
/// <param name="other">Коллайдер</param>
protected override void OnImpactFinal(Collider2D other)
{
if (Time.time - lastImpactTime > stickDelayAfterImpact && CurrentState == State.Airborne)
@ -233,9 +294,15 @@ namespace RR.Game2D.Entities.Projectiles
// -------------------------------------------
// Подбор
//
#region Pickup
#region Pickup
/// <summary>
/// Попытка подобрать снаряд указанным владельцем.
/// Если снаряд уже держат — возвращает true.
/// </summary>
/// <param name="newOwner">Игровой объект, пытающийся подобрать снаряд</param>
/// <param name="optionalAttachPoint">Опциональная точка привязки</param>
/// <returns>True — если успешно подобран или уже в руках, иначе false</returns>
public bool TryPickup(GameObject newOwner, Transform optionalAttachPoint = null)
{
if (CurrentState == State.Held) return true;
@ -250,12 +317,14 @@ namespace RR.Game2D.Entities.Projectiles
if (!holderAttachPoint)
holderAttachPoint = newOwner.transform;
// Отключаем физическое поведение и делаем снаряд кинематическим, чтобы он "встал" в руки игрока
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
rb.bodyType = RigidbodyType2D.Kinematic;
col.enabled = false;
transform.SetParent(holderAttachPoint);
// Устанавливаем позицию и вращение относительно точки привязки.
transform.SetPositionAndRotation(holderAttachPoint.TransformPoint(heldLocalOffset), new Quaternion(0, 90f, 0, 0));
transform.localScale = baseLocalScale * heldScale;
@ -267,17 +336,19 @@ namespace RR.Game2D.Entities.Projectiles
launched = false;
hadBeenHeld = true;
// При подборе убираем jumpPad, чтобы игрок не случайно "прыгнул" с него
jumpPad.SetActive(false);
return true;
}
/// <summary>
/// Метод для получения точки привязки на игроке.
/// Поиск точки привязки на holder (игроке).
/// Возвращает preferred если задано, иначе ищет в PlayerFireScript, иначе возвращает трансформ самого объекта.
/// </summary>
/// <param name="holder">Игрок, который держит снаряд</param>
/// <param name="preferred">Опциональная точка привязки. При передаче, снаряд будет привязан на нее</param>
/// <returns>Transform attachPoint - точка привязки</returns>
/// <param name="holder">Игрок / владелец</param>
/// <param name="preferred">Предпочтительная точка привязки</param>
/// <returns>Transform точки привязки</returns>
private Transform FindAttachPoint(GameObject holder, Transform preferred = null)
{
if (preferred) return preferred;
@ -297,11 +368,17 @@ namespace RR.Game2D.Entities.Projectiles
// -------------------------------------------
// Отскок
//
#region Bounce
/// <summary>
/// Попытка отскока при столкновении. Возвращает true, если отскок выполнен.
/// Здесь используется отражение вектора скорости относительно нормали контакта и коэффициент упругости из материала ко<D0BA><D0BE>лайдера.
/// </summary>
/// <param name="collision">Данные столкновения</param>
/// <returns>True — если был отскок</returns>
public bool TryBounce(Collision2D collision)
{
// Короткий таймаут между прыжками, чтобы избежать слишком частых "залипаний" на отскоках
if (Time.time - lastImpactTime < stickDelayAfterImpact) return true;
if (bounceCounter > maxBounces) return false;
@ -311,8 +388,10 @@ namespace RR.Game2D.Entities.Projectiles
if (speed < minBounceSpeed) return false;
// Нормаль первого контакта
Vector2 direction = Vector2.Reflect(velocityIn, collision.contacts[0].normal);
// Здесь используется bounciness физического материала коллайдера
rb.linearVelocity = col.sharedMaterial.bounciness * direction;
@ -335,6 +414,12 @@ namespace RR.Game2D.Entities.Projectiles
#region Launch
/// <summary>
/// Запуск снаряда: делаем его динамическим, добавляем импульс, настраиваем параметры состояния.
/// </summary>
/// <param name="direction">Направление запуска (в мировых координатах)</param>
/// <param name="speed">Скорость — если значение меньше либо равно нуля, используется initialSpeed</param>
/// <param name="thrower">Игрок, который бросает снаряд (владелец)</param>
public override void Launch(Vector2 direction, float speed = -1, GameObject thrower = null)
{
if (CurrentState == State.Held)
@ -357,6 +442,7 @@ namespace RR.Game2D.Entities.Projectiles
rb.angularVelocity = 0f;
rb.AddForce(finalSpeed * rb.mass * launchDirection, ForceMode2D.Impulse);
// Ориентируем снаряд по направлению скорости
transform.up = launchDirection;
transform.localScale = baseLocalScale;
@ -376,13 +462,16 @@ namespace RR.Game2D.Entities.Projectiles
// -------------------------------------------
// Возвращение
//
#region Recall
/// <summary>
/// Логика возврата снаряда к последнему известному владельцу. Если снаряд найден вне экрана — переключается в режим Returning и летит к владельцу.
/// </summary>
public void Recall()
{
GameObject playerGo = lastKnownHolder;
// Если нет последнего известного владельца — пытаемся получить игрока из GameManager или найти по тегу Player
if (playerGo == null || !playerGo.activeInHierarchy)
playerGo = (GameManager.Instance && GameManager.Instance.PlayerObj)
? GameManager.Instance.PlayerObj
@ -398,12 +487,14 @@ namespace RR.Game2D.Entities.Projectiles
if (mainCamera)
{
// Если камера есть — проверяем, видим ли снаряд в пределах экрана (+margin)
Vector3 viewPoint = mainCamera.WorldToViewportPoint(transform.position);
isVisible = viewPoint.x >= -margin && viewPoint.x <= 1f + margin
&& viewPoint.y >= -margin && viewPoint.y <= 1f + margin;
}
else
{
// Если камеры нет — проверяем дистанцию до владельца как запасной критерий видимости
Vector2 toOwnerTest = (Vector2)attachPoint.position - rb.position;
isVisible = (toOwnerTest.magnitude <= recallStartDistance);
}
@ -415,12 +506,14 @@ namespace RR.Game2D.Entities.Projectiles
{
if (!isVisible)
{
// Переходим в режим возврата: включаем физику и делаем триггер, чтобы не мешать столкновениям
CurrentState = State.Returning;
rb.bodyType = RigidbodyType2D.Dynamic;
col.isTrigger = true;
}
else
{
// Если видим снаряд — не начинаем возврат
return;
}
}
@ -431,6 +524,7 @@ namespace RR.Game2D.Entities.Projectiles
if (rb.linearVelocity.sqrMagnitude > 0.01f)
transform.up = rb.linearVelocity.normalized;
// Если достаточно близко — автоматически подбираем
if (dist <= recallArriveDistance)
TryPickup(playerGo);
}
@ -444,11 +538,11 @@ namespace RR.Game2D.Entities.Projectiles
// -------------------------------------------
// Собственные незаисимые методы
//
#region Self methods
/// <summary>
/// Метод для заморозки снаряда на земле.
/// Переводит снаряд в состояние "на земле" (Grounded): блокирует или оставляет динамическим,
/// включает/выключает триггер и активирует jumpPad — платформу, с которой игрок может прыгнуть.
/// </summary>
private void BecomeGrounded()
{
@ -457,6 +551,7 @@ namespace RR.Game2D.Entities.Projectiles
if (lockWhenLanded)
{
// Полностью останавливаем и делаем кинематическим
rb.linearVelocity = Vector2.zero;
rb.angularVelocity = 0f;
rb.bodyType = RigidbodyType2D.Kinematic;
@ -469,23 +564,28 @@ namespace RR.Game2D.Entities.Projectiles
col.enabled = true;
col.isTrigger = true;
// Отвязываем от родителя
transform.SetParent(null, true);
bounceCounter = 0;
MoveJumpPad();
jumpPad.SetActive(true);
}
/// <summary>
/// Метод для перемещения прыжковой платформы Тамагочика
/// Расчёт позиции и перемещение вспомогательной прыжковой платформы относительно снаряда.
/// Здесь используется приближённая геометрия, чтобы корректно позиционировать платформу даже при повороте снаряда.
/// </summary>
private void MoveJumpPad()
{
// Размеры коллайдера используются для определения смещения платформы
float sizeX = col.bounds.size.y;
float sizeY = col.bounds.size.x;
Vector3 jumpPadPos = transform.position;
// Прыжковая платформа размещается выше снаряда с учётом его поворота.
// Формула — приближённая (без учёта всех трансформаций), но даёт корректный результат в большинстве случаев.
float jumpPadY = Mathf.Sqrt(sizeX * sizeX * Mathf.Pow(Mathf.Cos(transform.localEulerAngles.z), 2) + sizeY * sizeY * Mathf.Pow(Mathf.Sin(transform.localEulerAngles.z), 2));
jumpPadPos.y = jumpPadPos.y + 0.5f * jumpPadY;
@ -493,9 +593,9 @@ namespace RR.Game2D.Entities.Projectiles
}
/// <summary>
/// Метод для обработки событий при отпрыгивания игрока от прыжковой платформы.
/// Обработчик события прыжка с jumpPad — пытаемся подобрать снаряд игроком, которы<D180><D18B> прыгнул.
/// </summary>
/// <param name="playerObj"></param>
/// <param name="playerObj">Игрок, совершивший прыжок</param>
private void OnPlayerJumped(GameObject playerObj)
{
TryPickup(playerObj);
@ -507,4 +607,5 @@ namespace RR.Game2D.Entities.Projectiles
//
// -------------------------------------------
}
}
}

File diff suppressed because it is too large Load Diff