놀이터 밖 배경 충돌을 끄고 지면·접근 소품 역할 유지

This commit is contained in:
NINI
2026-10-10 02:35:20 +09:00
parent 9a316cfd89
commit a7439bd49b
3 changed files with 62 additions and 1 deletions
@@ -76,7 +76,7 @@ namespace MagnetismHeroes.Tests
string name = decoration.prefab.name; string name = decoration.prefab.name;
bool solid = name == "MeadowClearing" || name == "GentleHill" || name.Contains("Tree") || name.Contains("Limestone") || name == "SlateBoulder" || name == "OpenStoneArch" || name == "MossStoneSteps"; bool solid = name == "MeadowClearing" || name == "GentleHill" || name.Contains("Tree") || name.Contains("Limestone") || name == "SlateBoulder" || name == "OpenStoneArch" || name == "MossStoneSteps";
var colliders = decoration.prefab.GetComponentsInChildren<Collider>(true); var colliders = decoration.prefab.GetComponentsInChildren<Collider>(true);
if (solid) Assert.That(colliders, Is.Not.Empty, name + " needs a shape-matched collider."); if (solid) Assert.That(colliders, Is.Not.Empty, name + " needs a shape-matched collider; runtime disables inaccessible instances, while authoring uses terrain for surface probes.");
else Assert.That(colliders, Is.Empty, name + " is small vegetation or unreachable scenery."); else Assert.That(colliders, Is.Empty, name + " is small vegetation or unreachable scenery.");
foreach (var collider in colliders) foreach (var collider in colliders)
{ {
@@ -1,6 +1,7 @@
#if UNITY_EDITOR #if UNITY_EDITOR
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MagnetismHeroes.Core; using MagnetismHeroes.Core;
@@ -112,6 +113,58 @@ namespace MagnetismHeroes.Tests
Capture("world1-ten-single-groups-phone-default", "Validated synthetic Core FreePlay fixture of ten single groups; underlying stage 1 UI remains puzzle. Used for like-for-like Editor resource observation only."); Capture("world1-ten-single-groups-phone-default", "Validated synthetic Core FreePlay fixture of ten single groups; underlying stage 1 UI remains puzzle. Used for like-for-like Editor resource observation only.");
Assert.That(_app.Presenter.Balls.Count, Is.EqualTo(10)); Assert.That(_app.Presenter.Balls.Count, Is.EqualTo(10));
} }
[UnityTest] public IEnumerator AllThirtyRuntimeEnvironmentsOnlyBlockWithReachableScenery()
{
var errors = new List<string>(); int distant = 0, reachable = 0, selected = 0;
for (int id = 1; id <= 30; id++)
{
_app.StartStage(id); _app.Voice.Clear(); yield return null; yield return null; Physics.SyncTransforms();
var playground = _app.Environment.PlaygroundBounds;
foreach (var collider in _app.Environment.GetComponentsInChildren<MeshCollider>())
{
var surface = collider.GetComponent<MeshRenderer>().bounds;
bool outside = collider.name != "MeadowClearing" && !playground.Intersects(surface);
if (outside)
{
distant++;
if (collider.enabled) errors.Add($"Stage {id} unreachable {collider.name} still blocks at {surface}.");
}
else
{
reachable++;
Assert.That(collider.enabled && !collider.isTrigger, Is.True, $"Stage {id} reachable {collider.name} must block.");
}
bool hit = false;
foreach (var direction in new[] { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back })
{
float distance = surface.extents.magnitude + 1;
hit |= Physics.RaycastAll(surface.center + direction * distance, -direction, distance * 2)
.Any(item => item.collider == collider);
}
if (outside && hit) errors.Add($"Stage {id} background {collider.name} still receives a Physics ray.");
if (!outside) Assert.That(hit, Is.True, $"Stage {id} reachable {collider.name} no longer blocks a Physics ray.");
}
Assert.That(Physics.Raycast(new Vector3(0, .1f, 0), Vector3.down, out var ground, 2), Is.True);
Assert.That(ground.collider.GetComponentInParent<BoardEnvironment>(), Is.SameAs(_app.Environment), "Visible play ground must still block.");
Debug.Log($"Stage {id} ground ray y={ground.point.y:R}, aerial bounds min y={playground.min.y:R}, difference={ground.point.y - playground.min.y:R}");
foreach (var ball in _app.Presenter.Balls.Values)
{
bool found = false;
foreach (float pitch in new[] { 20f, 45f, 70f })
for (int yaw = 0; yaw < 360 && !found; yaw += 30)
{
View(yaw, pitch, 12); var point = _app.Presenter.Camera.WorldToViewportPoint(ball.transform.position);
if (point.z <= 0 || point.x < .05f || point.x > .95f || point.y < .05f || point.y > .95f) continue;
var ray = _app.Presenter.Camera.ViewportPointToRay(point);
found = Physics.Raycast(ray, out var first, 100) && first.collider == ball.GetComponent<SphereCollider>();
}
Assert.That(found, Is.True, $"Stage {id} sphere {ball.BallId} no longer selectable by first hit."); selected++;
}
}
Debug.Log($"Runtime scenery roles: 30 stages, {distant} distant colliders, {reachable} reachable colliders, {selected} selectable spheres.");
Assert.That(distant, Is.GreaterThan(0)); Assert.That(reachable, Is.GreaterThan(0));
Assert.That(errors, Is.Empty, string.Join("\n", errors));
}
} }
} }
#endif #endif
@@ -29,6 +29,14 @@ namespace MagnetismHeroes.UnityRuntime.Presentation
(corner & 2) == 0 ? bounds.min.y : bounds.max.y, (corner & 2) == 0 ? bounds.min.y : bounds.max.y,
(corner & 4) == 0 ? bounds.min.z : bounds.max.z))); (corner & 4) == 0 ? bounds.min.z : bounds.max.z)));
PlaygroundBounds = worldBounds; PlaygroundBounds = worldBounds;
Physics.SyncTransforms();
foreach (Transform prop in transform)
{
// The clearing supports the aerial volume from just below its lower boundary.
if (!prop.gameObject.activeSelf || prop.name == "MeadowClearing(Clone)") continue;
foreach (var collider in prop.GetComponentsInChildren<Collider>())
if (!PlaygroundBounds.Intersects(collider.bounds)) collider.enabled = false;
}
} }
} }
} }