Initial commit: Tiny Tackle Heroes Unity project
This commit is contained in:
@@ -0,0 +1,886 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using BumpCombat.Audio;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Progression;
|
||||
using BumpCombat.Spawning;
|
||||
using BumpCombat.UI;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace BumpCombat.PlayModeTests
|
||||
{
|
||||
public sealed class PresentationAudioMenuTests
|
||||
{
|
||||
private const string SceneName = "CombatPrototype";
|
||||
private float originalMaster;
|
||||
private float originalMusic;
|
||||
private float originalSfx;
|
||||
private bool capturedVolumes;
|
||||
private InputSettings.BackgroundBehavior previousBackgroundBehavior;
|
||||
private InputSettings.EditorInputBehaviorInPlayMode previousEditorInputBehavior;
|
||||
private bool previousRunInBackground;
|
||||
private Keyboard virtualKeyboard;
|
||||
private bool virtualKeyboardInputActive;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
ArtifactRewardController.GrantCatalogForTests = false;
|
||||
RunManager.ForceProductionModeForTests = true;
|
||||
Time.timeScale = 1f;
|
||||
capturedVolumes = false;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
EndVirtualKeyboardInput();
|
||||
BumpCombatAudioService service =
|
||||
Object.FindAnyObjectByType<BumpCombatAudioService>();
|
||||
if (service != null && capturedVolumes)
|
||||
{
|
||||
service.SetMasterVolume(originalMaster);
|
||||
service.SetMusicVolume(originalMusic);
|
||||
service.SetSfxVolume(originalSfx);
|
||||
}
|
||||
ArtifactRewardController.GrantCatalogForTests = false;
|
||||
RunManager.ForceProductionModeForTests = false;
|
||||
Time.timeScale = 1f;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DevelopmentStartVisibility_RequiresEditorOrDevelopmentBuild()
|
||||
{
|
||||
MethodInfo predicate = typeof(RunMenuController).GetMethod(
|
||||
"IsDevelopmentStartVisible",
|
||||
BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Assert.That(predicate, Is.Not.Null);
|
||||
Assert.That(predicate.Invoke(null, new object[] { false, false }), Is.False);
|
||||
Assert.That(predicate.Invoke(null, new object[] { true, false }), Is.True);
|
||||
Assert.That(predicate.Invoke(null, new object[] { false, true }), Is.True);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TitleStart_UnlocksRunAfterExplicitStart()
|
||||
{
|
||||
yield return LoadScene();
|
||||
RunManager manager = Object.FindAnyObjectByType<RunManager>();
|
||||
Assert.That(manager, Is.Not.Null);
|
||||
Assert.That(manager.IsTitleScreen, Is.True);
|
||||
Assert.That(RunManager.GameplayInputEnabled, Is.False);
|
||||
Assert.That(Time.timeScale, Is.Zero);
|
||||
|
||||
Keyboard keyboard = BeginVirtualKeyboardInput();
|
||||
try
|
||||
{
|
||||
yield return PressAndRelease(keyboard, Key.Space);
|
||||
Assert.That(manager.IsTitleScreen, Is.True,
|
||||
"Space opens the mode submenu without starting a run.");
|
||||
yield return PressAndRelease(keyboard, Key.Enter);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndVirtualKeyboardInput();
|
||||
}
|
||||
Assert.That(manager.IsTitleScreen, Is.False);
|
||||
Assert.That(manager.IsProductionRun, Is.False);
|
||||
Assert.That(manager.UseDebugEventTimes, Is.True);
|
||||
Assert.That(manager.IsArtifactChargeUnlocked, Is.True);
|
||||
ArtifactRewardController rewards =
|
||||
Object.FindAnyObjectByType<ArtifactRewardController>();
|
||||
Assert.That(rewards, Is.Not.Null);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugIsSelectionVisible, Is.True);
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
RunTutorialController tutorials =
|
||||
Object.FindAnyObjectByType<RunTutorialController>();
|
||||
Assert.That(tutorials.DebugCurrentTutorial,
|
||||
Is.EqualTo(RunTutorialController.TutorialKind.Artifact));
|
||||
Assert.That(tutorials.DebugContinue(), Is.True);
|
||||
Assert.That(tutorials.DebugCurrentTutorial,
|
||||
Is.EqualTo(RunTutorialController.TutorialKind.Guard));
|
||||
Assert.That(tutorials.DebugContinue(), Is.True);
|
||||
Assert.That(tutorials.DebugCurrentTutorial,
|
||||
Is.EqualTo(RunTutorialController.TutorialKind.ArtifactEnhancement));
|
||||
Assert.That(tutorials.DebugContinue(), Is.True);
|
||||
Assert.That(RunManager.GameplayInputEnabled, Is.True);
|
||||
Assert.That(Time.timeScale, Is.EqualTo(1f));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator TitleProductionStart_IsSecondKeyboardItemAndLabelsFit()
|
||||
{
|
||||
yield return LoadScene();
|
||||
RunManager manager = Object.FindAnyObjectByType<RunManager>();
|
||||
Transform menu = Object.FindAnyObjectByType<RunMenuController>()
|
||||
.transform.Find("Run Menu");
|
||||
Text title = menu.Find("Title").GetComponent<Text>();
|
||||
Assert.That(title.text, Is.EqualTo("Tiny Tackle Heroes"));
|
||||
float titlePreferredWidth = title.cachedTextGeneratorForLayout
|
||||
.GetPreferredWidth(
|
||||
title.text,
|
||||
title.GetGenerationSettings(Vector2.zero))
|
||||
/ title.pixelsPerUnit;
|
||||
Assert.That(title.cachedTextGeneratorForLayout.lineCount, Is.EqualTo(1));
|
||||
Assert.That(titlePreferredWidth,
|
||||
Is.LessThanOrEqualTo(title.rectTransform.rect.width),
|
||||
"The game title must fit in its single-line title label.");
|
||||
string[] expectedLabels = { "게임시작", "설정", "업적", "나가기" };
|
||||
for (int i = 0; i < expectedLabels.Length; i++)
|
||||
{
|
||||
Text label = menu.Find($"Button {i + 1}/Label").GetComponent<Text>();
|
||||
Assert.That(label.text, Is.EqualTo(expectedLabels[i]));
|
||||
float preferredWidth = label.cachedTextGeneratorForLayout
|
||||
.GetPreferredWidth(
|
||||
label.text,
|
||||
label.GetGenerationSettings(Vector2.zero))
|
||||
/ label.pixelsPerUnit;
|
||||
Assert.That(label.cachedTextGeneratorForLayout.lineCount, Is.EqualTo(1));
|
||||
float labelWidth = label.rectTransform.rect.width;
|
||||
Assert.That(
|
||||
preferredWidth,
|
||||
Is.LessThanOrEqualTo(labelWidth),
|
||||
$"{expectedLabels[i]} must fit in its single-line menu label.");
|
||||
}
|
||||
|
||||
Keyboard keyboard = BeginVirtualKeyboardInput();
|
||||
try
|
||||
{
|
||||
yield return PressAndRelease(keyboard, Key.Enter);
|
||||
Assert.That(manager.IsTitleScreen, Is.True,
|
||||
"Opening the mode submenu must not begin a run.");
|
||||
|
||||
Transform submenu = menu;
|
||||
bool showDevelopment = Application.isEditor || Debug.isDebugBuild;
|
||||
var submenuLabels = new System.Collections.Generic.List<string>();
|
||||
if (showDevelopment)
|
||||
{
|
||||
submenuLabels.Add("개발용 시작");
|
||||
}
|
||||
submenuLabels.Add("기본 모드");
|
||||
submenuLabels.Add("챌린지 모드(준비 중)");
|
||||
submenuLabels.Add("하드코어 모드(준비 중)");
|
||||
submenuLabels.Add("뒤로");
|
||||
for (int i = 0; i < submenuLabels.Count; i++)
|
||||
{
|
||||
Button button = submenu.Find($"Button {i + 1}").GetComponent<Button>();
|
||||
Text label = button.GetComponentInChildren<Text>();
|
||||
Assert.That(button.gameObject.activeSelf, Is.True);
|
||||
Assert.That(label.text, Is.EqualTo(submenuLabels[i]));
|
||||
float preferredWidth = label.cachedTextGeneratorForLayout
|
||||
.GetPreferredWidth(
|
||||
label.text,
|
||||
label.GetGenerationSettings(Vector2.zero))
|
||||
/ label.pixelsPerUnit;
|
||||
Assert.That(label.cachedTextGeneratorForLayout.lineCount, Is.EqualTo(1));
|
||||
Assert.That(preferredWidth, Is.LessThanOrEqualTo(label.rectTransform.rect.width),
|
||||
$"{label.text} must fit in its single-line menu label.");
|
||||
Assert.That(button.interactable,
|
||||
Is.EqualTo(i < submenuLabels.Count - 3 || i == submenuLabels.Count - 1),
|
||||
label.text);
|
||||
}
|
||||
|
||||
int placeholderIndex = showDevelopment ? 2 : 1;
|
||||
for (int i = placeholderIndex; i < placeholderIndex + 2; i++)
|
||||
{
|
||||
submenu.Find($"Button {i + 1}").GetComponent<Button>().onClick.Invoke();
|
||||
Assert.That(manager.IsTitleScreen, Is.True,
|
||||
"A preparation placeholder cannot launch a run.");
|
||||
}
|
||||
|
||||
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.");
|
||||
yield return PressAndRelease(keyboard, Key.UpArrow);
|
||||
Assert.That(
|
||||
EventSystem.current.currentSelectedGameObject
|
||||
.GetComponentInChildren<Text>().text,
|
||||
Is.EqualTo("기본 모드"));
|
||||
yield return PressAndRelease(keyboard, Key.Enter);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndVirtualKeyboardInput();
|
||||
}
|
||||
|
||||
Assert.That(manager.IsTitleScreen, Is.False);
|
||||
Assert.That(manager.IsProductionRun, Is.True);
|
||||
Assert.That(manager.UseDebugEventTimes, Is.False);
|
||||
Assert.That(manager.IsGuardUnlocked, Is.False);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator UiInitialization_ReusesEventSystemAndPreservesPointerModule()
|
||||
{
|
||||
yield return LoadScene();
|
||||
EventSystem original = EventSystem.current;
|
||||
Assert.That(original, Is.Not.Null);
|
||||
Object.Destroy(original.gameObject);
|
||||
yield return null;
|
||||
|
||||
EnsurePresentationEventSystem(false);
|
||||
EventSystem eventSystem = EventSystem.current;
|
||||
Assert.That(eventSystem, Is.Not.Null);
|
||||
Assert.That(CountActiveSceneEventSystems(), Is.EqualTo(1));
|
||||
InputSystemUIInputModule module =
|
||||
eventSystem.GetComponent<InputSystemUIInputModule>();
|
||||
Assert.That(module, Is.Not.Null);
|
||||
Assert.That(module.point, Is.Not.Null);
|
||||
Assert.That(module.leftClick, Is.Not.Null);
|
||||
Assert.That(module.scrollWheel, Is.Not.Null);
|
||||
Assert.That(module.move, Is.Not.Null);
|
||||
Assert.That(module.submit, Is.Not.Null);
|
||||
Assert.That(module.cancel, Is.Not.Null);
|
||||
var pointAction = module.point;
|
||||
var clickAction = module.leftClick;
|
||||
var scrollAction = module.scrollWheel;
|
||||
var moveAction = module.move;
|
||||
var submitAction = module.submit;
|
||||
var cancelAction = module.cancel;
|
||||
|
||||
// Exercise HUD -> menu -> HUD initialization, including repeats.
|
||||
EnsurePresentationEventSystem(false);
|
||||
Assert.That(module.move, Is.SameAs(moveAction));
|
||||
Assert.That(module.submit, Is.SameAs(submitAction));
|
||||
Assert.That(module.cancel, Is.SameAs(cancelAction));
|
||||
EnsurePresentationEventSystem(true);
|
||||
EnsurePresentationEventSystem(false);
|
||||
Assert.That(EventSystem.current, Is.SameAs(eventSystem));
|
||||
Assert.That(CountActiveSceneEventSystems(), Is.EqualTo(1));
|
||||
Assert.That(eventSystem.GetComponents<BaseInputModule>(),
|
||||
Has.Length.EqualTo(1));
|
||||
Assert.That(eventSystem.GetComponent<InputSystemUIInputModule>(),
|
||||
Is.SameAs(module));
|
||||
Assert.That(module.move, Is.Null);
|
||||
Assert.That(module.submit, Is.Null);
|
||||
Assert.That(module.cancel, Is.Null);
|
||||
Assert.That(module.point, Is.SameAs(pointAction));
|
||||
Assert.That(module.leftClick, Is.SameAs(clickAction));
|
||||
Assert.That(module.scrollWheel, Is.SameAs(scrollAction));
|
||||
|
||||
Object.Destroy(module);
|
||||
yield return null;
|
||||
StandaloneInputModule existingModule =
|
||||
eventSystem.gameObject.AddComponent<StandaloneInputModule>();
|
||||
existingModule.enabled = false;
|
||||
EnsurePresentationEventSystem(false);
|
||||
EnsurePresentationEventSystem(true);
|
||||
Assert.That(eventSystem.GetComponent<BaseInputModule>(),
|
||||
Is.SameAs(existingModule));
|
||||
Assert.That(eventSystem.GetComponent<InputSystemUIInputModule>(),
|
||||
Is.Null);
|
||||
Assert.That(CountActiveSceneEventSystems(), Is.EqualTo(1));
|
||||
|
||||
InputSystemUIInputModule secondaryModule =
|
||||
eventSystem.gameObject.AddComponent<InputSystemUIInputModule>();
|
||||
var secondaryPoint = secondaryModule.point;
|
||||
var secondaryClick = secondaryModule.leftClick;
|
||||
var secondaryScroll = secondaryModule.scrollWheel;
|
||||
EnsurePresentationEventSystem(false);
|
||||
EnsurePresentationEventSystem(true);
|
||||
EnsurePresentationEventSystem(false);
|
||||
Assert.That(eventSystem.GetComponent<BaseInputModule>(),
|
||||
Is.SameAs(existingModule));
|
||||
Assert.That(eventSystem.GetComponent<InputSystemUIInputModule>(),
|
||||
Is.SameAs(secondaryModule));
|
||||
Assert.That(eventSystem.GetComponents<BaseInputModule>(),
|
||||
Has.Length.EqualTo(2));
|
||||
Assert.That(secondaryModule.move, Is.Null);
|
||||
Assert.That(secondaryModule.submit, Is.Null);
|
||||
Assert.That(secondaryModule.cancel, Is.Null);
|
||||
Assert.That(secondaryModule.point, Is.SameAs(secondaryPoint));
|
||||
Assert.That(secondaryModule.leftClick, Is.SameAs(secondaryClick));
|
||||
Assert.That(secondaryModule.scrollWheel, Is.SameAs(secondaryScroll));
|
||||
|
||||
Object.Destroy(secondaryModule);
|
||||
Object.Destroy(existingModule);
|
||||
yield return null;
|
||||
EnsurePresentationEventSystem(false);
|
||||
InputSystemUIInputModule restoredModule =
|
||||
eventSystem.GetComponent<InputSystemUIInputModule>();
|
||||
Assert.That(restoredModule, Is.Not.Null);
|
||||
Assert.That(eventSystem.GetComponents<BaseInputModule>(),
|
||||
Has.Length.EqualTo(1));
|
||||
Assert.That(CountActiveSceneEventSystems(), Is.EqualTo(1));
|
||||
EnsurePresentationEventSystem(true);
|
||||
Assert.That(restoredModule.move, Is.Null);
|
||||
Assert.That(restoredModule.submit, Is.Null);
|
||||
Assert.That(restoredModule.cancel, Is.Null);
|
||||
Assert.That(restoredModule.point, Is.Not.Null);
|
||||
Assert.That(restoredModule.leftClick, Is.Not.Null);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ArtifactSelection_SharedCardsKeepIconAndTextLayout()
|
||||
{
|
||||
ArtifactRewardController.GrantCatalogForTests = false;
|
||||
RunManager.ForceProductionModeForTests = true;
|
||||
yield return LoadScene();
|
||||
|
||||
RunManager manager = Object.FindAnyObjectByType<RunManager>();
|
||||
ArtifactRewardController rewards =
|
||||
Object.FindAnyObjectByType<ArtifactRewardController>();
|
||||
Assert.That(manager.BeginRun(), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugIsSelectionVisible, Is.True);
|
||||
|
||||
GameObject panel = GameObject.Find("Artifact Reward Selection");
|
||||
Text optionText = panel.transform.Find("Option 1").GetComponent<Text>();
|
||||
Image card = panel.transform.Find("Option 1 Card").GetComponent<Image>();
|
||||
Image icon = panel.transform.Find("Option 1 Icon").GetComponent<Image>();
|
||||
RectTransform textRect = optionText.rectTransform;
|
||||
RectTransform cardRect = card.rectTransform;
|
||||
RectTransform iconRect = icon.rectTransform;
|
||||
Assert.That(panel.GetComponent<Image>().type, Is.EqualTo(Image.Type.Sliced));
|
||||
Assert.That(optionText.alignment, Is.EqualTo(TextAnchor.MiddleCenter));
|
||||
Assert.That(optionText.font, Is.SameAs(
|
||||
Resources.Load<Font>("Presentation/Fonts/Galmuri9")));
|
||||
Assert.That(optionText.raycastTarget, Is.False);
|
||||
Assert.That(textRect.sizeDelta, Is.EqualTo(new Vector2(380f, 64f)));
|
||||
Assert.That(cardRect.anchorMin, Is.EqualTo(textRect.anchorMin));
|
||||
Assert.That(cardRect.anchorMax, Is.EqualTo(textRect.anchorMax));
|
||||
Assert.That(cardRect.pivot, Is.EqualTo(textRect.pivot));
|
||||
Assert.That(cardRect.anchoredPosition,
|
||||
Is.EqualTo(textRect.anchoredPosition + Vector2.up * 25f));
|
||||
Assert.That(cardRect.sizeDelta, Is.EqualTo(new Vector2(380f, 128f)));
|
||||
Assert.That(card.transform.GetSiblingIndex(),
|
||||
Is.EqualTo(optionText.transform.GetSiblingIndex() - 2));
|
||||
Assert.That(icon.transform.GetSiblingIndex(),
|
||||
Is.EqualTo(optionText.transform.GetSiblingIndex() - 1));
|
||||
Assert.That(card.raycastTarget, Is.False);
|
||||
Assert.That(icon.preserveAspect, Is.True);
|
||||
Assert.That(icon.raycastTarget, Is.False);
|
||||
Assert.That(iconRect.anchorMin, Is.EqualTo(textRect.anchorMin));
|
||||
Assert.That(iconRect.anchorMax, Is.EqualTo(textRect.anchorMax));
|
||||
Assert.That(iconRect.pivot, Is.EqualTo(textRect.pivot));
|
||||
Assert.That(iconRect.anchoredPosition,
|
||||
Is.EqualTo(textRect.anchoredPosition + Vector2.up * 45f));
|
||||
Assert.That(iconRect.sizeDelta, Is.EqualTo(new Vector2(48f, 48f)));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator MenuKeyboard_SettingsSliderAndBack_AreSingleStep()
|
||||
{
|
||||
yield return LoadScene();
|
||||
RunManager manager = Object.FindAnyObjectByType<RunManager>();
|
||||
BumpCombatAudioService service =
|
||||
Object.FindAnyObjectByType<BumpCombatAudioService>();
|
||||
Keyboard keyboard = BeginVirtualKeyboardInput();
|
||||
try
|
||||
{
|
||||
yield return PressAndRelease(keyboard, Key.DownArrow);
|
||||
GameObject selected = EventSystem.current.currentSelectedGameObject;
|
||||
Assert.That(selected, Is.Not.Null);
|
||||
Assert.That(selected.GetComponentInChildren<Text>().text, Is.EqualTo("설정"));
|
||||
|
||||
yield return PressAndRelease(keyboard, Key.Enter);
|
||||
Slider slider = GameObject.Find("Master Slider").GetComponent<Slider>();
|
||||
Assert.That(slider.gameObject.activeSelf, Is.True);
|
||||
slider.value = 0.5f;
|
||||
float before = slider.value;
|
||||
|
||||
yield return PressAndRelease(keyboard, Key.RightArrow);
|
||||
Assert.That(slider.value, Is.EqualTo(before + 0.05f).Within(0.001f));
|
||||
|
||||
yield return PressAndRelease(keyboard, Key.Escape);
|
||||
Assert.That(slider.gameObject.activeSelf, Is.False);
|
||||
Assert.That(manager.IsTitleScreen, Is.True);
|
||||
Assert.That(service.MasterVolume, Is.EqualTo(slider.value).Within(0.001f));
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndVirtualKeyboardInput();
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SettingsFocus_IsVisibleAndPointerBackSyncsKeyboardSelection()
|
||||
{
|
||||
yield return LoadScene();
|
||||
RunManager manager = Object.FindAnyObjectByType<RunManager>();
|
||||
RunMenuController menuController = Object.FindAnyObjectByType<RunMenuController>();
|
||||
Assert.That(menuController, Is.Not.Null);
|
||||
Transform menu = menuController.transform.Find("Run Menu");
|
||||
Text inputHelp = menu.Find("Menu Input Help").GetComponent<Text>();
|
||||
Assert.That(inputHelp.text, Is.EqualTo("항목 이동: 위/아래 선택: Enter/Space"));
|
||||
Assert.That(inputHelp.rectTransform.anchorMin, Is.EqualTo(Vector2.zero));
|
||||
Assert.That(inputHelp.alignment, Is.EqualTo(TextAnchor.MiddleLeft));
|
||||
Assert.That(menu.Find("Button 1/Focus Marker").gameObject.activeSelf, Is.True);
|
||||
|
||||
Keyboard keyboard = BeginVirtualKeyboardInput();
|
||||
try
|
||||
{
|
||||
menu.Find("Button 2").GetComponent<Button>().onClick.Invoke();
|
||||
yield return null;
|
||||
Assert.That(inputHelp.text, Is.EqualTo(
|
||||
"항목 이동: 위/아래 음량 조절: 왼쪽/오른쪽\n뒤로 선택: Enter/Space 즉시 뒤로: Esc"));
|
||||
|
||||
GameObject masterFocus = menu.Find("Settings Focus 0").gameObject;
|
||||
GameObject musicFocus = menu.Find("Settings Focus 1").gameObject;
|
||||
GameObject backFocus = menu.Find("Settings Focus 3").gameObject;
|
||||
Slider masterSlider = menu.Find("Master Slider").GetComponent<Slider>();
|
||||
Button backButton = menu.Find("Button 4").GetComponent<Button>();
|
||||
Assert.That(masterFocus.activeSelf, Is.True);
|
||||
Assert.That(EventSystem.current.currentSelectedGameObject, Is.EqualTo(masterSlider.gameObject));
|
||||
|
||||
yield return PressAndRelease(keyboard, Key.DownArrow);
|
||||
Assert.That(masterFocus.activeSelf, Is.False);
|
||||
Assert.That(musicFocus.activeSelf, Is.True);
|
||||
|
||||
ExecuteEvents.Execute(
|
||||
backButton.gameObject,
|
||||
new PointerEventData(EventSystem.current),
|
||||
ExecuteEvents.pointerEnterHandler);
|
||||
Assert.That(EventSystem.current.currentSelectedGameObject, Is.EqualTo(backButton.gameObject));
|
||||
Assert.That(backFocus.activeSelf, Is.True);
|
||||
|
||||
yield return PressAndRelease(keyboard, Key.DownArrow);
|
||||
Assert.That(masterFocus.activeSelf, Is.True);
|
||||
Assert.That(backFocus.activeSelf, Is.False);
|
||||
Assert.That(EventSystem.current.currentSelectedGameObject, Is.EqualTo(masterSlider.gameObject));
|
||||
|
||||
yield return PressAndRelease(keyboard, Key.UpArrow);
|
||||
yield return PressAndRelease(keyboard, Key.Enter);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndVirtualKeyboardInput();
|
||||
}
|
||||
|
||||
Assert.That(manager.IsTitleScreen, Is.True);
|
||||
Assert.That(inputHelp.text, Is.EqualTo("항목 이동: 위/아래 선택: Enter/Space"));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator PauseAndSelection_KeepEachOtherInterlocked()
|
||||
{
|
||||
yield return LoadScene();
|
||||
RunManager manager = Object.FindAnyObjectByType<RunManager>();
|
||||
Assert.That(manager.BeginRun(), Is.True);
|
||||
yield return null;
|
||||
ArtifactRewardController rewards =
|
||||
Object.FindAnyObjectByType<ArtifactRewardController>();
|
||||
Assert.That(rewards.DebugIsSelectionVisible, Is.True);
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
|
||||
CloseArtifactTutorialModal(manager);
|
||||
|
||||
GameObject ownerObject = new("Selection Owner Test");
|
||||
SelectionOwner owner = ownerObject.AddComponent<SelectionOwner>();
|
||||
try
|
||||
{
|
||||
Assert.That(manager.TryOpenSelection(owner), Is.True);
|
||||
Assert.That(manager.TryOpenPause(), Is.False);
|
||||
Assert.That(RunManager.GameplayInputEnabled, Is.False);
|
||||
Assert.That(manager.CloseSelection(owner), Is.True);
|
||||
Assert.That(manager.TryOpenPause(), Is.True);
|
||||
Text inputHelp = GameObject.Find("Menu Input Help").GetComponent<Text>();
|
||||
Assert.That(inputHelp.text, Is.EqualTo(
|
||||
"항목 이동: 위/아래 선택: Enter/Space 계속: Esc"));
|
||||
Transform menu = Object.FindAnyObjectByType<RunMenuController>().transform.Find("Run Menu");
|
||||
Assert.That(menu.Find("Button 1/Focus Marker").gameObject.activeSelf, Is.True);
|
||||
Assert.That(manager.TryOpenSelection(owner), Is.False);
|
||||
Assert.That(manager.ClosePause(), Is.True);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.Destroy(ownerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator StartupSelection_RecoversWhenPausedBeforeItsFirstFrame()
|
||||
{
|
||||
yield return LoadScene();
|
||||
RunManager manager = Object.FindAnyObjectByType<RunManager>();
|
||||
ArtifactRewardController rewards =
|
||||
Object.FindAnyObjectByType<ArtifactRewardController>();
|
||||
Assert.That(manager, Is.Not.Null);
|
||||
Assert.That(rewards, Is.Not.Null);
|
||||
|
||||
Assert.That(manager.BeginRun(), Is.True);
|
||||
Assert.That(manager.TryOpenPause(), Is.True);
|
||||
Assert.That(manager.IsPaused, Is.True);
|
||||
|
||||
// The startup request is queued by BeginRun. A paused frame must
|
||||
// leave it pending so resuming can open the first color step.
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugIsSelectionVisible, Is.False);
|
||||
Assert.That(manager.IsSelectionOpen, Is.False);
|
||||
Assert.That(RunManager.GameplayInputEnabled, Is.False);
|
||||
|
||||
Assert.That(manager.ClosePause(), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugIsSelectionVisible, Is.True);
|
||||
Assert.That(rewards.DebugShownCount, Is.EqualTo(2));
|
||||
Assert.That(
|
||||
rewards.DebugGetShownArtifact(0).ArtifactColor,
|
||||
Is.EqualTo(ArtifactColor.Green));
|
||||
Assert.That(
|
||||
rewards.DebugGetShownArtifact(1).ArtifactColor,
|
||||
Is.EqualTo(ArtifactColor.Green));
|
||||
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugIsSelectionVisible, Is.True);
|
||||
Assert.That(rewards.DebugShownCount, Is.EqualTo(2));
|
||||
Assert.That(
|
||||
rewards.DebugGetShownArtifact(0).ArtifactColor,
|
||||
Is.EqualTo(ArtifactColor.Red));
|
||||
Assert.That(
|
||||
rewards.DebugGetShownArtifact(1).ArtifactColor,
|
||||
Is.EqualTo(ArtifactColor.Red));
|
||||
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugIsSelectionVisible, Is.True);
|
||||
Assert.That(rewards.DebugShownCount, Is.EqualTo(2));
|
||||
Assert.That(
|
||||
rewards.DebugGetShownArtifact(0).ArtifactColor,
|
||||
Is.EqualTo(ArtifactColor.Blue));
|
||||
Assert.That(
|
||||
rewards.DebugGetShownArtifact(1).ArtifactColor,
|
||||
Is.EqualTo(ArtifactColor.Blue));
|
||||
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
|
||||
CloseArtifactTutorialModal(manager);
|
||||
|
||||
Assert.That(rewards.DebugIsSelectionVisible, Is.False);
|
||||
Assert.That(manager.IsSelectionOpen, Is.False);
|
||||
Assert.That(RunManager.GameplayInputEnabled, Is.True);
|
||||
Assert.That(
|
||||
Object.FindAnyObjectByType<ActiveArtifactController>().OwnedArtifactCount,
|
||||
Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator AudioService_LoadsContractClipsAndCapsVoices()
|
||||
{
|
||||
yield return LoadScene();
|
||||
BumpCombatAudioService service =
|
||||
Object.FindAnyObjectByType<BumpCombatAudioService>();
|
||||
Assert.That(service, Is.Not.Null);
|
||||
Assert.That(service.VoiceCount, Is.EqualTo(8));
|
||||
string[] ids =
|
||||
{
|
||||
"ui_move", "ui_confirm", "hit_light", "hit_heavy",
|
||||
"player_hurt", "enemy_defeat", "xp_pickup", "level_up",
|
||||
"gauge_ready", "run_end", "charge_start", "artifact_dash",
|
||||
"artifact_pulse", "artifact_ray", "artifact_cyclone",
|
||||
"artifact_thunder", "artifact_arc", "bgm_courtyard", "bgm_boss",
|
||||
};
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
Assert.That(service.DebugHasClip(ids[i]), Is.True, ids[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator VolumeSettings_PersistNormalizedValues()
|
||||
{
|
||||
yield return LoadScene();
|
||||
BumpCombatAudioService service =
|
||||
Object.FindAnyObjectByType<BumpCombatAudioService>();
|
||||
service.SetMasterVolume(0.31f);
|
||||
service.SetMusicVolume(0.62f);
|
||||
service.SetSfxVolume(0.47f);
|
||||
Assert.That(service.MasterVolume, Is.EqualTo(0.31f).Within(0.0001f));
|
||||
Assert.That(service.MusicVolume, Is.EqualTo(0.62f).Within(0.0001f));
|
||||
Assert.That(service.SfxVolume, Is.EqualTo(0.47f).Within(0.0001f));
|
||||
Assert.That(PlayerPrefs.GetFloat("BumpCombat.MasterVolume"), Is.EqualTo(0.31f).Within(0.0001f));
|
||||
Assert.That(PlayerPrefs.GetFloat("BumpCombat.MusicVolume"), Is.EqualTo(0.62f).Within(0.0001f));
|
||||
Assert.That(PlayerPrefs.GetFloat("BumpCombat.SfxVolume"), Is.EqualTo(0.47f).Within(0.0001f));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SuccessfulArtifactCast_UsesArtifactClipOnce()
|
||||
{
|
||||
ArtifactRewardController.GrantCatalogForTests = true;
|
||||
yield return LoadScene();
|
||||
BumpCombatAudioService service =
|
||||
Object.FindAnyObjectByType<BumpCombatAudioService>();
|
||||
ActiveArtifactController artifacts =
|
||||
Object.FindAnyObjectByType<ActiveArtifactController>();
|
||||
artifacts.AddMovementCharge(20f);
|
||||
Assert.That(artifacts.TryUseCurrent(false), Is.True);
|
||||
Assert.That(service.LastPlayedClipId, Is.EqualTo("artifact_dash"));
|
||||
Assert.That(artifacts.TryUseCurrent(false), Is.False);
|
||||
Assert.That(service.LastPlayedClipId, Is.EqualTo("artifact_dash"));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator BossSpawn_SwitchesToBossMusic()
|
||||
{
|
||||
ArtifactRewardController.GrantCatalogForTests = true;
|
||||
RunManager.ForceProductionModeForTests = false;
|
||||
yield return LoadScene();
|
||||
BumpCombatAudioService service =
|
||||
Object.FindAnyObjectByType<BumpCombatAudioService>();
|
||||
GameObject midBossButtonObject = GameObject.Find("Debug Spawn MidBoss");
|
||||
GameObject finalBossButtonObject = GameObject.Find("Debug Spawn FinalBoss");
|
||||
Assert.That(midBossButtonObject, Is.Not.Null);
|
||||
Assert.That(finalBossButtonObject, Is.Not.Null);
|
||||
Button midBossButton = midBossButtonObject.GetComponent<Button>();
|
||||
Button finalBossButton = finalBossButtonObject.GetComponent<Button>();
|
||||
Assert.That(CountPlayingMusicSources(), Is.EqualTo(1));
|
||||
Assert.That(midBossButton, Is.Not.Null);
|
||||
Assert.That(finalBossButton, Is.Not.Null);
|
||||
midBossButton.onClick.Invoke();
|
||||
Assert.That(service.ActiveMusicClipId, Is.EqualTo("bgm_boss"));
|
||||
Assert.That(
|
||||
CountPlayingMusicSources(),
|
||||
Is.EqualTo(1),
|
||||
"Starting boss music must stop ambient music before the fade-in.");
|
||||
yield return null;
|
||||
Assert.That(CountPlayingMusicSources(), Is.EqualTo(1));
|
||||
|
||||
finalBossButton.onClick.Invoke();
|
||||
Assert.That(service.ActiveMusicClipId, Is.EqualTo("bgm_boss"));
|
||||
Assert.That(CountPlayingMusicSources(), Is.EqualTo(1));
|
||||
yield return null;
|
||||
Assert.That(CountPlayingMusicSources(), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator AudioService_DestroyAndReadd_ClearsLegacyMusicChildren()
|
||||
{
|
||||
ArtifactRewardController.GrantCatalogForTests = true;
|
||||
RunManager.ForceProductionModeForTests = false;
|
||||
yield return LoadScene();
|
||||
BumpCombatAudioService service =
|
||||
Object.FindAnyObjectByType<BumpCombatAudioService>();
|
||||
Canvas canvas = service.GetComponentInParent<Canvas>();
|
||||
GameObject legacyObject = new("Music A");
|
||||
legacyObject.transform.SetParent(service.transform, false);
|
||||
AudioSource legacySource = legacyObject.AddComponent<AudioSource>();
|
||||
AudioClip legacyClip = AudioClip.Create("Legacy Music", 4410, 1, 44100, false);
|
||||
legacySource.clip = legacyClip;
|
||||
legacySource.loop = true;
|
||||
legacySource.Play();
|
||||
Assert.That(CountPlayingMusicSources(), Is.EqualTo(2));
|
||||
|
||||
Object.Destroy(service);
|
||||
yield return null;
|
||||
Assert.That(CountPlayingMusicSources(), Is.Zero);
|
||||
|
||||
canvas.gameObject.AddComponent<BumpCombatAudioService>();
|
||||
yield return null;
|
||||
Assert.That(CountPlayingMusicSources(), Is.EqualTo(1));
|
||||
Object.Destroy(legacyClip);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator MusicDuckAndGameOverFade_StopsMusicSource()
|
||||
{
|
||||
ArtifactRewardController.GrantCatalogForTests = false;
|
||||
RunManager.ForceProductionModeForTests = true;
|
||||
yield return LoadScene();
|
||||
RunManager manager = Object.FindAnyObjectByType<RunManager>();
|
||||
BumpCombatAudioService service =
|
||||
Object.FindAnyObjectByType<BumpCombatAudioService>();
|
||||
float titleGain = service.CurrentMusicOutputGain;
|
||||
Assert.That(titleGain, Is.GreaterThan(0f));
|
||||
|
||||
Assert.That(manager.BeginRun(), Is.True);
|
||||
yield return null;
|
||||
ArtifactRewardController rewards =
|
||||
Object.FindAnyObjectByType<ArtifactRewardController>();
|
||||
Assert.That(rewards.DebugIsSelectionVisible, Is.True);
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
Assert.That(rewards.DebugChooseOption(0), Is.True);
|
||||
yield return null;
|
||||
|
||||
CloseArtifactTutorialModal(manager);
|
||||
|
||||
Assert.That(service.CurrentMusicOutputGain, Is.GreaterThan(titleGain));
|
||||
|
||||
service.SetMasterVolume(0f);
|
||||
RunManager.ForceProductionModeForTests = false;
|
||||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||||
Assert.That(director.TryDebugSpawnEventEnemy(RunTimedEvent.MidBoss), Is.True);
|
||||
yield return null;
|
||||
Assert.That(service.CurrentMusicOutputGain, Is.Zero);
|
||||
|
||||
manager.EndRun();
|
||||
yield return new WaitForSecondsRealtime(0.6f);
|
||||
Assert.That(service.AreMusicSourcesStopped, Is.True);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SpawnDirector_DoesNotWaveSpawnWhileTitleIsBlocked()
|
||||
{
|
||||
ArtifactRewardController.GrantCatalogForTests = false;
|
||||
RunManager.ForceProductionModeForTests = true;
|
||||
yield return LoadScene();
|
||||
RunManager manager = Object.FindAnyObjectByType<RunManager>();
|
||||
SpawnDirector director = Object.FindAnyObjectByType<SpawnDirector>();
|
||||
Assert.That(manager.IsTitleScreen, Is.True);
|
||||
typeof(SpawnDirector)
|
||||
.GetField("nextWaveTime", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.SetValue(director, -1f);
|
||||
yield return null;
|
||||
Assert.That(EnemyController.AliveCount, Is.Zero);
|
||||
Assert.That(RunManager.GameplayInputEnabled, Is.False);
|
||||
}
|
||||
|
||||
private IEnumerator LoadScene()
|
||||
{
|
||||
AsyncOperation load = SceneManager.LoadSceneAsync(SceneName);
|
||||
while (!load.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield return null;
|
||||
BumpCombatAudioService service =
|
||||
Object.FindAnyObjectByType<BumpCombatAudioService>();
|
||||
if (service != null && !capturedVolumes)
|
||||
{
|
||||
originalMaster = service.MasterVolume;
|
||||
originalMusic = service.MusicVolume;
|
||||
originalSfx = service.SfxVolume;
|
||||
capturedVolumes = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerator PressAndRelease(Keyboard keyboard, Key key)
|
||||
{
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState(key));
|
||||
yield return null;
|
||||
Assert.That(keyboard[key].isPressed, Is.True);
|
||||
InputSystem.QueueStateEvent(keyboard, new KeyboardState());
|
||||
yield return null;
|
||||
Assert.That(keyboard[key].isPressed, Is.False);
|
||||
}
|
||||
|
||||
private static void EnsurePresentationEventSystem(
|
||||
bool clearMenuNavigationActions)
|
||||
{
|
||||
System.Type styleType = typeof(RunHUD).Assembly.GetType(
|
||||
"BumpCombat.UI.PresentationUiStyle",
|
||||
true);
|
||||
MethodInfo ensureMethod = styleType.GetMethod(
|
||||
"EnsureEventSystem",
|
||||
BindingFlags.Public | BindingFlags.Static);
|
||||
Assert.That(ensureMethod, Is.Not.Null);
|
||||
ensureMethod.Invoke(null, new object[] { clearMenuNavigationActions });
|
||||
}
|
||||
|
||||
private static void CloseArtifactTutorialModal(RunManager manager)
|
||||
{
|
||||
RunTutorialController tutorials =
|
||||
Object.FindAnyObjectByType<RunTutorialController>();
|
||||
Assert.That(tutorials, Is.Not.Null);
|
||||
Assert.That(tutorials.DebugIsVisible, Is.True,
|
||||
"The first acquired artifact opens its tutorial modal before the next interaction.");
|
||||
Assert.That(tutorials.DebugContinue(), Is.True);
|
||||
Assert.That(tutorials.DebugIsVisible, Is.False);
|
||||
Assert.That(manager.IsSelectionOpen, Is.False);
|
||||
}
|
||||
|
||||
private static int CountActiveSceneEventSystems()
|
||||
{
|
||||
int count = 0;
|
||||
GameObject[] roots = SceneManager.GetActiveScene().GetRootGameObjects();
|
||||
foreach (GameObject root in roots)
|
||||
{
|
||||
count += root.GetComponentsInChildren<EventSystem>(true).Length;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static int CountPlayingMusicSources()
|
||||
{
|
||||
AudioSource[] sources = Object.FindObjectsByType<AudioSource>(
|
||||
FindObjectsInactive.Include,
|
||||
FindObjectsSortMode.None);
|
||||
int playingCount = 0;
|
||||
for (int i = 0; i < sources.Length; i++)
|
||||
{
|
||||
if (sources[i].name.StartsWith("Music", System.StringComparison.Ordinal)
|
||||
&& sources[i].isPlaying)
|
||||
{
|
||||
playingCount++;
|
||||
}
|
||||
}
|
||||
return playingCount;
|
||||
}
|
||||
|
||||
private Keyboard BeginVirtualKeyboardInput()
|
||||
{
|
||||
Assert.That(virtualKeyboardInputActive, Is.False);
|
||||
previousBackgroundBehavior = InputSystem.settings.backgroundBehavior;
|
||||
previousEditorInputBehavior = InputSystem.settings.editorInputBehaviorInPlayMode;
|
||||
previousRunInBackground = Application.runInBackground;
|
||||
InputSystem.settings.backgroundBehavior =
|
||||
InputSettings.BackgroundBehavior.IgnoreFocus;
|
||||
InputSystem.settings.editorInputBehaviorInPlayMode =
|
||||
InputSettings.EditorInputBehaviorInPlayMode.AllDeviceInputAlwaysGoesToGameView;
|
||||
Application.runInBackground = true;
|
||||
virtualKeyboard = InputSystem.AddDevice<Keyboard>();
|
||||
virtualKeyboard.MakeCurrent();
|
||||
Assert.That(Keyboard.current, Is.SameAs(virtualKeyboard));
|
||||
virtualKeyboardInputActive = true;
|
||||
return virtualKeyboard;
|
||||
}
|
||||
|
||||
private void EndVirtualKeyboardInput()
|
||||
{
|
||||
if (!virtualKeyboardInputActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (virtualKeyboard != null && virtualKeyboard.added)
|
||||
{
|
||||
InputSystem.RemoveDevice(virtualKeyboard);
|
||||
}
|
||||
|
||||
InputSystem.settings.backgroundBehavior = previousBackgroundBehavior;
|
||||
InputSystem.settings.editorInputBehaviorInPlayMode = previousEditorInputBehavior;
|
||||
Application.runInBackground = previousRunInBackground;
|
||||
virtualKeyboard = null;
|
||||
virtualKeyboardInputActive = false;
|
||||
}
|
||||
|
||||
private sealed class SelectionOwner : MonoBehaviour
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user