convert game playing..
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f16c4a8dfe44f9e8c455a2e24ad7281
|
||||
@@ -1,15 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Progression;
|
||||
using BumpCombat.Spawning;
|
||||
using BumpCombat.Stage;
|
||||
using BumpCombat.UI;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BumpCombat.PlayModeTests
|
||||
{
|
||||
@@ -298,6 +301,142 @@ namespace BumpCombat.PlayModeTests
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator RoomScaleComparison_UsesMatchedCameraAndMinimapForOriginalAndExpanded()
|
||||
{
|
||||
const int referenceSeed = 20260922;
|
||||
RunConstants constants = GameplayConstants.Current.Run;
|
||||
RoomScaleComparisonMode previousMode = constants.Stage1FirstCombatRoomScaleComparison;
|
||||
UnityEngine.Random.State previousRandomState = UnityEngine.Random.state;
|
||||
float previousTimeScale = Time.timeScale;
|
||||
bool previousProductionMode = RunManager.ForceProductionModeForTests;
|
||||
string captureDirectory = System.Environment.GetEnvironmentVariable(
|
||||
"BUMPCOMBAT_COMPARISON_CAPTURE_DIRECTORY");
|
||||
|
||||
try
|
||||
{
|
||||
constants.Stage1FirstCombatRoomScaleComparison = RoomScaleComparisonMode.Original;
|
||||
yield return LoadAndStartRun();
|
||||
|
||||
StageManager stageManager = Object.FindAnyObjectByType<StageManager>();
|
||||
Camera camera = Camera.main;
|
||||
Assert.That(stageManager, Is.Not.Null);
|
||||
Assert.That(camera, Is.Not.Null);
|
||||
|
||||
RoomScaleComparisonMode[] modes =
|
||||
{
|
||||
RoomScaleComparisonMode.Original,
|
||||
RoomScaleComparisonMode.Expanded,
|
||||
};
|
||||
for (int modeIndex = 0; modeIndex < modes.Length; modeIndex++)
|
||||
{
|
||||
RoomScaleComparisonMode mode = modes[modeIndex];
|
||||
constants.Stage1FirstCombatRoomScaleComparison = mode;
|
||||
stageManager.StartRun(referenceSeed);
|
||||
yield return new WaitForSecondsRealtime(0.4f);
|
||||
|
||||
StageNode firstCombat = FindFirstCombat(stageManager.CurrentPlan);
|
||||
Assert.That(firstCombat, Is.Not.Null);
|
||||
Assert.That(firstCombat.PresetId,
|
||||
Is.EqualTo(mode == RoomScaleComparisonMode.Expanded
|
||||
? LevelPresetCatalog.S1AComparisonExpandedPresetId
|
||||
: "GRAVE_S1_COMBAT_A"));
|
||||
|
||||
StageDirection entryDirection = GetEntryDirection(
|
||||
stageManager.CurrentPlan.StartNode,
|
||||
firstCombat);
|
||||
|
||||
// Reset immediately before the same room activation so the
|
||||
// encounter's UnityEngine.Random group rolls match A/B.
|
||||
UnityEngine.Random.InitState(referenceSeed);
|
||||
stageManager.RequestZoneTransition(firstCombat.Id, entryDirection);
|
||||
float deadline = Time.realtimeSinceStartup + 4f;
|
||||
while (stageManager.CurrentZone == null
|
||||
|| stageManager.CurrentZone.NodeId != firstCombat.Id
|
||||
|| stageManager.IsTransitioning)
|
||||
{
|
||||
Assert.That(Time.realtimeSinceStartup, Is.LessThan(deadline),
|
||||
"The comparison fixture did not reach the first combat room.");
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// CoTransitionZone sets CurrentZone before its doorway
|
||||
// hold. Wait for ActivateZone and encounter spawn to
|
||||
// finish before inspecting or capturing the room.
|
||||
yield return new WaitForSecondsRealtime(0.4f);
|
||||
|
||||
ZoneInstance zone = stageManager.CurrentZone;
|
||||
Assert.That(stageManager.IsTransitioning, Is.False);
|
||||
AssertReferenceEncounter(
|
||||
zone,
|
||||
mode == RoomScaleComparisonMode.Expanded ? 6 : 3,
|
||||
mode == RoomScaleComparisonMode.Expanded ? 4 : 2);
|
||||
float expectedOrtho = mode == RoomScaleComparisonMode.Expanded
|
||||
? constants.StageComparisonCameraViewHalfExtents.y
|
||||
: constants.StageComparisonOriginalCameraViewHalfExtents.y;
|
||||
Assert.That(zone.Preset.GridWidth,
|
||||
Is.EqualTo(mode == RoomScaleComparisonMode.Expanded ? 36 : 18));
|
||||
Assert.That(zone.Preset.GridHeight,
|
||||
Is.EqualTo(mode == RoomScaleComparisonMode.Expanded ? 24 : 12));
|
||||
|
||||
PlayerController comparisonPlayer =
|
||||
Object.FindAnyObjectByType<PlayerController>();
|
||||
Vector2 returnPosition = comparisonPlayer.transform.position;
|
||||
Vector2 explorationPosition = returnPosition;
|
||||
bool foundUnseenCell = false;
|
||||
for (int y = 0; y < zone.Preset.GridHeight && !foundUnseenCell; y++)
|
||||
{
|
||||
for (int x = 0; x < zone.Preset.GridWidth; x++)
|
||||
{
|
||||
if (zone.GetRuntimeCell(x, y) == PresetCellType.Empty
|
||||
|| zone.IsCellVisible(x, y))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
explorationPosition = zone.Preset.CellToWorldPosition(
|
||||
zone.ZoneCenter,
|
||||
new Vector2Int(x, y));
|
||||
foundUnseenCell = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(foundUnseenCell, Is.True,
|
||||
"The comparison room must have an unseen terrain cell.");
|
||||
Assert.That(comparisonPlayer.TryReposition(explorationPosition), Is.True);
|
||||
zone.UpdateVisibility(explorationPosition);
|
||||
yield return null;
|
||||
Assert.That(comparisonPlayer.TryReposition(returnPosition), Is.True);
|
||||
zone.UpdateVisibility(returnPosition);
|
||||
yield return null;
|
||||
|
||||
camera.Render();
|
||||
Assert.That(camera.orthographicSize, Is.EqualTo(expectedOrtho).Within(0.01f));
|
||||
AssertPixelPerfectReference(
|
||||
camera,
|
||||
mode == RoomScaleComparisonMode.Expanded ? 480 : 320,
|
||||
mode == RoomScaleComparisonMode.Expanded ? 270 : 180);
|
||||
AssertMinimapTerrainMatches(zone);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(captureDirectory))
|
||||
{
|
||||
CaptureWorldOnlyCamera(
|
||||
camera,
|
||||
captureDirectory,
|
||||
mode == RoomScaleComparisonMode.Expanded ? "expanded" : "original");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
constants.Stage1FirstCombatRoomScaleComparison = previousMode;
|
||||
UnityEngine.Random.state = previousRandomState;
|
||||
Time.timeScale = previousTimeScale;
|
||||
RunManager.ForceProductionModeForTests = previousProductionMode;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerator DestroyActiveEnemies()
|
||||
{
|
||||
EnemyController[] enemies = Object.FindObjectsByType<EnemyController>(
|
||||
@@ -313,6 +452,184 @@ namespace BumpCombat.PlayModeTests
|
||||
yield return null;
|
||||
}
|
||||
|
||||
private static StageNode FindFirstCombat(StagePlan plan)
|
||||
{
|
||||
StageNode entrance = plan.StartNode;
|
||||
for (int i = 0; i < plan.Nodes.Count; i++)
|
||||
{
|
||||
StageNode node = plan.Nodes[i];
|
||||
if (node.NodeType != StageNodeType.Combat)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int direction = 0; direction < 4; direction++)
|
||||
{
|
||||
if (entrance.GetConnection((StageDirection)direction) == node.Id)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static StageDirection GetEntryDirection(StageNode entrance, StageNode target)
|
||||
{
|
||||
for (int direction = 0; direction < 4; direction++)
|
||||
{
|
||||
StageDirection sourceDirection = (StageDirection)direction;
|
||||
if (entrance.GetConnection(sourceDirection) == target.Id)
|
||||
{
|
||||
return sourceDirection.Opposite();
|
||||
}
|
||||
}
|
||||
|
||||
return StageDirection.West;
|
||||
}
|
||||
|
||||
private static void AssertPixelPerfectReference(
|
||||
Camera camera,
|
||||
int expectedWidth,
|
||||
int expectedHeight)
|
||||
{
|
||||
Component pixelPerfect = camera.GetComponent(
|
||||
"UnityEngine.Rendering.Universal.PixelPerfectCamera");
|
||||
Assert.That(pixelPerfect, Is.Not.Null);
|
||||
PropertyInfo referenceWidth = pixelPerfect.GetType().GetProperty("refResolutionX");
|
||||
PropertyInfo referenceHeight = pixelPerfect.GetType().GetProperty("refResolutionY");
|
||||
Assert.That(referenceWidth.GetValue(pixelPerfect), Is.EqualTo(expectedWidth));
|
||||
Assert.That(referenceHeight.GetValue(pixelPerfect), Is.EqualTo(expectedHeight));
|
||||
}
|
||||
|
||||
private static void AssertMinimapTerrainMatches(ZoneInstance zone)
|
||||
{
|
||||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||||
Assert.That(hud, Is.Not.Null);
|
||||
FieldInfo terrainField = typeof(RunHUD).GetField(
|
||||
"minimapTerrainRoot",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
RectTransform terrainRoot = terrainField?.GetValue(hud) as RectTransform;
|
||||
Assert.That(terrainRoot, Is.Not.Null);
|
||||
|
||||
int visibleCellCount = 0;
|
||||
for (int y = 0; y < zone.Preset.GridHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < zone.Preset.GridWidth; x++)
|
||||
{
|
||||
if (zone.GetRuntimeCell(x, y) != PresetCellType.Empty)
|
||||
{
|
||||
visibleCellCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(terrainRoot.childCount, Is.EqualTo(visibleCellCount));
|
||||
Assert.That(zone.VisibilityRevision, Is.GreaterThan(0));
|
||||
|
||||
int darkCellCount = 0;
|
||||
int dimCellCount = 0;
|
||||
for (int y = 0; y < zone.Preset.GridHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < zone.Preset.GridWidth; x++)
|
||||
{
|
||||
if (zone.GetRuntimeCell(x, y) == PresetCellType.Empty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Image cell = terrainRoot.Find($"Terrain {x},{y}")?.GetComponent<Image>();
|
||||
Assert.That(cell, Is.Not.Null);
|
||||
Color32 color = cell.color;
|
||||
if (zone.IsCellVisible(x, y))
|
||||
{
|
||||
Assert.That(color, Is.Not.EqualTo(new Color32(2, 4, 5, 255)));
|
||||
}
|
||||
else if (zone.IsCellExplored(x, y))
|
||||
{
|
||||
dimCellCount++;
|
||||
Assert.That(color.a, Is.EqualTo(255));
|
||||
}
|
||||
else
|
||||
{
|
||||
darkCellCount++;
|
||||
Assert.That(color, Is.EqualTo(new Color32(2, 4, 5, 255)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(dimCellCount, Is.GreaterThan(0));
|
||||
Assert.That(darkCellCount, Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
private static void AssertReferenceEncounter(
|
||||
ZoneInstance zone,
|
||||
int expectedBatCount,
|
||||
int expectedSkeletonCount)
|
||||
{
|
||||
int batCount = 0;
|
||||
int skeletonCount = 0;
|
||||
for (int i = 0; i < zone.SpawnedEnemies.Count; i++)
|
||||
{
|
||||
EnemyController enemy = zone.SpawnedEnemies[i];
|
||||
if (enemy == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (enemy.name.StartsWith("Bat", System.StringComparison.Ordinal))
|
||||
{
|
||||
batCount++;
|
||||
}
|
||||
else if (enemy.name.StartsWith("Skeleton", System.StringComparison.Ordinal))
|
||||
{
|
||||
skeletonCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(batCount, Is.EqualTo(expectedBatCount));
|
||||
Assert.That(skeletonCount, Is.EqualTo(expectedSkeletonCount));
|
||||
}
|
||||
|
||||
private static void CaptureWorldOnlyCamera(
|
||||
Camera camera,
|
||||
string directory,
|
||||
string variant)
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(directory);
|
||||
RenderTexture previousTarget = camera.targetTexture;
|
||||
RenderTexture previousActive = RenderTexture.active;
|
||||
RenderTexture target = new(640, 360, 24, RenderTextureFormat.ARGB32);
|
||||
Texture2D snapshot = null;
|
||||
try
|
||||
{
|
||||
target.Create();
|
||||
camera.targetTexture = target;
|
||||
camera.Render();
|
||||
RenderTexture.active = target;
|
||||
snapshot = new Texture2D(640, 360, TextureFormat.RGB24, false);
|
||||
snapshot.ReadPixels(new Rect(0f, 0f, 640f, 360f), 0, 0);
|
||||
snapshot.Apply();
|
||||
string path = System.IO.Path.Combine(
|
||||
directory,
|
||||
$"s1a-{variant}-world.png");
|
||||
System.IO.File.WriteAllBytes(path, snapshot.EncodeToPNG());
|
||||
}
|
||||
finally
|
||||
{
|
||||
camera.targetTexture = previousTarget;
|
||||
RenderTexture.active = previousActive;
|
||||
if (snapshot != null)
|
||||
{
|
||||
Object.Destroy(snapshot);
|
||||
}
|
||||
|
||||
target.Release();
|
||||
Object.Destroy(target);
|
||||
}
|
||||
}
|
||||
|
||||
private static EnemyController FindRangedPrefab(SpawnDirector director)
|
||||
{
|
||||
FieldInfo field = typeof(SpawnDirector).GetField(
|
||||
|
||||
@@ -15,6 +15,21 @@ namespace BumpCombat.PlayModeTests
|
||||
private static readonly BindingFlags NonPublicInstance =
|
||||
BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
|
||||
private float previousTimeScale;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
previousTimeScale = Time.timeScale;
|
||||
Time.timeScale = 1f;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
Time.timeScale = previousTimeScale;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ExperienceOrb_UsesChangedPickupRadius()
|
||||
{
|
||||
|
||||
@@ -40,6 +40,10 @@ namespace BumpCombat.PlayModeTests
|
||||
LancerThrustVisual visual = lancer.GetComponent<LancerThrustVisual>();
|
||||
SpriteRenderer source = lancer.GetComponent<SpriteRenderer>();
|
||||
lancer.enabled = false;
|
||||
Rigidbody2D lancerBody = lancer.GetComponent<Rigidbody2D>();
|
||||
lancerBody.linearVelocity = Vector2.zero;
|
||||
lancerBody.angularVelocity = 0f;
|
||||
lancerBody.constraints = RigidbodyConstraints2D.FreezeAll;
|
||||
|
||||
Vector2[] directions =
|
||||
{
|
||||
|
||||
@@ -740,7 +740,7 @@ namespace BumpCombat.PlayModeTests
|
||||
Assert.That(keyboard.escapeKey.isPressed, Is.False);
|
||||
}
|
||||
|
||||
private static void CaptureAtResolution(
|
||||
internal static void CaptureAtResolution(
|
||||
Canvas canvas,
|
||||
RectTransform panelRect,
|
||||
RectTransform finalMissionRow,
|
||||
|
||||
@@ -174,6 +174,10 @@ namespace BumpCombat.PlayModeTests
|
||||
submenuLabels.Add("개발용 시작");
|
||||
}
|
||||
submenuLabels.Add("기본 모드");
|
||||
if (showDevelopment)
|
||||
{
|
||||
submenuLabels.Add("방어전 테스트");
|
||||
}
|
||||
submenuLabels.Add("챌린지 모드(준비 중)");
|
||||
submenuLabels.Add("하드코어 모드(준비 중)");
|
||||
submenuLabels.Add("뒤로");
|
||||
@@ -196,7 +200,7 @@ namespace BumpCombat.PlayModeTests
|
||||
label.text);
|
||||
}
|
||||
|
||||
int placeholderIndex = showDevelopment ? 2 : 1;
|
||||
int placeholderIndex = showDevelopment ? 3 : 1;
|
||||
for (int i = placeholderIndex; i < placeholderIndex + 2; i++)
|
||||
{
|
||||
submenu.Find($"Button {i + 1}").GetComponent<Button>().onClick.Invoke();
|
||||
@@ -212,12 +216,28 @@ namespace BumpCombat.PlayModeTests
|
||||
EventSystem.current.currentSelectedGameObject
|
||||
.GetComponentInChildren<Text>().text,
|
||||
Is.EqualTo("기본 모드"));
|
||||
if (showDevelopment)
|
||||
{
|
||||
yield return PressAndRelease(keyboard, Key.DownArrow);
|
||||
Assert.That(
|
||||
EventSystem.current.currentSelectedGameObject
|
||||
.GetComponentInChildren<Text>().text,
|
||||
Is.EqualTo("방어전 테스트"));
|
||||
}
|
||||
yield return PressAndRelease(keyboard, Key.DownArrow);
|
||||
Assert.That(
|
||||
EventSystem.current.currentSelectedGameObject
|
||||
.GetComponentInChildren<Text>().text,
|
||||
Is.EqualTo("뒤로"),
|
||||
"Keyboard navigation skips disabled mode placeholders.");
|
||||
if (showDevelopment)
|
||||
{
|
||||
yield return PressAndRelease(keyboard, Key.UpArrow);
|
||||
Assert.That(
|
||||
EventSystem.current.currentSelectedGameObject
|
||||
.GetComponentInChildren<Text>().text,
|
||||
Is.EqualTo("방어전 테스트"));
|
||||
}
|
||||
yield return PressAndRelease(keyboard, Key.UpArrow);
|
||||
Assert.That(
|
||||
EventSystem.current.currentSelectedGameObject
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
@@ -155,7 +156,186 @@ namespace BumpCombat.PlayModeTests
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator MovingPlayer_BumpKillsPassThroughCrowdWithoutImpactRecoil()
|
||||
public IEnumerator MovingPlayer_BumpFrontCollision_DefaultAndToggleControlPlayerDamage()
|
||||
{
|
||||
CombatConstants combatConstants = GameplayConstants.Current.Combat;
|
||||
bool originalFrontalBumpSelfDamage =
|
||||
combatConstants.EnableFrontalBumpSelfDamage;
|
||||
EnemyController targetEnemy = null;
|
||||
try
|
||||
{
|
||||
Assert.That(
|
||||
originalFrontalBumpSelfDamage,
|
||||
Is.False,
|
||||
"CombatConstants default must preserve the removed front-bump self damage");
|
||||
combatConstants.EnableFrontalBumpSelfDamage = false;
|
||||
|
||||
AsyncOperation load = SceneManager.LoadSceneAsync("CombatPrototype");
|
||||
while (!load.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||||
Assert.That(director, Is.Not.Null);
|
||||
director.enabled = false;
|
||||
yield return null;
|
||||
|
||||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||||
Assert.That(player, Is.Not.Null);
|
||||
PlayerHealth playerHealth = player.GetComponent<PlayerHealth>();
|
||||
Rigidbody2D playerBody = player.GetComponent<Rigidbody2D>();
|
||||
BumpCombatResolver resolver = player.GetComponent<BumpCombatResolver>();
|
||||
Assert.That(playerBody, Is.Not.Null);
|
||||
|
||||
Vector2 playerOrigin = playerBody.position;
|
||||
FieldInfo enemyPrefabsField = typeof(SpawnDirector).GetField(
|
||||
"enemyPrefabs",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
EnemyController[] enemyPrefabs =
|
||||
enemyPrefabsField?.GetValue(director) as EnemyController[];
|
||||
EnemyController skeletonPrefab = null;
|
||||
if (enemyPrefabs != null)
|
||||
{
|
||||
foreach (EnemyController prefab in enemyPrefabs)
|
||||
{
|
||||
if (prefab != null
|
||||
&& prefab.Definition != null
|
||||
&& prefab.Definition.Kind == EnemyKind.Skeleton)
|
||||
{
|
||||
skeletonPrefab = prefab;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(skeletonPrefab, Is.Not.Null);
|
||||
targetEnemy = Object.Instantiate(
|
||||
skeletonPrefab.gameObject,
|
||||
playerOrigin + Vector2.right * 0.5f,
|
||||
Quaternion.identity).GetComponent<EnemyController>();
|
||||
yield return null;
|
||||
|
||||
Assert.That(targetEnemy, Is.Not.Null);
|
||||
Assert.That(targetEnemy.ContactDamage, Is.GreaterThan(0f));
|
||||
Assert.That(targetEnemy.HasContactDamage, Is.False);
|
||||
Assert.That(
|
||||
EnemyController.AllowsContactDamage(
|
||||
targetEnemy.State,
|
||||
targetEnemy.AttackShape),
|
||||
Is.False);
|
||||
Collider2D enemyCollider = targetEnemy.GetComponent<Collider2D>();
|
||||
Assert.That(
|
||||
enemyCollider.isTrigger,
|
||||
Is.False,
|
||||
"Promoted enemy collider must be solid");
|
||||
|
||||
foreach (EnemyController enemy in
|
||||
Object.FindObjectsByType<EnemyController>(FindObjectsSortMode.None))
|
||||
{
|
||||
if (enemy != targetEnemy)
|
||||
{
|
||||
enemy.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(
|
||||
targetEnemy.TryReposition(playerOrigin + Vector2.right * 0.5f),
|
||||
Is.True);
|
||||
SetAutoProperty(
|
||||
targetEnemy,
|
||||
"FacingDirection",
|
||||
((Vector2)player.transform.position
|
||||
- (Vector2)targetEnemy.transform.position).normalized);
|
||||
targetEnemy.enabled = false;
|
||||
Physics2D.SyncTransforms();
|
||||
yield return new WaitForFixedUpdate();
|
||||
|
||||
Assert.That(RunManager.GameplayInputEnabled, Is.True);
|
||||
|
||||
float initialPlayerHealth = playerHealth.CurrentHealth;
|
||||
float initialEnemyHealth = targetEnemy.CurrentHealth;
|
||||
|
||||
SetAutoProperty(player, "AttackIntentVelocity", Vector2.right * 3f);
|
||||
SetAutoProperty(player, "MoveDirection", Vector2.right);
|
||||
|
||||
MethodInfo resolveNormalContact = typeof(BumpCombatResolver).GetMethod(
|
||||
"TryResolveNormalContact",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(resolveNormalContact, Is.Not.Null);
|
||||
|
||||
resolveNormalContact.Invoke(
|
||||
resolver,
|
||||
new object[] { targetEnemy, Vector2.Lerp(playerBody.position, targetEnemy.transform.position, 0.5f) });
|
||||
|
||||
Assert.That(
|
||||
targetEnemy.CurrentHealth,
|
||||
Is.LessThan(initialEnemyHealth),
|
||||
"Enemy must take damage from front bump");
|
||||
Assert.That(
|
||||
playerHealth.CurrentHealth,
|
||||
Is.EqualTo(initialPlayerHealth),
|
||||
"OFF must preserve no front-bump self damage");
|
||||
Assert.That(
|
||||
player.IsDamageKnockbackActive,
|
||||
Is.False,
|
||||
"OFF must preserve no front-bump damage knockback");
|
||||
|
||||
yield return new WaitForSeconds(0.3f);
|
||||
Assert.That(
|
||||
targetEnemy.TryReposition(playerBody.position + Vector2.right * 0.5f),
|
||||
Is.True);
|
||||
SetAutoProperty(
|
||||
targetEnemy,
|
||||
"FacingDirection",
|
||||
((Vector2)player.transform.position
|
||||
- (Vector2)targetEnemy.transform.position).normalized);
|
||||
Physics2D.SyncTransforms();
|
||||
yield return new WaitForFixedUpdate();
|
||||
Assert.That(targetEnemy.ContactDamage, Is.GreaterThan(0f));
|
||||
Assert.That(targetEnemy.HasContactDamage, Is.False);
|
||||
Assert.That(
|
||||
EnemyController.AllowsContactDamage(
|
||||
targetEnemy.State,
|
||||
targetEnemy.AttackShape),
|
||||
Is.False);
|
||||
combatConstants.EnableFrontalBumpSelfDamage = true;
|
||||
SetAutoProperty(player, "AttackIntentVelocity", Vector2.right * 3f);
|
||||
SetAutoProperty(player, "MoveDirection", Vector2.right);
|
||||
float enabledPlayerHealth = playerHealth.CurrentHealth;
|
||||
float enabledEnemyHealth = targetEnemy.CurrentHealth;
|
||||
|
||||
resolveNormalContact.Invoke(
|
||||
resolver,
|
||||
new object[] { targetEnemy, Vector2.Lerp(playerBody.position, targetEnemy.transform.position, 0.5f) });
|
||||
|
||||
Assert.That(
|
||||
targetEnemy.CurrentHealth,
|
||||
Is.LessThan(enabledEnemyHealth),
|
||||
"ON must preserve enemy bump damage");
|
||||
Assert.That(
|
||||
playerHealth.CurrentHealth,
|
||||
Is.LessThan(enabledPlayerHealth),
|
||||
"ON must restore front-bump self damage");
|
||||
Assert.That(
|
||||
player.IsDamageKnockbackActive,
|
||||
Is.True,
|
||||
"ON must use normal PlayerHealth damage knockback");
|
||||
LogAssert.NoUnexpectedReceived();
|
||||
}
|
||||
finally
|
||||
{
|
||||
combatConstants.EnableFrontalBumpSelfDamage = originalFrontalBumpSelfDamage;
|
||||
if (targetEnemy != null)
|
||||
{
|
||||
Object.Destroy(targetEnemy.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator MovingPlayer_BumpBackHit_DamagesEnemyWithoutPlayerDamage()
|
||||
{
|
||||
AsyncOperation load = SceneManager.LoadSceneAsync("CombatPrototype");
|
||||
while (!load.isDone)
|
||||
@@ -170,39 +350,45 @@ namespace BumpCombat.PlayModeTests
|
||||
yield return null;
|
||||
|
||||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||||
Rigidbody2D playerBody = player.GetComponent<Rigidbody2D>();
|
||||
PlayerHealth playerHealth = player.GetComponent<PlayerHealth>();
|
||||
BumpCombatResolver resolver = player.GetComponent<BumpCombatResolver>();
|
||||
EnemyController crowd = null;
|
||||
EnemyController targetEnemy = null;
|
||||
foreach (EnemyController enemy in
|
||||
Object.FindObjectsByType<EnemyController>(
|
||||
FindObjectsSortMode.None))
|
||||
{
|
||||
if (enemy.IsCrowd && !enemy.IsDead)
|
||||
if (!enemy.IsDead)
|
||||
{
|
||||
crowd = enemy;
|
||||
targetEnemy = enemy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(crowd, Is.Not.Null);
|
||||
Collider2D crowdCollider = crowd.GetComponent<Collider2D>();
|
||||
Assert.That(crowdCollider.isTrigger, Is.True);
|
||||
playerBody.position = Vector2.zero;
|
||||
Assert.That(crowd.TryReposition(new Vector2(0.2f, 0f)), Is.True);
|
||||
Assert.That(targetEnemy, Is.Not.Null);
|
||||
targetEnemy.TryReposition(new Vector2(0.5f, 0f));
|
||||
SetAutoProperty(targetEnemy, "FacingDirection", Vector2.right);
|
||||
Physics2D.SyncTransforms();
|
||||
yield return new WaitForFixedUpdate();
|
||||
|
||||
float initialPlayerHealth = playerHealth.CurrentHealth;
|
||||
float initialEnemyHealth = targetEnemy.CurrentHealth;
|
||||
|
||||
SetAutoProperty(player, "AttackIntentVelocity", Vector2.right * 3f);
|
||||
SetAutoProperty(player, "MoveDirection", Vector2.right);
|
||||
typeof(BumpCombatResolver)
|
||||
.GetMethod(
|
||||
"OnTriggerEnter2D",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?.Invoke(resolver, new object[] { crowdCollider });
|
||||
|
||||
Assert.That(crowd.IsDead, Is.True);
|
||||
yield return new WaitForFixedUpdate();
|
||||
Assert.That(playerBody.position.x, Is.EqualTo(0f).Within(0.01f));
|
||||
MethodInfo resolveNormalContact = typeof(BumpCombatResolver).GetMethod(
|
||||
"TryResolveNormalContact",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(resolveNormalContact, Is.Not.Null);
|
||||
|
||||
resolveNormalContact.Invoke(
|
||||
resolver,
|
||||
new object[] { targetEnemy, new Vector2(0.25f, 0f) });
|
||||
|
||||
Assert.That(targetEnemy.CurrentHealth, Is.LessThan(initialEnemyHealth), "Enemy must take damage from back bump");
|
||||
Assert.That(playerHealth.CurrentHealth, Is.EqualTo(initialPlayerHealth), "Player must NOT take damage from back bump");
|
||||
Assert.That(player.IsDamageKnockbackActive, Is.False, "Player must NOT receive damage knockback from back bump");
|
||||
|
||||
LogAssert.NoUnexpectedReceived();
|
||||
}
|
||||
|
||||
@@ -1580,6 +1766,8 @@ namespace BumpCombat.PlayModeTests
|
||||
PlayerStats stats = player.GetComponent<PlayerStats>();
|
||||
ActiveArtifactController artifacts =
|
||||
player.GetComponent<ActiveArtifactController>();
|
||||
Animator animator = player.GetComponent<Animator>();
|
||||
RunManager runManager = RunManager.Instance;
|
||||
|
||||
for (int i = 0; i < artifacts.OwnedArtifactCount * 2; i++)
|
||||
{
|
||||
@@ -1607,12 +1795,28 @@ namespace BumpCombat.PlayModeTests
|
||||
0.75f),
|
||||
Is.True);
|
||||
Assert.That(health.CurrentHealth, Is.Zero);
|
||||
Assert.That(runManager.IsGameOver, Is.False);
|
||||
Assert.That(runManager.IsGameOverPresentationActive, Is.True);
|
||||
Assert.That(RunManager.GameplayInputEnabled, Is.False);
|
||||
Assert.That(
|
||||
animator.GetCurrentAnimatorStateInfo(0).IsName(PlayerController.DeathStateName),
|
||||
Is.True);
|
||||
Assert.That(
|
||||
stats.GetStackCount(
|
||||
"pulse.damage-reduction",
|
||||
CharacterStat.IncomingDamage),
|
||||
Is.Zero);
|
||||
Assert.That(artifacts.IsExecutingArtifact, Is.False);
|
||||
|
||||
float timeout = Time.realtimeSinceStartup + 3f;
|
||||
while (!runManager.IsGameOver && Time.realtimeSinceStartup < timeout)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Assert.That(runManager.IsGameOver, Is.True);
|
||||
Assert.That(runManager.IsGameOverPresentationActive, Is.False);
|
||||
Assert.That(Time.timeScale, Is.Zero);
|
||||
LogAssert.NoUnexpectedReceived();
|
||||
}
|
||||
|
||||
@@ -1678,6 +1882,119 @@ namespace BumpCombat.PlayModeTests
|
||||
LogAssert.NoUnexpectedReceived();
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ValidRearHit_ShakesInKnockbackDirectionRefreshesCooldownAndIgnoresDashBack()
|
||||
{
|
||||
yield return SceneManager.LoadSceneAsync("CombatPrototype");
|
||||
yield return null;
|
||||
|
||||
var player = Object.FindFirstObjectByType<PlayerController>();
|
||||
var cameraFollow = Object.FindFirstObjectByType<ArenaCameraFollow>();
|
||||
Assert.IsNotNull(player);
|
||||
Assert.IsNotNull(cameraFollow);
|
||||
|
||||
var target = new GameObject("RearHitTarget");
|
||||
var shakeUnit = 1f / 32f;
|
||||
var firstDirectionOffset = new Vector2(shakeUnit, 0f);
|
||||
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
player.gameObject,
|
||||
target,
|
||||
false,
|
||||
HitSide.Back,
|
||||
10f,
|
||||
Vector2.right,
|
||||
1f,
|
||||
new Vector2(0.25f, 0f)));
|
||||
Assert.That(cameraFollow.ExternalShakeOffset, Is.EqualTo(firstDirectionOffset));
|
||||
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
player.gameObject,
|
||||
target,
|
||||
false,
|
||||
HitSide.Back,
|
||||
10f,
|
||||
Vector2.left,
|
||||
1f,
|
||||
new Vector2(0.25f, 0f)));
|
||||
Assert.That(cameraFollow.ExternalShakeOffset, Is.EqualTo(firstDirectionOffset));
|
||||
|
||||
yield return new WaitForSecondsRealtime(0.10f);
|
||||
Assert.That(cameraFollow.ExternalShakeOffset, Is.EqualTo(Vector2.zero));
|
||||
yield return new WaitForSecondsRealtime(0.13f);
|
||||
Assert.That(Time.timeScale, Is.EqualTo(1f));
|
||||
|
||||
yield return new WaitForSeconds(0.20f);
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
player.gameObject,
|
||||
target,
|
||||
false,
|
||||
HitSide.Back,
|
||||
10f,
|
||||
Vector2.left,
|
||||
1f,
|
||||
new Vector2(0.25f, 0f)));
|
||||
Assert.That(cameraFollow.ExternalShakeOffset, Is.EqualTo(Vector2.zero));
|
||||
yield return new WaitForSecondsRealtime(0.2f);
|
||||
|
||||
yield return new WaitForSeconds(0.15f);
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
player.gameObject,
|
||||
target,
|
||||
false,
|
||||
HitSide.Back,
|
||||
10f,
|
||||
Vector2.right,
|
||||
1f,
|
||||
new Vector2(0.25f, 0f)));
|
||||
Assert.That(cameraFollow.ExternalShakeOffset, Is.EqualTo(Vector2.zero));
|
||||
yield return new WaitForSecondsRealtime(0.2f);
|
||||
|
||||
yield return new WaitForSeconds(0.15f);
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
player.gameObject,
|
||||
target,
|
||||
false,
|
||||
HitSide.Back,
|
||||
10f,
|
||||
Vector2.left,
|
||||
1f,
|
||||
new Vector2(0.25f, 0f)));
|
||||
Assert.That(cameraFollow.ExternalShakeOffset, Is.EqualTo(Vector2.zero));
|
||||
yield return new WaitForSecondsRealtime(0.2f);
|
||||
|
||||
yield return new WaitForSeconds(0.45f);
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
player.gameObject,
|
||||
target,
|
||||
false,
|
||||
HitSide.Back,
|
||||
10f,
|
||||
Vector2.left,
|
||||
1f,
|
||||
new Vector2(0.25f, 0f)));
|
||||
Assert.That(
|
||||
cameraFollow.ExternalShakeOffset,
|
||||
Is.EqualTo(new Vector2(-shakeUnit, 0f)));
|
||||
yield return new WaitForSecondsRealtime(0.2f);
|
||||
|
||||
yield return new WaitForSeconds(0.45f);
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
player.gameObject,
|
||||
target,
|
||||
true,
|
||||
HitSide.Back,
|
||||
10f,
|
||||
Vector2.right,
|
||||
1f,
|
||||
new Vector2(0.25f, 0f)));
|
||||
Assert.That(cameraFollow.ExternalShakeOffset, Is.EqualTo(Vector2.zero));
|
||||
yield return new WaitForSecondsRealtime(0.2f);
|
||||
|
||||
Object.Destroy(target);
|
||||
LogAssert.NoUnexpectedReceived();
|
||||
}
|
||||
|
||||
private static IEnumerator AssertAreaArtifactBatchesHitFeedback(
|
||||
ActiveArtifactEffect expectedEffect,
|
||||
int selectionCount,
|
||||
@@ -2345,7 +2662,7 @@ namespace BumpCombat.PlayModeTests
|
||||
Assert.That(panelRect.anchorMin, Is.EqualTo(new Vector2(0f, 1f)));
|
||||
Assert.That(panelRect.anchorMax, Is.EqualTo(new Vector2(0f, 1f)));
|
||||
Assert.That(panelRect.pivot, Is.EqualTo(new Vector2(0f, 1f)));
|
||||
string[] labels = { "엘리트 출현", "중간보스 출현", "보스 출현" };
|
||||
string[] labels = { "엘리트 출현", "중간보스 출현", "보스방 이동" };
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
Assert.That(buttons[i].GetComponentInChildren<Text>().text, Is.EqualTo(labels[i]));
|
||||
@@ -2353,18 +2670,10 @@ namespace BumpCombat.PlayModeTests
|
||||
}
|
||||
|
||||
float elapsed = RunManager.Instance.ElapsedTime;
|
||||
ClickButtonThroughEventSystem(buttons[2]);
|
||||
Assert.That(RunManager.Instance.ElapsedTime, Is.EqualTo(elapsed));
|
||||
Assert.That(director.CanDebugSpawnEventEnemy(RunTimedEvent.FinalBoss), Is.False);
|
||||
Assert.That(FindEventEnemy(RunTimedEvent.FinalBoss), Is.Not.Null);
|
||||
|
||||
EnemyController firstFinalBoss = FindEventEnemy(RunTimedEvent.FinalBoss);
|
||||
yield return KillEventEnemyThroughGroggy(firstFinalBoss);
|
||||
Assert.That(director.CanDebugSpawnEventEnemy(RunTimedEvent.FinalBoss), Is.True);
|
||||
yield return null;
|
||||
Assert.That(buttons[2].interactable, Is.True);
|
||||
ClickButtonThroughEventSystem(buttons[2]);
|
||||
Assert.That(FindEventEnemy(RunTimedEvent.FinalBoss), Is.Not.Null);
|
||||
Assert.That(RunManager.Instance.ElapsedTime, Is.EqualTo(elapsed));
|
||||
yield return null;
|
||||
|
||||
ClickButtonThroughEventSystem(buttons[0]);
|
||||
ClickButtonThroughEventSystem(buttons[1]);
|
||||
@@ -2374,8 +2683,7 @@ namespace BumpCombat.PlayModeTests
|
||||
Assert.That(director.CanDebugSpawnEventEnemy(RunTimedEvent.Elite), Is.False);
|
||||
|
||||
RunManager.ForceProductionModeForTests = true;
|
||||
EnemyController secondFinalBoss = FindEventEnemy(RunTimedEvent.FinalBoss);
|
||||
yield return KillEventEnemyThroughGroggy(secondFinalBoss);
|
||||
yield return null;
|
||||
Assert.That(panel.activeSelf, Is.False);
|
||||
Assert.That(director.CanDebugSpawnEventEnemy(RunTimedEvent.FinalBoss), Is.False);
|
||||
Assert.That(director.TryDebugSpawnEventEnemy(RunTimedEvent.FinalBoss), Is.False);
|
||||
@@ -2423,12 +2731,12 @@ namespace BumpCombat.PlayModeTests
|
||||
eventSystem.GetComponent<UnityEngine.InputSystem.UI.InputSystemUIInputModule>();
|
||||
Assert.That(inputModule, Is.Not.Null);
|
||||
Assert.That(eventSystem.currentInputModule, Is.SameAs(inputModule));
|
||||
yield return ClickButtonWithVirtualMouse(buttons[2], mouse, inputModule);
|
||||
yield return ClickButtonWithVirtualMouse(buttons[0], mouse, inputModule);
|
||||
Assert.That(
|
||||
Object.FindAnyObjectByType<SpawnDirector>()
|
||||
.CanDebugSpawnEventEnemy(RunTimedEvent.FinalBoss),
|
||||
.CanDebugSpawnEventEnemy(RunTimedEvent.Elite),
|
||||
Is.False);
|
||||
Assert.That(FindEventEnemy(RunTimedEvent.FinalBoss), Is.Not.Null);
|
||||
Assert.That(FindEventEnemy(RunTimedEvent.Elite), Is.Not.Null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace BumpCombat.PlayModeTests
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator EnemyStatusVisual_LoadsBothSheetsAndSharesGroundAnchor()
|
||||
public IEnumerator EnemyStatusVisual_LoadsBothSheetsAndFollowsRenderedAnchor()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
EnemyController enemy = FindOrSpawnEnemy();
|
||||
@@ -151,7 +151,7 @@ namespace BumpCombat.PlayModeTests
|
||||
Assert.That(source == null, Is.False);
|
||||
enemy.transform.localScale = Vector3.one * 0.8f;
|
||||
source.flipX = true;
|
||||
source.flipY = false;
|
||||
source.flipY = true;
|
||||
|
||||
Assert.That(enemy.ApplyIgnite(3f, 0.5f, 0.01f), Is.True);
|
||||
Assert.That(enemy.ApplyShock(3f, 0.2f), Is.True);
|
||||
@@ -172,15 +172,18 @@ namespace BumpCombat.PlayModeTests
|
||||
Assert.That(ignite.sprite.pixelsPerUnit, Is.EqualTo(32f).Within(0.01f));
|
||||
Assert.That(shock.sprite.pixelsPerUnit, Is.EqualTo(32f).Within(0.01f));
|
||||
|
||||
Vector3 expectedStatusPosition = ExpectedStatusPosition(enemy, source);
|
||||
Assert.That(
|
||||
Vector3.Distance(
|
||||
ignite.transform.position,
|
||||
(Vector3)enemy.GroundAnchorPosition),
|
||||
ignite.transform.parent,
|
||||
Is.EqualTo(GetCurrentVisualTransform(enemy)));
|
||||
Assert.That(
|
||||
shock.transform.parent,
|
||||
Is.EqualTo(GetCurrentVisualTransform(enemy)));
|
||||
Assert.That(
|
||||
Vector3.Distance(ignite.transform.position, expectedStatusPosition),
|
||||
Is.LessThan(0.0001f));
|
||||
Assert.That(
|
||||
Vector3.Distance(
|
||||
shock.transform.position,
|
||||
(Vector3)enemy.GroundAnchorPosition),
|
||||
Vector3.Distance(shock.transform.position, expectedStatusPosition),
|
||||
Is.LessThan(0.0001f));
|
||||
Assert.That(ignite.flipX, Is.EqualTo(source.flipX));
|
||||
Assert.That(shock.flipX, Is.EqualTo(source.flipX));
|
||||
@@ -203,6 +206,122 @@ namespace BumpCombat.PlayModeTests
|
||||
Assert.That(enemy.transform.Find("Stun Runes") == null, Is.False);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator EnemyStatusVisual_UsesRenderedTransformWhenPhysicsPositionDiffers()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
EnemyController enemy = FindOrSpawnEnemy();
|
||||
Assert.That(enemy, Is.Not.Null);
|
||||
|
||||
Rigidbody2D body = enemy.GetComponent<Rigidbody2D>();
|
||||
SpriteRenderer source = enemy.GetComponent<SpriteRenderer>();
|
||||
Assert.That(body, Is.Not.Null);
|
||||
Assert.That(source, Is.Not.Null);
|
||||
|
||||
Time.timeScale = 0f;
|
||||
body.simulated = false;
|
||||
|
||||
Vector2 physicalPosition = (Vector2)enemy.transform.position
|
||||
+ Vector2.down * 3f;
|
||||
Vector3 renderedPosition = enemy.transform.position
|
||||
+ Vector3.up * 1.5f;
|
||||
body.position = physicalPosition;
|
||||
enemy.transform.position = renderedPosition;
|
||||
|
||||
Assert.That(
|
||||
Vector2.Distance(body.position, enemy.transform.position),
|
||||
Is.GreaterThan(1f));
|
||||
Assert.That(enemy.ApplyIgnite(3f, 0.5f, 0.01f), Is.True);
|
||||
|
||||
ArtifactStatusVisual statusVisual =
|
||||
enemy.GetComponent<ArtifactStatusVisual>();
|
||||
Assert.That(statusVisual, Is.Not.Null);
|
||||
statusVisual.SendMessage(
|
||||
"LateUpdate",
|
||||
SendMessageOptions.RequireReceiver);
|
||||
|
||||
SpriteRenderer ignite = FindChildRenderer(enemy, IgniteVisualName);
|
||||
Assert.That(ignite, Is.Not.Null);
|
||||
Assert.That(
|
||||
Vector3.Distance(
|
||||
ignite.transform.position,
|
||||
ExpectedStatusPosition(enemy, source)),
|
||||
Is.LessThan(0.0001f));
|
||||
Assert.That(
|
||||
Vector2.Distance(ignite.transform.position, body.position),
|
||||
Is.GreaterThan(0.5f));
|
||||
|
||||
body.simulated = true;
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator EnemyStatusVisual_FollowsLaunchAndFogVisibility()
|
||||
{
|
||||
yield return LoadCombatPrototype();
|
||||
EnemyController enemy = FindOrSpawnEnemy();
|
||||
Assert.That(enemy, Is.Not.Null);
|
||||
|
||||
SpriteRenderer source = enemy.GetComponent<SpriteRenderer>();
|
||||
Assert.That(source, Is.Not.Null);
|
||||
Assert.That(enemy.ApplyIgnite(3f, 0.5f, 0.01f), Is.True);
|
||||
yield return null;
|
||||
|
||||
SpriteRenderer ignite = FindChildRenderer(enemy, IgniteVisualName);
|
||||
Assert.That(ignite, Is.Not.Null);
|
||||
|
||||
enemy.PlayLaunchVisual(Vector2.right, 0.8f, 0.4f);
|
||||
yield return null;
|
||||
|
||||
Transform launchVisual = GetCurrentVisualTransform(enemy);
|
||||
Assert.That(enemy.IsLaunchVisualActive, Is.True);
|
||||
Assert.That(launchVisual, Is.Not.EqualTo(enemy.transform));
|
||||
Assert.That(ignite.transform.parent, Is.EqualTo(launchVisual));
|
||||
Assert.That(
|
||||
Vector3.Distance(
|
||||
ignite.transform.position,
|
||||
ExpectedStatusPosition(enemy, source)),
|
||||
Is.LessThan(0.0001f));
|
||||
|
||||
enemy.SetFogVisibility(false);
|
||||
yield return null;
|
||||
Assert.That(ignite.forceRenderingOff, Is.True);
|
||||
|
||||
SpriteRenderer airborneRenderer =
|
||||
launchVisual.GetComponent<SpriteRenderer>();
|
||||
Assert.That(airborneRenderer, Is.Not.Null);
|
||||
Assert.That(airborneRenderer.forceRenderingOff, Is.True);
|
||||
|
||||
Assert.That(enemy.ApplyShock(3f, 0.2f), Is.True);
|
||||
yield return null;
|
||||
SpriteRenderer shock = FindChildRenderer(enemy, ShockVisualName);
|
||||
Assert.That(shock, Is.Not.Null);
|
||||
Assert.That(shock.forceRenderingOff, Is.True);
|
||||
|
||||
enemy.SetFogVisibility(true);
|
||||
yield return null;
|
||||
Assert.That(ignite.forceRenderingOff, Is.False);
|
||||
Assert.That(shock.forceRenderingOff, Is.False);
|
||||
Assert.That(airborneRenderer.forceRenderingOff, Is.False);
|
||||
|
||||
yield return new WaitForSeconds(0.45f);
|
||||
yield return null;
|
||||
|
||||
Assert.That(enemy.IsLaunchVisualActive, Is.False);
|
||||
Assert.That(enemy.IsIgnited, Is.True);
|
||||
|
||||
SpriteRenderer landedIgnite =
|
||||
FindChildRenderer(enemy, IgniteVisualName);
|
||||
Assert.That(landedIgnite, Is.Not.Null);
|
||||
Assert.That(
|
||||
landedIgnite.transform.parent,
|
||||
Is.EqualTo(enemy.transform));
|
||||
Assert.That(
|
||||
Vector3.Distance(
|
||||
landedIgnite.transform.position,
|
||||
ExpectedStatusPosition(enemy, source)),
|
||||
Is.LessThan(0.0001f));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator EnemyStatusVisual_FreezesDuringPauseAndAdvancesAfterResume()
|
||||
{
|
||||
@@ -438,6 +557,26 @@ namespace BumpCombat.PlayModeTests
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Transform GetCurrentVisualTransform(EnemyController enemy)
|
||||
{
|
||||
Transform airborneVisual =
|
||||
enemy.transform.Find("Airborne Enemy Visual");
|
||||
return airborneVisual != null
|
||||
? airborneVisual
|
||||
: enemy.transform;
|
||||
}
|
||||
|
||||
private static Vector3 ExpectedStatusPosition(
|
||||
EnemyController enemy,
|
||||
SpriteRenderer source)
|
||||
{
|
||||
float anchorOffsetY = source.flipY
|
||||
? 13f / 32f
|
||||
: -13f / 32f;
|
||||
return GetCurrentVisualTransform(enemy).TransformPoint(
|
||||
new Vector3(0f, anchorOffsetY, 0f));
|
||||
}
|
||||
|
||||
private static SpriteRenderer FindChildRenderer(
|
||||
EnemyController enemy,
|
||||
string objectName)
|
||||
|
||||
@@ -2,11 +2,13 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Progression;
|
||||
using BumpCombat.Spawning;
|
||||
using BumpCombat.Stage;
|
||||
using BumpCombat.UI;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
@@ -676,8 +678,7 @@ namespace BumpCombat.PlayModeTests
|
||||
AssertRect(eventText.rectTransform, new Vector2(0.5f, 1f),
|
||||
new Vector2(0.5f, 1f), new Vector2(0f, -100f),
|
||||
new Vector2(420f, 28f));
|
||||
Assert.That(eventText.gameObject.activeSelf, Is.True);
|
||||
Assert.That(eventText.text, Does.Contain("다음"));
|
||||
Assert.That(eventText.gameObject.activeSelf, Is.False);
|
||||
Assert.That(debug.gameObject.activeSelf, Is.True);
|
||||
AssertRect(gauge, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f),
|
||||
new Vector2(0f, -24f), new Vector2(352f, 18f));
|
||||
@@ -780,25 +781,13 @@ namespace BumpCombat.PlayModeTests
|
||||
gridSnapping.GetValue(pixelPerfect).ToString(),
|
||||
Is.EqualTo("PixelSnapping"));
|
||||
|
||||
RenderTexture target = new(1399, 799, 24, RenderTextureFormat.ARGB32);
|
||||
target.Create();
|
||||
RenderTexture previousTarget = camera.targetTexture;
|
||||
camera.targetTexture = target;
|
||||
RunConstants stageConstants = GameplayConstants.Current.Run;
|
||||
try
|
||||
{
|
||||
camera.Render();
|
||||
yield return null;
|
||||
|
||||
Rect pixelRect = camera.pixelRect;
|
||||
Assert.That(pixelRect.width, Is.EqualTo(1398f).Within(0.01f));
|
||||
Assert.That(pixelRect.height, Is.EqualTo(798f).Within(0.01f));
|
||||
Assert.That(pixelRect.x, Is.EqualTo(0f).Within(0.01f));
|
||||
Assert.That(pixelRect.y, Is.EqualTo(0f).Within(0.01f));
|
||||
Assert.That(pixelRect.width % 2f, Is.EqualTo(0f).Within(0.01f));
|
||||
Assert.That(pixelRect.height % 2f, Is.EqualTo(0f).Within(0.01f));
|
||||
Assert.That(camera.aspect, Is.EqualTo(1398f / 798f).Within(0.001f));
|
||||
Assert.That(camera.orthographicSize, Is.EqualTo(6.234375f).Within(0.01f));
|
||||
|
||||
PropertyInfo assetsPpu = pixelPerfect.GetType().GetProperty("assetsPPU");
|
||||
PropertyInfo referenceWidth = pixelPerfect.GetType().GetProperty("refResolutionX");
|
||||
PropertyInfo referenceHeight = pixelPerfect.GetType().GetProperty("refResolutionY");
|
||||
FieldInfo internalField = pixelPerfect.GetType().GetField(
|
||||
"m_Internal",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
@@ -812,31 +801,66 @@ namespace BumpCombat.PlayModeTests
|
||||
FieldInfo useOffscreenField = internalCamera.GetType().GetField(
|
||||
"useOffscreenRT",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(widthField.GetValue(internalCamera), Is.EqualTo(0));
|
||||
Assert.That(heightField.GetValue(internalCamera), Is.EqualTo(0));
|
||||
Assert.That(useOffscreenField.GetValue(internalCamera), Is.False);
|
||||
int[,] targetSizes = { { 1920, 1080 }, { 1280, 720 }, { 1399, 799 } };
|
||||
for (int i = 0; i < targetSizes.GetLength(0); i++)
|
||||
{
|
||||
RenderTexture target = new(
|
||||
targetSizes[i, 0],
|
||||
targetSizes[i, 1],
|
||||
24,
|
||||
RenderTextureFormat.ARGB32);
|
||||
target.Create();
|
||||
camera.targetTexture = target;
|
||||
try
|
||||
{
|
||||
camera.Render();
|
||||
yield return null;
|
||||
|
||||
float correctedOrthoSize = camera.orthographicSize;
|
||||
camera.Render();
|
||||
Assert.That(camera.pixelRect.width, Is.EqualTo(1398f).Within(0.01f));
|
||||
Assert.That(camera.pixelRect.height, Is.EqualTo(798f).Within(0.01f));
|
||||
Assert.That(camera.orthographicSize,
|
||||
Is.EqualTo(correctedOrthoSize).Within(0.01f));
|
||||
int targetWidth = targetSizes[i, 0];
|
||||
int targetHeight = targetSizes[i, 1];
|
||||
int expectedWidth = targetWidth & ~1;
|
||||
int expectedHeight = targetHeight & ~1;
|
||||
Rect pixelRect = camera.pixelRect;
|
||||
Assert.That(pixelRect.width, Is.EqualTo(expectedWidth).Within(0.01f));
|
||||
Assert.That(pixelRect.height, Is.EqualTo(expectedHeight).Within(0.01f));
|
||||
Assert.That(pixelRect.x, Is.EqualTo(0f).Within(0.01f));
|
||||
Assert.That(pixelRect.y, Is.EqualTo(0f).Within(0.01f));
|
||||
Assert.That(camera.aspect,
|
||||
Is.EqualTo(expectedWidth / (float)expectedHeight).Within(0.001f));
|
||||
Assert.That(camera.orthographicSize,
|
||||
Is.EqualTo(stageConstants.StageCameraViewHalfExtents.y
|
||||
* expectedHeight / targetHeight).Within(0.01f));
|
||||
Assert.That(assetsPpu.GetValue(pixelPerfect), Is.EqualTo(32));
|
||||
Assert.That(
|
||||
referenceWidth.GetValue(pixelPerfect),
|
||||
Is.EqualTo(Mathf.RoundToInt(
|
||||
stageConstants.StageCameraViewHalfExtents.x * 2f * 32f)));
|
||||
Assert.That(
|
||||
referenceHeight.GetValue(pixelPerfect),
|
||||
Is.EqualTo(Mathf.RoundToInt(
|
||||
stageConstants.StageCameraViewHalfExtents.y * 2f * 32f)));
|
||||
Assert.That(widthField.GetValue(internalCamera), Is.EqualTo(0));
|
||||
Assert.That(heightField.GetValue(internalCamera), Is.EqualTo(0));
|
||||
Assert.That(useOffscreenField.GetValue(internalCamera), Is.False);
|
||||
|
||||
ArenaCameraFollow follow = camera.GetComponent<ArenaCameraFollow>();
|
||||
bool previousFollowEnabled = follow.enabled;
|
||||
follow.enabled = false;
|
||||
camera.Render();
|
||||
follow.enabled = previousFollowEnabled;
|
||||
camera.Render();
|
||||
Assert.That(camera.pixelRect.width, Is.EqualTo(1398f).Within(0.01f));
|
||||
Assert.That(camera.pixelRect.height, Is.EqualTo(798f).Within(0.01f));
|
||||
camera.Render();
|
||||
Assert.That(camera.pixelRect.width, Is.EqualTo(expectedWidth).Within(0.01f));
|
||||
Assert.That(camera.pixelRect.height, Is.EqualTo(expectedHeight).Within(0.01f));
|
||||
Assert.That(camera.orthographicSize,
|
||||
Is.EqualTo(stageConstants.StageCameraViewHalfExtents.y
|
||||
* expectedHeight / targetHeight).Within(0.01f));
|
||||
}
|
||||
finally
|
||||
{
|
||||
camera.targetTexture = previousTarget;
|
||||
target.Release();
|
||||
Object.Destroy(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
camera.targetTexture = previousTarget;
|
||||
target.Release();
|
||||
Object.Destroy(target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -846,10 +870,14 @@ namespace BumpCombat.PlayModeTests
|
||||
yield return LoadCombatPrototype();
|
||||
|
||||
RunHUD hud = Object.FindAnyObjectByType<RunHUD>();
|
||||
StageManager stageManager = StageManager.Instance;
|
||||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||||
PlayerController player = Object.FindAnyObjectByType<PlayerController>();
|
||||
ArenaBounds arena = ArenaBounds.Resolve();
|
||||
Assert.That(hud, Is.Not.Null);
|
||||
Assert.That(stageManager, Is.Not.Null);
|
||||
Assert.That(stageManager.CurrentZone, Is.Not.Null);
|
||||
Assert.That(stageManager.CurrentZone.Preset, Is.Not.Null);
|
||||
Assert.That(arena, Is.Not.Null);
|
||||
|
||||
director.enabled = false;
|
||||
@@ -867,6 +895,28 @@ namespace BumpCombat.PlayModeTests
|
||||
Time.timeScale = 0f;
|
||||
yield return new WaitForSecondsRealtime(0.2f);
|
||||
|
||||
Camera worldCamera = Camera.main;
|
||||
EnemyController eventEnemy = null;
|
||||
EnemyController[] activeEnemies = Object.FindObjectsByType<EnemyController>(
|
||||
FindObjectsInactive.Exclude,
|
||||
FindObjectsSortMode.None);
|
||||
for (int i = 0; i < activeEnemies.Length; i++)
|
||||
{
|
||||
if (activeEnemies[i].IsEventEnemy)
|
||||
{
|
||||
eventEnemy = activeEnemies[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.That(eventEnemy, Is.Not.Null);
|
||||
ZoneInstance visibilityZone = stageManager.CurrentZone;
|
||||
Vector2 onScreenPosition = player.transform.position;
|
||||
visibilityZone.UpdateVisibility(onScreenPosition);
|
||||
eventEnemy.GetComponent<Rigidbody2D>().position = onScreenPosition;
|
||||
eventEnemy.transform.position = onScreenPosition;
|
||||
Physics2D.SyncTransforms();
|
||||
yield return new WaitForSecondsRealtime(0.12f);
|
||||
|
||||
GameObject minimap = GameObject.Find("Minimap");
|
||||
Image playerMarker = GameObject.Find("Minimap Player")
|
||||
.GetComponent<Image>();
|
||||
@@ -884,7 +934,73 @@ namespace BumpCombat.PlayModeTests
|
||||
}
|
||||
RectTransform viewport = GameObject.Find("Minimap Camera Viewport")
|
||||
.GetComponent<RectTransform>();
|
||||
Transform terrain = GameObject.Find("Minimap Terrain")?.transform;
|
||||
Assert.That(minimap.activeSelf, Is.True);
|
||||
Assert.That(terrain, Is.Not.Null);
|
||||
int expectedTerrainCells = 0;
|
||||
RoomPreset currentPreset = stageManager.CurrentZone.Preset;
|
||||
for (int y = 0; y < currentPreset.GridHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < currentPreset.GridWidth; x++)
|
||||
{
|
||||
PresetCellType cellType = stageManager.CurrentZone.GetRuntimeCell(x, y);
|
||||
if (cellType != PresetCellType.Empty)
|
||||
{
|
||||
expectedTerrainCells++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.That(terrain.childCount, Is.EqualTo(expectedTerrainCells));
|
||||
for (int i = 0; i < currentPreset.Sockets.Count; i++)
|
||||
{
|
||||
RoomPresetSocket socket = currentPreset.Sockets[i];
|
||||
for (int offset = 0; offset < socket.Width; offset++)
|
||||
{
|
||||
Vector2Int socketCell = socket.CellPosition;
|
||||
if (socket.Direction == StageDirection.North ||
|
||||
socket.Direction == StageDirection.South)
|
||||
{
|
||||
socketCell.x += offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
socketCell.y += offset;
|
||||
}
|
||||
|
||||
bool isActive = stageManager.CurrentZone.GetRuntimeCell(socketCell.x, socketCell.y)
|
||||
== PresetCellType.Socket;
|
||||
|
||||
Image terrainCell = terrain.Find($"Terrain {socketCell.x},{socketCell.y}")
|
||||
?.GetComponent<Image>();
|
||||
if (isActive)
|
||||
{
|
||||
Assert.That(terrainCell, Is.Not.Null, socket.Id);
|
||||
Color32 terrainColor = terrainCell.color;
|
||||
if (visibilityZone.IsCellVisible(socketCell.x, socketCell.y))
|
||||
{
|
||||
Assert.That(
|
||||
terrainColor.Equals(new Color32(54, 103, 112, 230)),
|
||||
Is.True,
|
||||
socket.Id);
|
||||
}
|
||||
else if (visibilityZone.IsCellExplored(socketCell.x, socketCell.y))
|
||||
{
|
||||
Assert.That(terrainColor, Is.Not.EqualTo(
|
||||
new Color32(54, 103, 112, 230)), socket.Id);
|
||||
Assert.That(terrainColor.a, Is.EqualTo(255), socket.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.That(terrainColor, Is.EqualTo(new Color32(2, 4, 5, 255)),
|
||||
socket.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.That(terrainCell, Is.Null, socket.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.That(
|
||||
((Color32)playerMarker.color).Equals(
|
||||
new Color32(106, 236, 255, 255)),
|
||||
@@ -898,6 +1014,68 @@ namespace BumpCombat.PlayModeTests
|
||||
Assert.That(viewport.rect.width, Is.GreaterThan(0f));
|
||||
Assert.That(viewport.rect.height, Is.GreaterThan(0f));
|
||||
|
||||
Vector2 nearEdgePosition = onScreenPosition;
|
||||
bool foundVisiblePosition = false;
|
||||
for (int y = 0; y < currentPreset.GridHeight && !foundVisiblePosition; y++)
|
||||
{
|
||||
for (int x = 0; x < currentPreset.GridWidth; x++)
|
||||
{
|
||||
if (!visibilityZone.IsCellVisible(x, y))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 candidate = currentPreset.CellToWorldPosition(
|
||||
visibilityZone.ZoneCenter,
|
||||
new Vector2Int(x, y));
|
||||
if (Vector2.Distance(candidate, onScreenPosition) > 0.5f
|
||||
&& arena.IsInsideCameraVisible(worldCamera, candidate))
|
||||
{
|
||||
nearEdgePosition = candidate;
|
||||
foundVisiblePosition = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(foundVisiblePosition, Is.True);
|
||||
eventEnemy.GetComponent<Rigidbody2D>().position = nearEdgePosition;
|
||||
eventEnemy.transform.position = nearEdgePosition;
|
||||
Physics2D.SyncTransforms();
|
||||
yield return new WaitForSecondsRealtime(0.12f);
|
||||
Assert.That(visibilityZone.IsWorldPositionVisible(nearEdgePosition), Is.True);
|
||||
Assert.That(eventMarker.gameObject.activeSelf, Is.True, "An enemy inside the current camera view should have a marker");
|
||||
|
||||
Vector2 outsidePosition = onScreenPosition + Vector2.right * 6.5f;
|
||||
bool foundHiddenPosition = false;
|
||||
for (int y = 0; y < currentPreset.GridHeight && !foundHiddenPosition; y++)
|
||||
{
|
||||
for (int x = 0; x < currentPreset.GridWidth; x++)
|
||||
{
|
||||
if (visibilityZone.IsCellVisible(x, y))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 candidate = currentPreset.CellToWorldPosition(
|
||||
visibilityZone.ZoneCenter,
|
||||
new Vector2Int(x, y));
|
||||
if (arena.IsInsideCameraVisible(worldCamera, candidate))
|
||||
{
|
||||
outsidePosition = candidate;
|
||||
foundHiddenPosition = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eventEnemy.GetComponent<Rigidbody2D>().position = outsidePosition;
|
||||
eventEnemy.transform.position = outsidePosition;
|
||||
Physics2D.SyncTransforms();
|
||||
yield return new WaitForSecondsRealtime(0.12f);
|
||||
Assert.That(visibilityZone.IsWorldPositionVisible(outsidePosition), Is.False);
|
||||
Assert.That(eventMarker.gameObject.activeSelf, Is.False, "An enemy outside the current camera view should not have a marker");
|
||||
|
||||
Vector2 oldMarkerPosition = playerMarker.rectTransform.anchoredPosition;
|
||||
Vector2 movedPosition = arena.ClampPlayerPosition(new Vector2(6f, 2f));
|
||||
player.GetComponent<Rigidbody2D>().position = movedPosition;
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
using System.Collections;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Stage;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace BumpCombat.PlayModeTests
|
||||
{
|
||||
public sealed class ZoneArrivalPlayModeTests
|
||||
{
|
||||
private GameObject arenaObject;
|
||||
private GameObject playerObject;
|
||||
private GameObject stageObject;
|
||||
private RunManager existingRunManager;
|
||||
private bool existingRunManagerWasEnabled;
|
||||
private float previousTimeScale;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
previousTimeScale = Time.timeScale;
|
||||
existingRunManager = Object.FindAnyObjectByType<RunManager>();
|
||||
if (existingRunManager != null)
|
||||
{
|
||||
existingRunManagerWasEnabled = existingRunManager.enabled;
|
||||
existingRunManager.enabled = false;
|
||||
}
|
||||
|
||||
Time.timeScale = 1f;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (stageObject != null)
|
||||
{
|
||||
Object.Destroy(stageObject);
|
||||
}
|
||||
|
||||
if (playerObject != null)
|
||||
{
|
||||
Object.Destroy(playerObject);
|
||||
}
|
||||
|
||||
if (arenaObject != null)
|
||||
{
|
||||
Object.Destroy(arenaObject);
|
||||
}
|
||||
|
||||
if (existingRunManager != null)
|
||||
{
|
||||
existingRunManager.enabled = existingRunManagerWasEnabled;
|
||||
}
|
||||
|
||||
Time.timeScale = previousTimeScale;
|
||||
}
|
||||
|
||||
[Timeout(60000)]
|
||||
[UnityTest]
|
||||
public IEnumerator ArrivalDoor_RejectsImmediateReverseAndRetriesOnStayAfterCooldown()
|
||||
{
|
||||
arenaObject = new GameObject("ArrivalPlayMode_ArenaBounds");
|
||||
arenaObject.AddComponent<ArenaBounds>();
|
||||
|
||||
playerObject = new GameObject("ArrivalPlayMode_Player");
|
||||
PlayerController player = playerObject.AddComponent<PlayerController>();
|
||||
CircleCollider2D playerBody = playerObject.AddComponent<CircleCollider2D>();
|
||||
playerBody.radius = 0.22f;
|
||||
playerBody.offset = new Vector2(0.11f, -0.07f);
|
||||
Rigidbody2D rigidbody = playerObject.GetComponent<Rigidbody2D>();
|
||||
rigidbody.bodyType = RigidbodyType2D.Kinematic;
|
||||
rigidbody.gravityScale = 0f;
|
||||
rigidbody.constraints = RigidbodyConstraints2D.FreezeRotation;
|
||||
playerObject.GetComponent<Animator>().enabled = false;
|
||||
player.enabled = false;
|
||||
|
||||
stageObject = new GameObject("ArrivalPlayMode_StageManager");
|
||||
StageManager stageManager = stageObject.AddComponent<StageManager>();
|
||||
stageManager.StartRun(4242);
|
||||
|
||||
// StageManager.Start also builds its autonomous test stage. Let
|
||||
// that lifecycle pass before taking connector references.
|
||||
yield return null;
|
||||
|
||||
ZoneInstance startZone = stageManager.CurrentZone;
|
||||
Assert.That(startZone, Is.Not.Null);
|
||||
ZoneConnector sourceConnector = FindOutgoingConnector(stageManager);
|
||||
Assert.That(sourceConnector, Is.Not.Null);
|
||||
|
||||
yield return new WaitForSecondsRealtime(0.4f);
|
||||
MoveBodyCenter(player, playerBody, sourceConnector.GetComponent<BoxCollider2D>().bounds.center);
|
||||
yield return new WaitForFixedUpdate();
|
||||
|
||||
int targetNodeId = sourceConnector.TargetNodeId;
|
||||
float transitionDeadline = Time.realtimeSinceStartup + 2f;
|
||||
while ((stageManager.IsTransitioning
|
||||
|| stageManager.CurrentZone == null
|
||||
|| stageManager.CurrentZone.NodeId != targetNodeId)
|
||||
&& Time.realtimeSinceStartup < transitionDeadline)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Assert.That(stageManager.CurrentZone.NodeId, Is.EqualTo(targetNodeId));
|
||||
Assert.That(stageManager.IsTransitioning, Is.False);
|
||||
|
||||
ZoneConnector arrivalConnector = stageManager.CurrentZone.GetConnector(sourceConnector.TargetSocketId);
|
||||
Assert.That(arrivalConnector, Is.Not.Null);
|
||||
arrivalConnector.SetLocked(false);
|
||||
BoxCollider2D arrivalTrigger = arrivalConnector.GetComponent<BoxCollider2D>();
|
||||
Assert.That(arrivalTrigger.bounds.Intersects(playerBody.bounds), Is.False);
|
||||
|
||||
// Enter the destination trigger immediately after transition. The
|
||||
// trigger stay must be ignored until the manager cooldown expires.
|
||||
MoveBodyCenter(player, playerBody, arrivalTrigger.bounds.center);
|
||||
yield return new WaitForFixedUpdate();
|
||||
yield return new WaitForSecondsRealtime(0.1f);
|
||||
Assert.That(stageManager.CurrentZone.NodeId, Is.EqualTo(targetNodeId));
|
||||
|
||||
float returnDeadline = Time.realtimeSinceStartup + 2f;
|
||||
while ((stageManager.IsTransitioning
|
||||
|| stageManager.CurrentZone == null
|
||||
|| stageManager.CurrentZone.NodeId != startZone.NodeId)
|
||||
&& Time.realtimeSinceStartup < returnDeadline)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Assert.That(stageManager.CurrentZone.NodeId, Is.EqualTo(startZone.NodeId));
|
||||
|
||||
// The return arrival is also outside its doorway. Leaving the
|
||||
// player there must not produce an automatic pingpong transition.
|
||||
yield return new WaitForSecondsRealtime(0.45f);
|
||||
Assert.That(stageManager.CurrentZone.NodeId, Is.EqualTo(startZone.NodeId));
|
||||
}
|
||||
|
||||
private static ZoneConnector FindOutgoingConnector(StageManager stageManager)
|
||||
{
|
||||
for (int direction = 0; direction < 4; direction++)
|
||||
{
|
||||
ZoneConnector connector = stageManager.CurrentZone.GetConnector((StageDirection)direction);
|
||||
if (connector != null && connector.TargetNodeId >= 0)
|
||||
{
|
||||
return connector;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void MoveBodyCenter(
|
||||
PlayerController player,
|
||||
Collider2D playerBody,
|
||||
Vector2 bodyCenter)
|
||||
{
|
||||
Vector2 bodyOffset = (Vector2)playerBody.bounds.center
|
||||
- (Vector2)player.transform.position;
|
||||
player.transform.position = bodyCenter - bodyOffset;
|
||||
Physics2D.SyncTransforms();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a4d1e6f9b824c1aa5e0d3f7b6c29841
|
||||
Reference in New Issue
Block a user