Fixes the "no Yarn Project has been configured" error coming back after running the setup menus. Last commit fixed the wrong layer. Retargeting YarnDemoSetup at a sandbox stopped it destroying the level, but left the deeper problem: all of these builders assume the single-scene layout, and after the split there is no target path that makes them correct. Two failures, both observed rather than reasoned about. Running the Yarn setup left a generated scene open whose DialogueRunner had no project and auto-started — that is the error the user kept seeing, coming from Dev/YarnSandbox.unity, not from Systems. And retargeting the other three at Systems made PlayerControlSetup pull navigation in with it; the baked NavMeshData is embedded rather than an asset, so Systems.unity silently turned binary, 61KB text to 109KB binary. That is trap 1 recurring on a scene that had just been fixed. So the three scene builders now return early through LegacySceneSetup.Blocked, with a message saying what they would have done and what to do instead. They are not deleted: UILayerSetup alone is 818 lines that still record how the UI layer is wired, and that record matters until prefabs replace it in Phase 5. Set Up Skill System keeps its useful half. It regenerates the SkillDefinition assets from JSON, which touches no scene; only the WireWorkingLevelScene call is dropped, because those objects already exist in Systems.unity. Set Up Candidate System was always asset-only and is untouched. While diagnosing, YarnDemoSetup's own bug was found and fixed even though the menu is now blocked: it set yarnProject through a SerializedObject on a prefab instance without recording the override, so the assignment was dropped on save. It now records the modification and refuses to save a scene whose runner has no project. Verified by running all five menus headlessly: the two asset generators run and produce no diff, the three builders refuse, and no scene or asset changes. Systems.unity restored to text, the level scene intact at 5 roots. EditMode 42/42, PlayMode 6/6, YarnCheck 9 files / 35 nodes. Co-Authored-By: Claude Opus 5 <[email protected]>
321 lines
11 KiB
C#
321 lines
11 KiB
C#
using NightclubArcadia.Cinematics;
|
|
using NightclubArcadia.Interaction;
|
|
using NightclubArcadia.Player;
|
|
using StarterAssets;
|
|
using Unity.AI.Navigation;
|
|
using Unity.Cinemachine;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
|
|
/// <summary>
|
|
/// Wires player control, navigation, and camera systems into Systems.unity.
|
|
/// Run via Tools → Nightclub Arcadia → Set Up Player Control & Camera.
|
|
/// </summary>
|
|
public static class PlayerControlSetup
|
|
{
|
|
const string ScenePath = NightclubArcadia.EditorTools.ScenePaths.Systems;
|
|
const string PlayerPrefabPath = "Assets/StarterAssets/ThirdPersonController/Prefabs/PlayerArmature.prefab";
|
|
const string SettingsFolder = "Assets/Settings";
|
|
|
|
[MenuItem("Tools/Nightclub Arcadia/Set Up Player Control & Camera")]
|
|
public static void SetUp()
|
|
{
|
|
if (NightclubArcadia.EditorTools.LegacySceneSetup.Blocked("Set Up Player Control & Camera",
|
|
"rebuild the player and camera rig and pull navigation into Systems.unity, "
|
|
+ "which turns that scene binary")) return;
|
|
|
|
EnsurePlayerPrefab();
|
|
WireScene();
|
|
AssetDatabase.SaveAssets();
|
|
Debug.Log("[PlayerControlSetup] Player control and camera setup complete.");
|
|
}
|
|
|
|
static void EnsurePlayerPrefab()
|
|
{
|
|
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
|
|
if (prefab == null)
|
|
{
|
|
Debug.LogError($"[PlayerControlSetup] Missing prefab at {PlayerPrefabPath}");
|
|
return;
|
|
}
|
|
|
|
var root = PrefabUtility.LoadPrefabContents(PlayerPrefabPath);
|
|
|
|
EnsureComponent<PlayerPointerInput>(root);
|
|
EnsureComponent<PlayerMotionArbiter>(root);
|
|
EnsureComponent<ClickToMoveController>(root);
|
|
EnsureComponent<PointerNavigationInput>(root);
|
|
EnsureComponent<NavMeshAgent>(root);
|
|
|
|
var inputs = root.GetComponent<StarterAssetsInputs>();
|
|
if (inputs != null)
|
|
{
|
|
inputs.analogMovement = true;
|
|
inputs.cursorLocked = false;
|
|
}
|
|
|
|
var controller = root.GetComponent<ThirdPersonController>();
|
|
if (controller != null)
|
|
{
|
|
controller.LockCameraPosition = true;
|
|
}
|
|
|
|
var clickToMove = root.GetComponent<ClickToMoveController>();
|
|
var pointerNav = root.GetComponent<PointerNavigationInput>();
|
|
var pointerInput = root.GetComponent<PlayerPointerInput>();
|
|
var arbiter = root.GetComponent<PlayerMotionArbiter>();
|
|
|
|
WireSerializedReference(clickToMove, "brainCamera", null);
|
|
WireSerializedReference(pointerNav, "pointerInput", pointerInput);
|
|
WireSerializedReference(pointerNav, "clickToMove", clickToMove);
|
|
WireSerializedReference(pointerNav, "motionArbiter", arbiter);
|
|
|
|
PrefabUtility.SaveAsPrefabAsset(root, PlayerPrefabPath);
|
|
PrefabUtility.UnloadPrefabContents(root);
|
|
}
|
|
|
|
static void WireScene()
|
|
{
|
|
var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
|
|
var player = GameObject.FindGameObjectWithTag("Player");
|
|
if (player == null)
|
|
{
|
|
Debug.LogError("[PlayerControlSetup] No Player-tagged object found in scene.");
|
|
return;
|
|
}
|
|
|
|
EnsureComponent<PlayerPointerInput>(player);
|
|
EnsureComponent<PlayerMotionArbiter>(player);
|
|
EnsureComponent<ClickToMoveController>(player);
|
|
EnsureComponent<PointerNavigationInput>(player);
|
|
EnsureComponent<NavMeshAgent>(player);
|
|
|
|
var inputs = player.GetComponent<StarterAssetsInputs>();
|
|
if (inputs != null)
|
|
{
|
|
inputs.analogMovement = true;
|
|
inputs.cursorLocked = false;
|
|
}
|
|
|
|
var controller = player.GetComponent<ThirdPersonController>();
|
|
if (controller != null)
|
|
{
|
|
controller.LockCameraPosition = true;
|
|
}
|
|
|
|
var brainCamera = Camera.main;
|
|
var pointerInput = player.GetComponent<PlayerPointerInput>();
|
|
var clickToMove = player.GetComponent<ClickToMoveController>();
|
|
var pointerNav = player.GetComponent<PointerNavigationInput>();
|
|
var arbiter = player.GetComponent<PlayerMotionArbiter>();
|
|
|
|
WireSerializedReference(clickToMove, "brainCamera", brainCamera);
|
|
WireSerializedReference(pointerNav, "brainCamera", brainCamera);
|
|
WireSerializedReference(pointerNav, "pointerInput", pointerInput);
|
|
WireSerializedReference(pointerNav, "clickToMove", clickToMove);
|
|
WireSerializedReference(pointerNav, "motionArbiter", arbiter);
|
|
|
|
var groundLayer = LayerMask.NameToLayer("Ground");
|
|
var interactableLayer = LayerMask.NameToLayer("Interactable");
|
|
SetLayerIfExists("Floor", groundLayer);
|
|
SetLayerIfExists("Room_101", groundLayer);
|
|
SetLayerIfExists("Wall_Back", groundLayer);
|
|
SetLayerIfExists("Wall_Front", groundLayer);
|
|
SetLayerIfExists("Table", groundLayer);
|
|
|
|
WireBartender("Bartender", interactableLayer);
|
|
WireBartender("Bartender (1)", interactableLayer);
|
|
|
|
EnsureNavigation(player.transform.position);
|
|
EnsureCameraAnchor(player.transform, brainCamera);
|
|
|
|
EditorSceneManager.MarkSceneDirty(scene);
|
|
EditorSceneManager.SaveScene(scene);
|
|
}
|
|
|
|
static void WireBartender(string objectName, int interactableLayer)
|
|
{
|
|
var bartender = GameObject.Find(objectName);
|
|
if (bartender == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (interactableLayer >= 0)
|
|
{
|
|
bartender.layer = interactableLayer;
|
|
}
|
|
|
|
EnsureComponent<ClickInteractableBridge>(bartender);
|
|
EnsureComponent<CameraPointOfInterest>(bartender);
|
|
}
|
|
|
|
static void EnsureNavigation(Vector3 sampleOrigin)
|
|
{
|
|
var navigation = GameObject.Find("Navigation");
|
|
if (navigation == null)
|
|
{
|
|
navigation = new GameObject("Navigation");
|
|
}
|
|
|
|
var surface = EnsureComponent<NavMeshSurface>(navigation);
|
|
surface.agentTypeID = 0;
|
|
surface.collectObjects = CollectObjects.All;
|
|
surface.useGeometry = NavMeshCollectGeometry.RenderMeshes;
|
|
|
|
var included = 0;
|
|
var defaultLayer = LayerMask.NameToLayer("Default");
|
|
var groundLayer = LayerMask.NameToLayer("Ground");
|
|
if (defaultLayer >= 0)
|
|
{
|
|
included |= 1 << defaultLayer;
|
|
}
|
|
|
|
if (groundLayer >= 0)
|
|
{
|
|
included |= 1 << groundLayer;
|
|
}
|
|
|
|
surface.layerMask = included;
|
|
|
|
var table = GameObject.Find("Table");
|
|
if (table != null)
|
|
{
|
|
var modifier = EnsureComponent<NavMeshModifier>(table);
|
|
modifier.overrideArea = true;
|
|
modifier.area = NavMesh.GetAreaFromName("Not Walkable");
|
|
}
|
|
|
|
surface.BuildNavMesh();
|
|
|
|
if (!NavMesh.SamplePosition(sampleOrigin, out _, 2f, NavMesh.AllAreas))
|
|
{
|
|
Debug.LogWarning("[PlayerControlSetup] NavMesh bake did not cover the player start position.");
|
|
}
|
|
}
|
|
|
|
static void EnsureCameraAnchor(Transform followTarget, Camera brainCamera)
|
|
{
|
|
var vcamObject = GameObject.Find("CinemachineCamera");
|
|
if (vcamObject == null)
|
|
{
|
|
Debug.LogError("[PlayerControlSetup] CinemachineCamera not found.");
|
|
return;
|
|
}
|
|
|
|
var vcam = vcamObject.GetComponent<CinemachineCamera>();
|
|
var composer = vcamObject.GetComponent<CinemachinePositionComposer>();
|
|
|
|
var anchorObject = GameObject.Find("CameraAnchor");
|
|
if (anchorObject == null)
|
|
{
|
|
anchorObject = new GameObject("CameraAnchor");
|
|
}
|
|
|
|
anchorObject.transform.position = followTarget.position;
|
|
|
|
var anchor = EnsureComponent<CameraRigAnchor>(anchorObject);
|
|
EnsureComponent<CameraPanInput>(anchorObject);
|
|
EnsureComponent<CameraPointOfInterestDirector>(anchorObject);
|
|
EnsureComponent<CameraStateDirector>(anchorObject);
|
|
|
|
WireSerializedReference(anchor, "followTarget", followTarget);
|
|
WireSerializedReference(anchor, "composer", composer);
|
|
|
|
if (vcam != null)
|
|
{
|
|
vcam.Target.TrackingTarget = anchorObject.transform;
|
|
vcam.Priority.Value = 10;
|
|
}
|
|
|
|
var pan = anchorObject.GetComponent<CameraPanInput>();
|
|
WireSerializedReference(pan, "brainCamera", brainCamera);
|
|
WireSerializedReference(pan, "anchor", anchor);
|
|
WireSerializedReference(pan, "stateDirector", anchorObject.GetComponent<CameraStateDirector>());
|
|
|
|
var poiDirector = anchorObject.GetComponent<CameraPointOfInterestDirector>();
|
|
WireSerializedReference(poiDirector, "anchor", anchor);
|
|
WireSerializedReference(poiDirector, "stateDirector", anchorObject.GetComponent<CameraStateDirector>());
|
|
|
|
var stateDirector = anchorObject.GetComponent<CameraStateDirector>();
|
|
WireSerializedReference(stateDirector, "anchor", anchor);
|
|
|
|
EnsureRevealCamera(vcamObject, followTarget);
|
|
}
|
|
|
|
static void EnsureRevealCamera(GameObject followVcam, Transform followTarget)
|
|
{
|
|
var revealName = "CM_Reveal_Room101";
|
|
var revealObject = GameObject.Find(revealName);
|
|
if (revealObject == null)
|
|
{
|
|
revealObject = new GameObject(revealName);
|
|
revealObject.transform.SetPositionAndRotation(
|
|
followVcam.transform.position + new Vector3(-4f, 2f, -2f),
|
|
followVcam.transform.rotation);
|
|
}
|
|
|
|
var revealCam = EnsureComponent<CinemachineCamera>(revealObject);
|
|
revealCam.Target.TrackingTarget = followTarget;
|
|
revealCam.Priority.Value = 0;
|
|
revealCam.Lens.FieldOfView = followVcam.GetComponent<CinemachineCamera>().Lens.FieldOfView;
|
|
|
|
var volumeObject = GameObject.Find("RevealVolume_Room101");
|
|
if (volumeObject == null)
|
|
{
|
|
volumeObject = new GameObject("RevealVolume_Room101");
|
|
volumeObject.transform.position = followTarget.position + new Vector3(0f, 1f, 2f);
|
|
}
|
|
|
|
var box = EnsureComponent<BoxCollider>(volumeObject);
|
|
box.isTrigger = true;
|
|
box.size = new Vector3(6f, 3f, 4f);
|
|
|
|
var volume = EnsureComponent<CameraFramingVolume>(volumeObject);
|
|
WireSerializedReference(volume, "revealCamera", revealCam);
|
|
}
|
|
|
|
static T EnsureComponent<T>(GameObject target) where T : Component
|
|
{
|
|
var component = target.GetComponent<T>();
|
|
if (component == null)
|
|
{
|
|
component = target.AddComponent<T>();
|
|
}
|
|
|
|
return component;
|
|
}
|
|
|
|
static void SetLayerIfExists(string objectName, int layer)
|
|
{
|
|
if (layer < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var obj = GameObject.Find(objectName);
|
|
if (obj != null)
|
|
{
|
|
obj.layer = layer;
|
|
}
|
|
}
|
|
|
|
static void WireSerializedReference(Object target, string propertyName, Object value)
|
|
{
|
|
if (target == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var so = new SerializedObject(target);
|
|
var property = so.FindProperty(propertyName);
|
|
if (property != null)
|
|
{
|
|
property.objectReferenceValue = value;
|
|
so.ApplyModifiedPropertiesWithoutUndo();
|
|
}
|
|
}
|
|
}
|