convert game playing..

This commit is contained in:
2026-09-23 12:53:52 +09:00
parent 37c7942b71
commit 5d11288a6f
136 changed files with 15856 additions and 299 deletions
@@ -0,0 +1,237 @@
using System;
using System.Collections;
using System.IO;
using BumpCombat.Core;
using BumpCombat.Player;
using BumpCombat.Progression;
using BumpCombat.Stage;
using BumpCombat.UI;
using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
[InitializeOnLoad]
internal static class AgyPlayValidationHarness
{
// A validation run starts only when run.marker explicitly contains "run".
private const string Out = "D:/project/game/output/agy-play-validation-20260920/";
private const string RunMarker = Out + "run.marker";
static AgyPlayValidationHarness()
{
EditorApplication.delayCall += () =>
{
if (IsRunRequested() && !EditorApplication.isPlayingOrWillChangePlaymode)
EditorApplication.EnterPlaymode();
};
EditorApplication.playModeStateChanged += s =>
{
if (s == PlayModeStateChange.EnteredPlayMode && IsRunRequested())
new GameObject("AgyPlayValidationRunner") { hideFlags = HideFlags.HideAndDontSave }
.AddComponent<AgyPlayValidationRunner>();
};
}
private static bool IsRunRequested()
{
return File.Exists(RunMarker)
&& string.Equals(File.ReadAllText(RunMarker).Trim(), "run", StringComparison.Ordinal);
}
}
internal sealed class AgyPlayValidationRunner : MonoBehaviour
{
private const string Out = "D:/project/game/output/agy-play-validation-20260920/";
private IEnumerator Start()
{
yield return new WaitForSecondsRealtime(0.5f);
RunManager run = RunManager.Instance;
if (run == null) { Fail("RunManager missing"); yield break; }
if (run.IsTitleScreen) run.BeginRun(RunMode.Development);
yield return ResolveBlockingSelections(run);
if (run.IsSelectionOpen || Time.timeScale <= 0f)
{
Fail($"Startup selection did not resolve. selectionOpen={run.IsSelectionOpen}, timeScale={Time.timeScale:R}");
yield break;
}
StageManager stage = StageManager.Instance;
PlayerController player = FindAnyObjectByType<PlayerController>();
WorldMapPanel map = FindAnyObjectByType<WorldMapPanel>();
if (stage == null || stage.CurrentZone == null || player == null || map == null)
{ Fail("Stage/player/map initialization failed"); yield break; }
Capture("01-start");
Vector2 before = player.transform.position;
yield return Hold(Key.RightArrow, 0.5f);
Vector2 after = player.transform.position;
float movementDistance = Vector2.Distance(before, after);
File.WriteAllText(Out + "movement.txt", $"before={before:R}\nafter={after:R}\ndistance={movementDistance:R}\n");
Capture("02-moved");
if (movementDistance <= 0.01f)
{
Fail($"Player did not move. distance={movementDistance:R}");
yield break;
}
yield return Press(Key.M);
File.WriteAllText(Out + "map.txt", $"open={map.IsOpen}\ntimeScale={Time.timeScale:R}\n");
Capture("03-world-map-open");
if (!map.IsOpen || Time.timeScale > 0f)
{
Fail($"World map did not open correctly. open={map.IsOpen}, timeScale={Time.timeScale:R}");
yield break;
}
yield return Press(Key.M);
if (map.IsOpen || Time.timeScale <= 0f)
{
Fail($"World map did not close correctly. open={map.IsOpen}, timeScale={Time.timeScale:R}");
yield break;
}
int target = -1; StageDirection exit = StageDirection.North;
for (int i = 0; i < 4; i++)
{
int candidate = stage.CurrentZone.NodeData.GetConnection((StageDirection)i);
if (candidate >= 0) { target = candidate; exit = (StageDirection)i; break; }
}
if (target < 0) { Fail("No connected zone"); yield break; }
int oldZone = stage.CurrentZone.NodeId;
stage.RequestZoneTransition(target, exit.Opposite());
yield return null;
File.WriteAllText(Out + "transition-active.txt", $"transitioning={stage.IsTransitioning}\nlocked={player.IsTransitionLocked}\n");
Capture("04-transition-active");
float deadline = Time.realtimeSinceStartup + 4f;
while (stage.IsTransitioning && Time.realtimeSinceStartup < deadline) yield return null;
ArenaBounds bounds = ArenaBounds.Resolve();
File.WriteAllText(Out + "transition.txt",
$"oldZone={oldZone}\nnewZone={stage.CurrentZone?.NodeId ?? -1}\ntarget={target}\n" +
$"transitioning={stage.IsTransitioning}\nlocked={player.IsTransitionLocked}\n" +
$"boundsCenter={bounds?.ZoneCenter.ToString("R")}\nzoneCenter={stage.CurrentZone?.ZoneCenter.ToString("R")}\n");
Capture("05-transition-complete");
if (stage.IsTransitioning
|| player.IsTransitionLocked
|| stage.CurrentZone == null
|| stage.CurrentZone.NodeId != target)
{
Fail($"Zone transition failed. oldZone={oldZone}, target={target}, newZone={stage.CurrentZone?.NodeId ?? -1}, transitioning={stage.IsTransitioning}, locked={player.IsTransitionLocked}");
yield break;
}
run.EndRunAsClear();
yield return null;
RunClearPanel clear = FindAnyObjectByType<RunClearPanel>();
File.WriteAllText(Out + "clear.txt", $"runCleared={run.IsRunCleared}\ntimeScale={Time.timeScale:R}\npanel={(clear != null)}\n");
Capture("06-run-clear");
if (!run.IsRunCleared || Time.timeScale > 0f || clear == null)
{
Fail($"Run clear validation failed. runCleared={run.IsRunCleared}, timeScale={Time.timeScale:R}, panel={(clear != null)}");
yield break;
}
File.WriteAllText(Out + "completed.marker", DateTime.Now.ToString("O"));
DeleteRunMarker();
EditorApplication.ExitPlaymode();
}
private static IEnumerator ResolveBlockingSelections(RunManager run)
{
ArtifactRewardController rewards = FindAnyObjectByType<ArtifactRewardController>();
RunTutorialController tutorial = FindAnyObjectByType<RunTutorialController>();
float deadline = Time.realtimeSinceStartup + 6f;
while (Time.realtimeSinceStartup < deadline)
{
bool handled = false;
if (rewards != null && rewards.DebugIsSelectionVisible)
{
if (rewards.DebugShownCount <= 0 || !rewards.DebugChooseOption(0))
{
Fail("Could not resolve startup artifact selection.");
yield break;
}
handled = true;
}
else if (tutorial != null && tutorial.DebugIsVisible)
{
if (!tutorial.DebugContinue())
{
Fail("Could not dismiss startup tutorial.");
yield break;
}
handled = true;
}
if (!handled && !run.IsSelectionOpen && Time.timeScale > 0f)
{
yield return null;
if (!run.IsSelectionOpen && Time.timeScale > 0f)
yield break;
}
yield return null;
}
}
private static IEnumerator Hold(Key key, float seconds)
{
if (Keyboard.current == null) yield break;
InputSystem.QueueStateEvent(Keyboard.current, new KeyboardState(key));
float end = Time.realtimeSinceStartup + seconds;
while (Time.realtimeSinceStartup < end) yield return null;
InputSystem.QueueStateEvent(Keyboard.current, new KeyboardState());
yield return null;
}
private static IEnumerator Press(Key key)
{
if (Keyboard.current == null) yield break;
InputSystem.QueueStateEvent(Keyboard.current, new KeyboardState(key));
yield return null;
InputSystem.QueueStateEvent(Keyboard.current, new KeyboardState());
yield return null;
}
private static void Capture(string name)
{
Directory.CreateDirectory(Out);
Camera camera = Camera.main;
if (camera == null) return;
RenderTexture previous = camera.targetTexture;
RenderTexture active = RenderTexture.active;
var target = new RenderTexture(1280, 720, 24);
var texture = new Texture2D(1280, 720, TextureFormat.RGB24, false);
try
{
camera.targetTexture = target; camera.Render(); RenderTexture.active = target;
texture.ReadPixels(new Rect(0, 0, 1280, 720), 0, 0); texture.Apply();
File.WriteAllBytes(Out + name + ".png", texture.EncodeToPNG());
}
finally
{
camera.targetTexture = previous; RenderTexture.active = active;
DestroyImmediate(texture); target.Release(); DestroyImmediate(target);
}
}
private static void Fail(string message)
{
Directory.CreateDirectory(Out);
File.WriteAllText(Out + "validation-error.txt", message);
DeleteRunMarker();
EditorApplication.ExitPlaymode();
}
private static void DeleteRunMarker()
{
string marker = Out + "run.marker";
if (File.Exists(marker))
{
File.Delete(marker);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5ec05c0337f620543ba3d30114ee07fe
@@ -1271,7 +1271,7 @@ namespace BumpCombat.Editor
body.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
CircleCollider2D collider = player.AddComponent<CircleCollider2D>();
collider.offset = new Vector2(0f, -0.28f);
collider.offset = new Vector2(0f, -0.06f);
collider.radius = 0.22f;
player.AddComponent<PlayerStats>();
@@ -1394,10 +1394,7 @@ namespace BumpCombat.Editor
radius,
experience,
animationLeadTime);
definition.ConfigureRole(
kind == EnemyKind.Slime || kind == EnemyKind.Skeleton
? EnemyRole.Crowd
: EnemyRole.Normal);
definition.ConfigureRole(EnemyRole.Normal);
AssetDatabase.CreateAsset(definition, path);
return definition;
}