Fixes "Can't start dialogue: no Yarn Project has been configured." Cause, and it was mine. Phase 3.7 said "retarget YarnDemoSetup.cs or retire it"; I retargeted it. But that menu does not modify a scene — it builds one from scratch and calls SaveScene over its target. Pointing it at SC101_ConferenceHall.unity armed it at the real level, and running it replaced the scene with a bare Yarn demo: ten of fourteen roots gone, including the player, the lighting and the navmesh. The leftover Dialogue System prefab instance it left behind had no yarnProject override, auto-started, and produced the error. The scene was committed, so nothing was lost; it is restored from HEAD at 14 roots with its sun reference intact. The menu now targets Assets/Scenes/Dev/YarnSandbox.unity and refuses to run if its target is not under Scenes/Dev, via ScenePaths.IsDisposable. It is renamed to "Set Up Yarn Sandbox Scene" so the menu says what it does. The other three setup menus open and modify their target rather than regenerating it, so they were never affected and are unchanged. The test suite could not have caught this, because it asserted only that a DialogueRunner existed. TheDialogueRunner_HasItsYarnProject now asserts there is exactly one runner across the loaded scenes and that its project is set and compiled — which reproduces the failure exactly when the scene is broken. Also here: four UI prefabs pick up m_EditorClassIdentifier changes from Assembly-CSharp to NightclubArcadia.Game, written by Unity after the assembly split in the previous commit. Expected and correct. EditMode 42/42, PlayMode 6/6, YarnCheck 9 files / 35 nodes. Co-Authored-By: Claude Opus 5 <[email protected]>
132 lines
5.2 KiB
C#
132 lines
5.2 KiB
C#
using System.Collections;
|
|
using System.Linq;
|
|
using NUnit.Framework;
|
|
using NightclubArcadia.Cinematics;
|
|
using Unity.Cinemachine;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.TestTools;
|
|
using Yarn.Unity;
|
|
|
|
namespace NightclubArcadia.PlayMode.Tests
|
|
{
|
|
/// <summary>
|
|
/// Proves the scene split actually comes up.
|
|
///
|
|
/// Splitting Systems out of the level nulled seven serialized references that
|
|
/// Unity cannot carry across a scene boundary — the two NPCs' dialogue runner, UI
|
|
/// and player fields, and the reveal camera's tracking target. Each is supposed to
|
|
/// be resolved at runtime instead. An EditMode test cannot see any of that, because
|
|
/// none of it has run. This can.
|
|
///
|
|
/// If this suite fails, the game does not start. Treat it that way.
|
|
/// </summary>
|
|
public class BootstrapTests
|
|
{
|
|
const string Bootstrap = "Bootstrap";
|
|
const string Systems = "Systems";
|
|
const string Level = "SC101_ConferenceHall";
|
|
|
|
[UnitySetUp]
|
|
public IEnumerator LoadFromBootstrap()
|
|
{
|
|
yield return SceneManager.LoadSceneAsync(Bootstrap, LoadSceneMode.Single);
|
|
|
|
// GameBootstrap loads the other two additively over a few frames.
|
|
var deadline = Time.realtimeSinceStartup + 20f;
|
|
while (Time.realtimeSinceStartup < deadline)
|
|
{
|
|
if (SceneManager.GetSceneByName(Systems).isLoaded &&
|
|
SceneManager.GetSceneByName(Level).isLoaded)
|
|
{
|
|
break;
|
|
}
|
|
yield return null;
|
|
}
|
|
|
|
// let Awake/Start run on everything that just loaded
|
|
for (var i = 0; i < 5; i++)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Bootstrap_LoadsSystemsAndLevel()
|
|
{
|
|
Assert.IsTrue(SceneManager.GetSceneByName(Systems).isLoaded, "Systems scene did not load");
|
|
Assert.IsTrue(SceneManager.GetSceneByName(Level).isLoaded, "Level scene did not load");
|
|
Assert.AreEqual(Level, SceneManager.GetActiveScene().name,
|
|
"The level should be the active scene, so new objects and lighting land there");
|
|
}
|
|
|
|
[Test]
|
|
public void ThePlayerExists_AndIsTagged()
|
|
{
|
|
var player = GameObject.FindGameObjectWithTag("Player");
|
|
Assert.IsNotNull(player, "No object tagged Player — the reveal camera and the NPCs both need it");
|
|
}
|
|
|
|
[Test]
|
|
public void TheDialogueRunnerExists()
|
|
{
|
|
Assert.IsNotNull(Object.FindFirstObjectByType<DialogueRunner>(),
|
|
"No DialogueRunner in any loaded scene");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Regression: "Can't start dialogue: no Yarn Project has been configured."
|
|
///
|
|
/// Asserting the runner merely exists is not enough — it auto-starts on Start(),
|
|
/// and a runner with no project throws that at load with nothing else wrong.
|
|
/// The project is a serialized asset reference supplied by a prefab override, so
|
|
/// it breaks silently if the override is ever dropped.
|
|
/// </summary>
|
|
[Test]
|
|
public void TheDialogueRunner_HasItsYarnProject()
|
|
{
|
|
var runners = Object.FindObjectsByType<DialogueRunner>(
|
|
FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
|
|
Assert.AreEqual(1, runners.Length,
|
|
"Expected exactly one DialogueRunner across the loaded scenes, found " +
|
|
string.Join(", ", runners.Select(r => $"'{r.name}' in {r.gameObject.scene.name}")));
|
|
|
|
var runner = runners[0];
|
|
Assert.IsNotNull(runner.YarnProject,
|
|
$"'{runner.name}' in scene '{runner.gameObject.scene.name}' has no Yarn Project. " +
|
|
"It auto-starts, so this is the 'no Yarn Project has been configured' error.");
|
|
Assert.IsNotEmpty(runner.YarnProject.NodeNames, "The Yarn Project compiled no nodes");
|
|
}
|
|
|
|
[Test]
|
|
public void EveryNpc_ResolvedItsDialogueRunner()
|
|
{
|
|
var npcs = Object.FindObjectsByType<NPCStandIn>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
Assert.IsNotEmpty(npcs, "Expected NPCs in the level scene");
|
|
|
|
foreach (var npc in npcs)
|
|
{
|
|
Assert.IsNotNull(npc.dialogueRunner,
|
|
$"'{npc.name}' did not resolve a DialogueRunner. It lives in the Systems scene, " +
|
|
"so the serialized reference is gone and SceneServices.Resolve has to find it.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void RevealCameras_BoundToThePlayer()
|
|
{
|
|
var binders = Object.FindObjectsByType<CinemachineFollowsPlayer>(
|
|
FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
Assert.IsNotEmpty(binders, "Expected at least one runtime-bound camera");
|
|
|
|
foreach (var binder in binders)
|
|
{
|
|
Assert.IsNotNull(binder.GetComponent<CinemachineCamera>().Follow,
|
|
$"'{binder.name}' never bound its Follow target to the player");
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|