Implement seamless physical Screen Wrapping using Compound Collider on wrap visuals container

Подробный список изменений:
- Объект wrapVisualCloneRoot сделан дочерним к transform игрока.
- Внедрен метод RebuildWrapCollider для автоматического дублирования основного коллайдера игрока (Box/Circle/Capsule) на wrapVisualCloneRoot.
- В Unity дочерние коллайдеры при наличии Rigidbody2D на родителе автоматически формируют Compound Collider (составное физическое тело). Это позволило собирать монеты, активировать пружины и получать урон от врагов визуальным клоном на противоположной стороне экрана.
- В методе CopyWrappedVisuals сменено позиционирование клонов SpriteRenderer на локальные координаты для предотвращения графических артефактов при переносе.
This commit is contained in:
Данил Ярош 2026-07-03 15:53:11 +03:00
parent 988f54bc01
commit 77c72e2f01

View File

@ -1588,13 +1588,23 @@ namespace DanroJump.Gameplay
sourceRenderers.Clear();
wrapRendererClones.Clear();
if (visualRoot != null)
if (wrapVisualCloneRoot == null)
{
sourceRenderers.AddRange(visualRoot.GetComponentsInChildren<SpriteRenderer>(true));
wrapVisualCloneRoot = new GameObject($"{name} Wrap Visuals").transform;
wrapVisualCloneRoot.SetParent(transform, false);
wrapVisualCloneRoot.gameObject.SetActive(false);
}
else
var allRenderers = visualRoot != null
? visualRoot.GetComponentsInChildren<SpriteRenderer>(true)
: GetComponentsInChildren<SpriteRenderer>(true);
foreach (var r in allRenderers)
{
sourceRenderers.AddRange(GetComponentsInChildren<SpriteRenderer>(true));
if (r != null && (wrapVisualCloneRoot == null || !r.transform.IsChildOf(wrapVisualCloneRoot)))
{
sourceRenderers.Add(r);
}
}
if (sourceRenderers.Count == 0)
@ -1602,12 +1612,6 @@ namespace DanroJump.Gameplay
return;
}
if (wrapVisualCloneRoot == null)
{
wrapVisualCloneRoot = new GameObject($"{name} Wrap Visuals").transform;
wrapVisualCloneRoot.gameObject.SetActive(false);
}
for (var index = sourceRenderers.Count - 1; index >= 0; index--)
{
var sourceRenderer = sourceRenderers[index];
@ -1621,6 +1625,59 @@ namespace DanroJump.Gameplay
clone.transform.SetParent(wrapVisualCloneRoot, false);
wrapRendererClones.Insert(0, clone);
}
RebuildWrapCollider();
}
private void RebuildWrapCollider()
{
if (wrapVisualCloneRoot == null)
{
return;
}
var oldColliders = wrapVisualCloneRoot.GetComponents<Collider2D>();
foreach (var old in oldColliders)
{
if (Application.isPlaying)
{
Destroy(old);
}
else
{
DestroyImmediate(old);
}
}
var sourceCollider = GetComponent<Collider2D>();
if (sourceCollider != null)
{
if (sourceCollider is BoxCollider2D boxCol)
{
var cloneCol = wrapVisualCloneRoot.gameObject.AddComponent<BoxCollider2D>();
cloneCol.size = boxCol.size;
cloneCol.offset = boxCol.offset;
cloneCol.isTrigger = boxCol.isTrigger;
cloneCol.sharedMaterial = boxCol.sharedMaterial;
}
else if (sourceCollider is CircleCollider2D circleCol)
{
var cloneCol = wrapVisualCloneRoot.gameObject.AddComponent<CircleCollider2D>();
cloneCol.radius = circleCol.radius;
cloneCol.offset = circleCol.offset;
cloneCol.isTrigger = circleCol.isTrigger;
cloneCol.sharedMaterial = circleCol.sharedMaterial;
}
else if (sourceCollider is CapsuleCollider2D capsuleCol)
{
var cloneCol = wrapVisualCloneRoot.gameObject.AddComponent<CapsuleCollider2D>();
cloneCol.size = capsuleCol.size;
cloneCol.offset = capsuleCol.offset;
cloneCol.direction = capsuleCol.direction;
cloneCol.isTrigger = capsuleCol.isTrigger;
cloneCol.sharedMaterial = capsuleCol.sharedMaterial;
}
}
}
/// <summary>
@ -1660,11 +1717,13 @@ namespace DanroJump.Gameplay
CopyWrappedVisuals(new Vector3(wrapOffsetX, 0f, 0f));
}
/// <summary>
/// Копирует состояние исходных SpriteRenderer в wrap-клоны.
/// </summary>
private void CopyWrappedVisuals(Vector3 offset)
{
if (wrapVisualCloneRoot != null)
{
wrapVisualCloneRoot.localPosition = offset;
}
for (var index = 0; index < sourceRenderers.Count; index++)
{
var source = sourceRenderers[index];
@ -1694,8 +1753,9 @@ namespace DanroJump.Gameplay
clone.sortingLayerID = source.sortingLayerID;
clone.sortingOrder = source.sortingOrder;
clone.sharedMaterial = source.sharedMaterial;
clone.transform.SetPositionAndRotation(source.transform.position + offset, source.transform.rotation);
clone.transform.localScale = source.transform.lossyScale;
clone.transform.localPosition = source.transform.localPosition;
clone.transform.localRotation = source.transform.localRotation;
clone.transform.localScale = source.transform.localScale;
clone.gameObject.layer = source.gameObject.layer;
}
catch (MissingReferenceException)