The game now starts from Bootstrap.unity, which additively loads Systems (the dialogue runner, skills, UI layer, camera rig and the player) and then a level (geometry, light, navmesh, reveal cameras). The level becomes the active scene so new objects and lighting land there. The player moved into Systems. It had been parented under Room_101 — under level geometry — and it is persistent content, not level content. Moving it also removes most of the cross-scene breakage on its own. Unity nulls any serialized reference that crosses a scene boundary. Measured rather than guessed: the pre-split scene was pulled from git and its null references diffed against the split result. Exactly seven were lost — both NPCs' dialogueRunner, dialogueUI and player, plus the reveal camera's tracking target. A first naive audit reported 210, which turned out to be pre-existing Unity defaults like Image.m_Material; the baseline diff is what separated the two. Each of the seven now resolves at runtime. SceneServices.Resolve fills in a null Inspector reference by searching the loaded scenes, keeping an assigned value if there is one; CinemachineFollowsPlayer binds the reveal camera once the player exists. Both follow the pattern already used by CameraFramingVolume and by NPCStandIn's player lookup, rather than introducing a new one. Assets/Tests/PlayMode is new, and it immediately paid for itself. BootstrapTests loads Bootstrap and asserts the whole game comes up; on its first run it caught a real bug the split had introduced. The player's NavMeshAgent lives in Systems while the NavMesh is baked into the level, so during load the agent exists off-mesh and ResetPath logs an error. ClickToMoveController now guards on agent.isOnNavMesh rather than a bare null check. No EditMode test could have seen that, because none of it has run yet. Two of those checks reach their types by name through reflection: NPCStandIn and the Cinematics namespace live in Assembly-CSharp, and an asmdef test assembly cannot reference the predefined assemblies. Per-area asmdefs remove the need. Build settings list all three scenes with Bootstrap at index 0, which LoadSceneAsync by name requires. EditMode 42/42, PlayMode 5/5, YarnCheck 9 files / 35 nodes. Co-Authored-By: Claude Opus 5 <[email protected]>
74 lines
2.4 KiB
C#
74 lines
2.4 KiB
C#
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using Yarn.Unity;
|
|
using NightclubArcadia.Core;
|
|
|
|
namespace NightclubArcadia.Dialogue
|
|
{
|
|
/// <summary>
|
|
/// Trigger-zone interactable: player walks in range, presses E, and this
|
|
/// starts the given Yarn node. The reverse of DialogueTransitionController's
|
|
/// <<enter_environment>> — this is the "step into dialogue" half of the loop.
|
|
/// </summary>
|
|
public class DialogueInteractable : MonoBehaviour
|
|
{
|
|
[SerializeField] private DialogueRunner dialogueRunner;
|
|
[SerializeField] private GameObject dialogueUI;
|
|
[SerializeField] private string yarnNodeName = "Bartender_Chat";
|
|
|
|
private bool playerInRange;
|
|
private InputAction interactAction;
|
|
|
|
void Awake()
|
|
{
|
|
// Same cross-scene resolution as NPCStandIn: the runner is in Systems.
|
|
dialogueRunner = SceneServices.Resolve(dialogueRunner, this, nameof(dialogueRunner));
|
|
|
|
interactAction = new InputAction("Interact");
|
|
interactAction.AddBinding("<Keyboard>/e");
|
|
interactAction.AddBinding("<Gamepad>/buttonNorth");
|
|
interactAction.performed += _ =>
|
|
{
|
|
if (playerInRange)
|
|
{
|
|
StartConversation();
|
|
}
|
|
};
|
|
}
|
|
|
|
void OnEnable() => interactAction?.Enable();
|
|
void OnDisable() => interactAction?.Disable();
|
|
|
|
void OnTriggerEnter(Collider other)
|
|
{
|
|
if (other.CompareTag("Player"))
|
|
{
|
|
playerInRange = true;
|
|
}
|
|
}
|
|
|
|
void OnTriggerExit(Collider other)
|
|
{
|
|
if (other.CompareTag("Player"))
|
|
{
|
|
playerInRange = false;
|
|
}
|
|
}
|
|
|
|
async void StartConversation()
|
|
{
|
|
// Player controls are locked by DialogueTransitionController off the
|
|
// runner's onDialogueStart event — nothing to do for that here.
|
|
if (DialogueUIVisibility.Show(dialogueUI))
|
|
{
|
|
// Only reached if the UI was found deactivated; give TMP a
|
|
// frame to re-cache its Canvas in OnEnable before any text is
|
|
// generated.
|
|
await Awaitable.NextFrameAsync();
|
|
}
|
|
|
|
await dialogueRunner.StartDialogue(yarnNodeName);
|
|
}
|
|
}
|
|
}
|