From b8decaadc530845a9650690893c8b56a6829a4b7 Mon Sep 17 00:00:00 2001 From: NINI Date: Mon, 5 Oct 2026 02:58:02 +0900 Subject: [PATCH] Keep friend numbers above spheres in the current camera view --- .../Tests/PlayMode/FirstPuzzlePlayTests.cs | 42 +++++++++++++++++++ .../Presentation/BoardPresenter.cs | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Assets/MagnetismHeroes/Tests/PlayMode/FirstPuzzlePlayTests.cs b/Assets/MagnetismHeroes/Tests/PlayMode/FirstPuzzlePlayTests.cs index 81023f7..5da96fa 100644 --- a/Assets/MagnetismHeroes/Tests/PlayMode/FirstPuzzlePlayTests.cs +++ b/Assets/MagnetismHeroes/Tests/PlayMode/FirstPuzzlePlayTests.cs @@ -4,6 +4,8 @@ using System.Linq; using MagnetismHeroes.Core; using MagnetismHeroes.UnityRuntime.Bootstrap; using MagnetismHeroes.UnityRuntime.StageAssets; +using MagnetismHeroes.UnityRuntime.Presentation; +using TMPro; using NUnit.Framework; using UnityEditor; using UnityEngine; @@ -66,6 +68,46 @@ namespace MagnetismHeroes.Tests Drag(start, camera.WorldToScreenPoint(destination + offset)); } + [UnityTest] public IEnumerator GroupNumberFrontViewStaysAboveAndVisible() => CheckGroupNumber(Quaternion.identity); + [UnityTest] public IEnumerator GroupNumberBelowViewStaysAboveAndVisible() => CheckGroupNumber(Quaternion.Euler(-90, 0, 0)); + [UnityTest] public IEnumerator GroupNumberRolledViewStaysAboveAndVisible() => CheckGroupNumber(Quaternion.Euler(0, 0, 90)); + [UnityTest] public IEnumerator GroupNumberPreviewFollowsTheFriendWithoutChangingCommittedState() => CheckGroupNumber(Quaternion.Euler(-90, 0, 0), true); + + private IEnumerator CheckGroupNumber(Quaternion rotation, bool preview = false) + { + _app.StartStage(1); yield return null; + var state = _app.Session.Board.Balls.Single(); + _app.CommitMove(_app.Session.Board.Groups.Single().id, new Position3(4 - state.position.x, 0, 0)); + _app.CountBall(state.id); Assert.That(_app.Session.Screen, Is.EqualTo(SessionScreen.FreePlay)); + var saved = JsonUtility.ToJson(_store.Saved); var position = _app.Session.Board.Balls.Single().position; + var committedCamera = JsonUtility.ToJson(_app.Session.Camera); + _app.Orbit.Apply(new CameraState { target = new Position3(4, 0.6f, 0), distance = 8, + rotation = new Rotation4(rotation.x, rotation.y, rotation.z, rotation.w) }); + if (preview) _app.Presenter.PreviewGroup(_app.Session.Board.Groups.Single().id, new Vector3(0.8f, 0, 0.3f)); + yield return null; Physics.SyncTransforms(); + var camera = _app.Presenter.Camera; var badge = _app.Presenter.GetComponentInChildren(); + var number = badge.GetComponentInChildren(); number.ForceMeshUpdate(); + Assert.That(number.text, Is.EqualTo("1")); Assert.That(number.textInfo.characterInfo[0].isVisible, Is.True); + Assert.That(Vector3.Dot(number.transform.forward, camera.transform.forward), Is.EqualTo(1).Within(0.001)); + Assert.That(Vector3.Dot(badge.transform.Find("RepresentativeFace").forward, camera.transform.forward), Is.EqualTo(1).Within(0.001)); + var glyph = number.textInfo.characterInfo[0]; + var glyphWorld = new[] { glyph.bottomLeft, glyph.topLeft, glyph.topRight, glyph.bottomRight }.Select(number.transform.TransformPoint).ToArray(); + var glyphScreen = glyphWorld.Select(camera.WorldToScreenPoint).ToArray(); + Assert.That(glyphScreen.All(point => point.z > camera.nearClipPlane && point.x > 0 && point.x < Screen.width && point.y > 0 && point.y < Screen.height), Is.True); + var ballBounds = _app.Presenter.Balls.Values.Single().GetComponent().bounds; + float ballTop = float.NegativeInfinity; + foreach (int x in new[] { -1, 1 }) foreach (int y in new[] { -1, 1 }) foreach (int z in new[] { -1, 1 }) + ballTop = Mathf.Max(ballTop, camera.WorldToScreenPoint(ballBounds.center + Vector3.Scale(ballBounds.extents, new Vector3(x, y, z))).y); + Assert.That(glyphScreen.Min(point => point.y), Is.GreaterThan(ballTop), "The visible glyph must sit above the sphere in the current camera view."); + var glyphCenter = glyphWorld.Aggregate(Vector3.zero, (sum, vertex) => sum + vertex) / glyphWorld.Length; + var ray = camera.ScreenPointToRay(camera.WorldToScreenPoint(glyphCenter)); + Assert.That(Physics.Raycast(ray, Vector3.Distance(ray.origin, glyphCenter)), Is.False, "Scene colliders must not hide the number before its glyph plane."); + Assert.That(_app.Session.Board.Balls.Single().position, Is.EqualTo(position)); + Assert.That(JsonUtility.ToJson(_app.Session.Camera), Is.EqualTo(committedCamera)); Assert.That(JsonUtility.ToJson(_store.Saved), Is.EqualTo(saved)); + _app.Presenter.CancelPreview(); + Assert.That(_app.Presenter.Balls.Values.Single().transform.position, Is.EqualTo(new Vector3(position.x, position.y, position.z))); + } + [UnityTest] public IEnumerator PinchReleaseOutsideRestoresCameraAndSuppressesRemainingTouch() { _touchscreen = InputSystem.AddDevice(); _app.StartStage(1); yield return null; Physics.SyncTransforms(); diff --git a/Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs b/Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs index cc56be7..5279aeb 100644 --- a/Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs +++ b/Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs @@ -79,7 +79,7 @@ namespace MagnetismHeroes.UnityRuntime.Presentation var group = groups[index]; var center = group.ballIds.Select(id => _balls[id].transform.position).Aggregate(Vector3.zero, (sum, value) => sum + value) / group.ballIds.Length; var representative = _balls[group.ballIds[0]].transform.position; - _badges[index].Bind(group.ballIds.Length, center + Vector3.up * 0.95f, + _badges[index].Bind(group.ballIds.Length, center + _camera.transform.up * 0.95f, representative - _camera.transform.forward * 0.49f); _badges[index].FaceCamera(_camera); }