904 lines
46 KiB
C#
904 lines
46 KiB
C#
using System.Collections.Generic;
|
||
using System.Reflection;
|
||
using BaseGames.Audio;
|
||
using BaseGames.Camera;
|
||
using BaseGames.Combat;
|
||
using BaseGames.Core;
|
||
using BaseGames.Core.Events;
|
||
using BaseGames.Core.Pool;
|
||
using BaseGames.Enemies;
|
||
using BaseGames.Input;
|
||
using BaseGames.Player;
|
||
using BaseGames.Player.States;
|
||
using BaseGames.Skills;
|
||
using BaseGames.UI;
|
||
using BaseGames.UI.HUD;
|
||
using BaseGames.UI.Menus;
|
||
using BaseGames.World;
|
||
using PathBerserker2d;
|
||
using Unity.Cinemachine;
|
||
using UnityEditor;
|
||
using UnityEditor.SceneManagement;
|
||
using UnityEngine;
|
||
using UnityEngine.SceneManagement;
|
||
using UnityEngine.Tilemaps;
|
||
using UnityEngine.UI;
|
||
|
||
namespace BaseGames.Editor
|
||
{
|
||
public static class SceneScaffoldTools
|
||
{
|
||
|
||
[MenuItem("BaseGames/Tools/Scaffold Persistent Scene")]
|
||
public static void ScaffoldPersistentScene()
|
||
{
|
||
var report = new List<string>();
|
||
|
||
EnsureEventChannelAssets(report);
|
||
|
||
GameObject root = GetOrCreateRoot("[Persistent]");
|
||
Transform services = GetOrCreateChild(root.transform, "[Services]");
|
||
Transform input = GetOrCreateChild(root.transform, "[Input]");
|
||
Transform camera = GetOrCreateChild(root.transform, "[Camera]");
|
||
Transform ui = GetOrCreateChild(root.transform, "[UI]");
|
||
|
||
GameObject registrarGo = GetOrCreateChild(services, "GameServiceRegistrar").gameObject;
|
||
GameObject deathRespawnGo = GetOrCreateChild(services, "DeathRespawnService").gameObject;
|
||
GameObject sceneServiceGo = GetOrCreateChild(services, "SceneService").gameObject;
|
||
GameObject sceneLoaderGo = GetOrCreateChild(services, "SceneLoader").gameObject;
|
||
GameObject registryGo = GetOrCreateChild(services, "EventChannelRegistry").gameObject;
|
||
GameObject settingsGo = GetOrCreateChild(services, "SettingsManager").gameObject;
|
||
GameObject poolGo = GetOrCreateChild(services, "GlobalObjectPool").gameObject;
|
||
GameObject gameManagerGo = GetOrCreateChild(services, "GameManager").gameObject;
|
||
GameObject audioManagerGo = GetOrCreateChild(services, "AudioManager").gameObject;
|
||
|
||
GameServiceRegistrar registrar = GetOrAddComponent<GameServiceRegistrar>(registrarGo);
|
||
DeathRespawnService deathRespawnService = GetOrAddComponent<DeathRespawnService>(deathRespawnGo);
|
||
SceneService sceneService = GetOrAddComponent<SceneService>(sceneServiceGo);
|
||
SceneLoader sceneLoader = GetOrAddComponent<SceneLoader>(sceneLoaderGo);
|
||
EventChannelRegistry registry = GetOrAddComponent<EventChannelRegistry>(registryGo);
|
||
SettingsManager settingsManager = GetOrAddComponent<SettingsManager>(settingsGo);
|
||
GetOrAddComponent<GlobalObjectPool>(poolGo);
|
||
GameManager gameManager = GetOrAddComponent<GameManager>(gameManagerGo);
|
||
AudioManager audioManager = GetOrAddComponent<AudioManager>(audioManagerGo);
|
||
|
||
GameObject inputHolderGo = GetOrCreateChild(input, "InputReaderHolder").gameObject;
|
||
Object inputReaderAsset = FindFirstAssetByType<InputReaderSO>("InputReader", "InputReaderSO");
|
||
if (inputReaderAsset == null)
|
||
inputReaderAsset = EnsureInputReaderAsset(report);
|
||
|
||
InputReaderBootstrap inputBootstrap = GetOrAddComponent<InputReaderBootstrap>(inputHolderGo);
|
||
AssignReference(inputBootstrap, "_inputReader", inputReaderAsset, report);
|
||
if (inputReaderAsset != null)
|
||
{
|
||
AssignReference(inputReaderAsset, "_onPauseRequested", FindFirstAssetByType<VoidEventChannelSO>("EVT_PauseRequested"), report);
|
||
AssignReference(inputReaderAsset, "_inputActions", FindFirstAssetWithExtension(".inputactions", "PlayerInputActions", "InputActions"), report);
|
||
}
|
||
if (inputReaderAsset == null)
|
||
report.Add("未找到 InputReaderSO 资产,InputReaderBootstrap 将保持空引用。请补齐 Assets/Data/Input/InputReader.asset。");
|
||
|
||
GameObject mainCameraGo = GetOrCreateChild(camera, "Main Camera").gameObject;
|
||
UnityEngine.Camera mainCamera = GetOrAddComponent<UnityEngine.Camera>(mainCameraGo);
|
||
mainCamera.orthographic = false;
|
||
mainCamera.fieldOfView = 60f;
|
||
mainCameraGo.tag = "MainCamera";
|
||
GetOrAddComponent<AudioListener>(mainCameraGo);
|
||
CinemachineBrain brain = GetOrAddComponent<CinemachineBrain>(mainCameraGo);
|
||
|
||
GameObject cameraStateGo = GetOrCreateChild(camera, "CameraStateController").gameObject;
|
||
CameraStateController cameraStateController = GetOrAddComponent<CameraStateController>(cameraStateGo);
|
||
CinemachineImpulseSource impulseSource = GetOrAddComponent<CinemachineImpulseSource>(cameraStateGo);
|
||
|
||
GameObject uiRootGo = GetOrCreateChild(ui, "UIRoot").gameObject;
|
||
UIManager uiManager = GetOrAddComponent<UIManager>(uiRootGo);
|
||
|
||
GameObject hudCanvasGo = GetOrCreateCanvas(uiRootGo.transform, "HUD Canvas", 0);
|
||
GameObject hudRootGo = GetOrCreateChild(hudCanvasGo.transform, "HUDRoot").gameObject;
|
||
HUDController hudController = GetOrAddComponent<HUDController>(hudRootGo);
|
||
|
||
GameObject pauseRootGo = GetOrCreateChild(uiRootGo.transform, "PauseMenuRoot").gameObject;
|
||
GameObject settingsRootGo = GetOrCreateChild(uiRootGo.transform, "SettingsRoot").gameObject;
|
||
GameObject mapRootGo = GetOrCreateChild(uiRootGo.transform, "MapRoot").gameObject;
|
||
GameObject shopRootGo = GetOrCreateChild(uiRootGo.transform, "ShopRoot").gameObject;
|
||
pauseRootGo.SetActive(false);
|
||
settingsRootGo.SetActive(false);
|
||
mapRootGo.SetActive(false);
|
||
shopRootGo.SetActive(false);
|
||
|
||
GameObject deathCanvasGo = GetOrCreateCanvas(uiRootGo.transform, "DeathScreen Canvas", 10);
|
||
GameObject deathRootGo = GetOrCreateChild(deathCanvasGo.transform, "DeathScreenRoot").gameObject;
|
||
DeathScreenController deathScreenController = GetOrAddComponent<DeathScreenController>(deathRootGo);
|
||
deathRootGo.SetActive(false);
|
||
GameObject respawnButtonGo = GetOrCreateChild(deathRootGo.transform, "RespawnButton").gameObject;
|
||
GetOrAddComponent<Image>(respawnButtonGo);
|
||
Button respawnButton = GetOrAddComponent<Button>(respawnButtonGo);
|
||
|
||
EnsureAudioSources(audioManagerGo, audioManager, report);
|
||
|
||
AssignReference(registrar, "_deathRespawnService", deathRespawnService);
|
||
AssignReference(registrar, "_sceneService", sceneService);
|
||
AssignReference(registrar, "_eventChannelRegistry", registry);
|
||
|
||
AssignReference(gameManager, "_settingsManager", settingsManager);
|
||
AssignReference(gameManager, "_deathRespawnService", deathRespawnService);
|
||
AssignReference(gameManager, "_sceneService", sceneService);
|
||
AssignAsset(gameManager, "_onPlayerDied", report, true, "EVT_PlayerDied");
|
||
AssignAsset(gameManager, "_onPauseRequested", report, false, "EVT_PauseRequested");
|
||
AssignAsset(gameManager, "_onResumeRequested", report, false, "EVT_ResumeRequested", "EVT_PauseResumed");
|
||
AssignAsset(gameManager, "_onBossFightStarted", report, false, "EVT_BossFightStarted", "EVT_BossFight");
|
||
AssignAsset(gameManager, "_onBossFightEnded", report, false, "EVT_BossFightEnded");
|
||
AssignAsset(gameManager, "_onDeathScreenConfirmed", report, true, "EVT_DeathScreenConfirmed");
|
||
AssignAsset(gameManager, "_onGameStateChanged", report, true, "EVT_GameStateChanged", "EVT_GameState");
|
||
AssignAsset(gameManager, "_onPlayerRespawned", report, false, "EVT_PlayerRespawned", "EVT_PlayerRespawn");
|
||
|
||
AssignAsset(sceneService, "_onSceneLoadRequest", report, false, "EVT_SceneLoadRequest");
|
||
AssignAsset(sceneService, "_onFadeInRequest", report, false, "EVT_FadeInRequest");
|
||
AssignAsset(sceneService, "_onFadeOutRequest", report, false, "EVT_FadeOutRequest");
|
||
AssignReference(sceneService, "_sceneLoader", sceneLoader);
|
||
|
||
AssignAsset(sceneLoader, "_onSceneLoaded", report, false, "EVT_SceneLoaded");
|
||
|
||
AssignAsset(deathRespawnService, "_onRespawnStarted", report, false, "EVT_RespawnStarted");
|
||
AssignAsset(deathRespawnService, "_onRespawnCompleted", report, false, "EVT_RespawnCompleted");
|
||
AssignAsset(deathRespawnService, "_onDeathScreenConfirmed", report, true, "EVT_DeathScreenConfirmed");
|
||
|
||
AssignAsset(settingsManager, "_defaultSettings", report, false, "SET_GlobalSettings");
|
||
|
||
AssignAsset(audioManager, "_onPlayerDied", report, false, "EVT_PlayerDied");
|
||
|
||
AssignReference(cameraStateController, "_brain", brain);
|
||
AssignReference(cameraStateController, "_impulseSource", impulseSource);
|
||
|
||
AssignReference(uiManager, "_hudRoot", hudRootGo);
|
||
AssignReference(uiManager, "_pauseMenuRoot", pauseRootGo);
|
||
AssignReference(uiManager, "_deathScreenRoot", deathRootGo);
|
||
AssignReference(uiManager, "_settingsRoot", settingsRootGo);
|
||
AssignReference(uiManager, "_mapRoot", mapRootGo);
|
||
AssignReference(uiManager, "_shopRoot", shopRootGo);
|
||
AssignAsset(uiManager, "_onGameStateChanged", report, true, "EVT_GameStateChanged", "EVT_GameState");
|
||
AssignAsset(uiManager, "_onPauseRequested", report, false, "EVT_PauseRequested");
|
||
AssignAsset(uiManager, "_onFastTravelOpen", report, false, "EVT_FastTravelOpen");
|
||
AssignAsset(uiManager, "_onShopOpen", report, false, "EVT_ShopOpen");
|
||
AssignAsset(uiManager, "_onMapOpen", report, false, "EVT_MapOpen");
|
||
|
||
AssignReference(deathScreenController, "_btnRespawn", respawnButton);
|
||
AssignAsset(deathScreenController, "_onPlayerDied", report, true, "EVT_PlayerDied");
|
||
AssignAsset(deathScreenController, "_onDeathScreenConfirmed", report, true, "EVT_DeathScreenConfirmed");
|
||
|
||
AddScaffoldNote(hudRootGo, "HUDController 已挂载。其内部图片/文本/图标 Prefab 依赖较多,需后续手工补 UI 资源与事件频道。", report);
|
||
|
||
MarkDirtyAndLog("Persistent 场景脚手架", root, report);
|
||
}
|
||
|
||
private static void EnsureEventChannelAssets(List<string> report)
|
||
{
|
||
bool hasCoreSet =
|
||
FindFirstAsset("EVT_PlayerDied") != null &&
|
||
FindFirstAsset("EVT_DeathScreenConfirmed") != null &&
|
||
(FindFirstAsset("EVT_GameStateChanged") != null || FindFirstAsset("EVT_GameState") != null) &&
|
||
FindFirstAsset("EVT_PauseRequested") != null &&
|
||
FindFirstAsset("EVT_SceneLoadRequest") != null;
|
||
|
||
if (hasCoreSet)
|
||
return;
|
||
|
||
CreateEventChannelAssets.CreateAll();
|
||
report?.Add("检测到关键事件频道缺失,已自动执行 Create Event Channel Assets。");
|
||
}
|
||
|
||
|
||
[MenuItem("BaseGames/Tools/Scaffold Test Room")]
|
||
public static void ScaffoldTestRoom()
|
||
{
|
||
var report = new List<string>();
|
||
|
||
EnsureEventChannelAssets(report);
|
||
|
||
Object inputReaderAsset = FindFirstAssetByType<InputReaderSO>("InputReader", "InputReaderSO");
|
||
if (inputReaderAsset == null)
|
||
inputReaderAsset = EnsureInputReaderAsset(report);
|
||
|
||
GameObject root = GetOrCreateRoot("[TestRoom]");
|
||
Transform environment = GetOrCreateChild(root.transform, "[Environment]");
|
||
Transform playerRoot = GetOrCreateChild(root.transform, "[Player]");
|
||
Transform enemyRoot = GetOrCreateChild(root.transform, "[Enemy]");
|
||
Transform savePointRoot = GetOrCreateChild(root.transform, "[SavePoint]");
|
||
Transform cameraRoot = GetOrCreateChild(root.transform, "[Camera]");
|
||
|
||
DisableRenderCamerasUnderRoot(root.transform, report);
|
||
|
||
Object playerStatsConfigAsset = EnsurePlayerStatsConfigAsset(report);
|
||
Object playerMovementConfigAsset = EnsurePlayerMovementConfigAsset(report);
|
||
Object playerAnimationConfigAsset = EnsurePlayerAnimationConfigAsset(report);
|
||
|
||
GameObject groundGridGo = GetOrCreateChild(environment, "GroundGrid").gameObject;
|
||
GetOrAddComponent<Grid>(groundGridGo);
|
||
GameObject groundGo = GetOrCreateChild(groundGridGo.transform, "Ground").gameObject;
|
||
TilemapCollider2D tilemapCollider = GetOrAddComponent<TilemapCollider2D>(groundGo);
|
||
tilemapCollider.usedByComposite = true;
|
||
GetOrAddComponent<Tilemap>(groundGo);
|
||
GetOrAddComponent<TilemapRenderer>(groundGo);
|
||
Rigidbody2D groundBody = GetOrAddComponent<Rigidbody2D>(groundGo);
|
||
groundBody.bodyType = RigidbodyType2D.Static;
|
||
GetOrAddComponent<CompositeCollider2D>(groundGo);
|
||
SetLayer(groundGo, "Ground", report);
|
||
|
||
GameObject fallbackFloorGo = GetOrCreateChild(environment, "GroundFallback").gameObject;
|
||
fallbackFloorGo.transform.position = new Vector3(0f, -2f, 0f);
|
||
BoxCollider2D fallbackFloorCollider = GetOrAddComponent<BoxCollider2D>(fallbackFloorGo);
|
||
fallbackFloorCollider.size = new Vector2(40f, 1f);
|
||
Rigidbody2D fallbackFloorBody = GetOrAddComponent<Rigidbody2D>(fallbackFloorGo);
|
||
fallbackFloorBody.bodyType = RigidbodyType2D.Static;
|
||
SetLayer(fallbackFloorGo, "Ground", report);
|
||
EnsureVisualSprite(fallbackFloorGo.transform, "Visual", new Color(0.25f, 0.7f, 0.3f, 1f), new Vector2(40f, 1f), -10);
|
||
AddScaffoldNote(fallbackFloorGo, "保底地板用于测试:若 Tilemap 资源未提供碰撞形状,角色仍不会穿地。", report);
|
||
|
||
GameObject navSurfaceGo = GetOrCreateChild(environment, "NavSurfaceRoot").gameObject;
|
||
GetOrAddComponent<NavSurface>(navSurfaceGo);
|
||
AddScaffoldNote(navSurfaceGo, "NavSurface 已创建,仍需在 Unity Inspector 中点击 Bake。", report);
|
||
|
||
GameObject playerGo = GetOrCreateChild(playerRoot, "Player").gameObject;
|
||
RemoveMissingScripts(playerGo, recursive: true, report);
|
||
playerGo.transform.position = new Vector3(0f, 1f, 0f);
|
||
playerGo.tag = "Player";
|
||
SetLayer(playerGo, "Player", report);
|
||
PlayerController playerController = GetOrAddComponent<PlayerController>(playerGo);
|
||
InputBuffer inputBuffer = GetOrAddComponent<InputBuffer>(playerGo);
|
||
PlayerMovement playerMovement = GetOrAddComponent<PlayerMovement>(playerGo);
|
||
PlayerStats playerStats = GetOrAddComponent<PlayerStats>(playerGo);
|
||
PlayerCombat playerCombat = GetOrAddComponent<PlayerCombat>(playerGo);
|
||
FormController formController = GetOrAddComponent<FormController>(playerGo);
|
||
WeaponManager weaponManager = GetOrAddComponent<WeaponManager>(playerGo);
|
||
SkillManager skillManager = GetOrAddComponent<SkillManager>(playerGo);
|
||
SpringSystem springSystem = GetOrAddComponent<SpringSystem>(playerGo);
|
||
Component parrySystem = AddComponentIfTypeExists(playerGo, "BaseGames.Parry.ParrySystem", report);
|
||
ShieldComponent shieldComponent = GetOrAddComponent<ShieldComponent>(playerGo);
|
||
Rigidbody2D playerBody = GetOrAddComponent<Rigidbody2D>(playerGo);
|
||
playerBody.bodyType = RigidbodyType2D.Dynamic;
|
||
playerBody.gravityScale = 2f;
|
||
playerBody.constraints = RigidbodyConstraints2D.FreezeRotation;
|
||
GetOrAddComponent<CapsuleCollider2D>(playerGo);
|
||
Animancer.AnimancerComponent playerAnimancer = GetOrAddComponent<Animancer.AnimancerComponent>(playerGo);
|
||
EnsureVisualSprite(playerGo.transform, "Visual", new Color(0.3f, 0.7f, 1f, 1f), new Vector2(0.8f, 1.6f), 20);
|
||
|
||
GameObject groundCheckGo = GetOrCreateChild(playerGo.transform, "GroundCheck").gameObject;
|
||
groundCheckGo.transform.localPosition = new Vector3(0f, -0.5f, 0f);
|
||
|
||
GameObject hitBoxGroundGo = GetOrCreateChild(playerGo.transform, "HitBoxGround").gameObject;
|
||
BoxCollider2D hitBoxGroundCollider = GetOrAddComponent<BoxCollider2D>(hitBoxGroundGo);
|
||
hitBoxGroundCollider.isTrigger = true;
|
||
HitBox hitBoxGround = GetOrAddComponent<HitBox>(hitBoxGroundGo);
|
||
SetLayer(hitBoxGroundGo, "PlayerHitBox", report);
|
||
|
||
GameObject hitBoxUpGo = GetOrCreateChild(playerGo.transform, "HitBoxUp").gameObject;
|
||
BoxCollider2D hitBoxUpCollider = GetOrAddComponent<BoxCollider2D>(hitBoxUpGo);
|
||
hitBoxUpCollider.isTrigger = true;
|
||
HitBox hitBoxUp = GetOrAddComponent<HitBox>(hitBoxUpGo);
|
||
SetLayer(hitBoxUpGo, "PlayerHitBox", report);
|
||
|
||
GameObject hitBoxDownGo = GetOrCreateChild(playerGo.transform, "HitBoxDown").gameObject;
|
||
BoxCollider2D hitBoxDownCollider = GetOrAddComponent<BoxCollider2D>(hitBoxDownGo);
|
||
hitBoxDownCollider.isTrigger = true;
|
||
HitBox hitBoxDown = GetOrAddComponent<HitBox>(hitBoxDownGo);
|
||
SetLayer(hitBoxDownGo, "PlayerHitBox", report);
|
||
|
||
GameObject hitBoxAirGo = GetOrCreateChild(playerGo.transform, "HitBoxAir").gameObject;
|
||
BoxCollider2D hitBoxAirCollider = GetOrAddComponent<BoxCollider2D>(hitBoxAirGo);
|
||
hitBoxAirCollider.isTrigger = true;
|
||
HitBox hitBoxAir = GetOrAddComponent<HitBox>(hitBoxAirGo);
|
||
SetLayer(hitBoxAirGo, "PlayerHitBox", report);
|
||
|
||
GameObject playerHurtBoxGo = GetOrCreateChild(playerGo.transform, "HurtBox").gameObject;
|
||
CapsuleCollider2D playerHurtCollider = GetOrAddComponent<CapsuleCollider2D>(playerHurtBoxGo);
|
||
playerHurtCollider.isTrigger = true;
|
||
HurtBox playerHurtBox = GetOrAddComponent<HurtBox>(playerHurtBoxGo);
|
||
SetLayer(playerHurtBoxGo, "PlayerHurtBox", report);
|
||
|
||
GameObject enemyGo = GetOrCreateChild(enemyRoot, "BasicEnemy").gameObject;
|
||
SetLayer(enemyGo, "Enemy", report);
|
||
EnemyBase enemyBase = GetOrAddComponent<EnemyBase>(enemyGo);
|
||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(enemyGo);
|
||
GetOrAddComponent<Rigidbody2D>(enemyGo).bodyType = RigidbodyType2D.Dynamic;
|
||
GetOrAddComponent<CapsuleCollider2D>(enemyGo);
|
||
GetOrAddComponent<Animancer.AnimancerComponent>(enemyGo);
|
||
EnsureVisualSprite(enemyGo.transform, "Visual", new Color(1f, 0.45f, 0.45f, 1f), new Vector2(0.8f, 1.4f), 15);
|
||
AddComponentIfTypeExists(enemyGo, "PathBerserker2d.NavAgent", report);
|
||
|
||
GameObject enemyHurtBoxGo = GetOrCreateChild(enemyGo.transform, "HurtBox").gameObject;
|
||
CapsuleCollider2D enemyHurtCollider = GetOrAddComponent<CapsuleCollider2D>(enemyHurtBoxGo);
|
||
enemyHurtCollider.isTrigger = true;
|
||
HurtBox enemyHurtBox = GetOrAddComponent<HurtBox>(enemyHurtBoxGo);
|
||
SetLayer(enemyHurtBoxGo, "EnemyHurtBox", report);
|
||
|
||
GameObject savePointGo = GetOrCreateChild(savePointRoot, "SavePointObject").gameObject;
|
||
SavePoint savePoint = GetOrAddComponent<SavePoint>(savePointGo);
|
||
BoxCollider2D savePointCollider = GetOrAddComponent<BoxCollider2D>(savePointGo);
|
||
savePointCollider.isTrigger = true;
|
||
SetLayer(savePointGo, "TriggerZone", report);
|
||
EnsureVisualSprite(savePointGo.transform, "Visual", new Color(1f, 0.9f, 0.3f, 0.9f), new Vector2(0.8f, 1.2f), 10);
|
||
AssignAsset(savePoint, "_onSavePointActivated", report, false, "EVT_SavePointActivated");
|
||
AssignAsset(savePoint, "_onFastTravelOpen", report, false, "EVT_FastTravelOpen");
|
||
|
||
GameObject roomCameraGo = GetOrCreateChild(cameraRoot, "RoomCamera").gameObject;
|
||
CinemachineCamera roomCameraComponent = GetOrAddComponent<CinemachineCamera>(roomCameraGo);
|
||
RoomCamera roomCamera = GetOrAddComponent<RoomCamera>(roomCameraGo);
|
||
UnityEngine.Camera roomRenderCamera = roomCameraGo.GetComponent<UnityEngine.Camera>();
|
||
if (roomRenderCamera != null)
|
||
{
|
||
roomRenderCamera.orthographic = false;
|
||
roomRenderCamera.fieldOfView = 60f;
|
||
roomRenderCamera.enabled = false;
|
||
report.Add("RoomCamera 上的 Unity Camera 已禁用(Additive 测试时仅保留 Persistent/Main Camera 渲染)。");
|
||
}
|
||
AudioListener roomAudioListener = roomCameraGo.GetComponent<AudioListener>();
|
||
if (roomAudioListener != null)
|
||
{
|
||
Undo.DestroyObjectImmediate(roomAudioListener);
|
||
report.Add("RoomCamera 上的 AudioListener 已移除(Additive 测试时由 Persistent/Main Camera 保留唯一监听器)。");
|
||
}
|
||
GameObject roomBoundaryGo = GetOrCreateChild(roomCameraGo.transform, "RoomBoundary").gameObject;
|
||
RemoveMissingScripts(roomBoundaryGo, recursive: true, report);
|
||
RoomVisibleArea roomVisibleArea = GetOrAddComponent<RoomVisibleArea>(roomBoundaryGo);
|
||
CinemachineConfiner2D confiner2D = GetOrAddComponent<CinemachineConfiner2D>(roomCameraGo);
|
||
AssignReference(roomCamera, "_visibleArea", roomVisibleArea);
|
||
AssignReferenceByCandidates(roomCameraComponent, playerGo.transform, report, "Follow", "m_Follow");
|
||
AssignReferenceByCandidates(confiner2D, roomVisibleArea.Collider, report, "BoundingShape2D", "m_BoundingShape2D");
|
||
AddScaffoldNote(roomBoundaryGo, "RoomBoundary 已挂 RoomVisibleArea,需要手工编辑 PolygonCollider2D 顶点定义房间边界。", report);
|
||
|
||
if (inputReaderAsset != null)
|
||
{
|
||
AssignReference(playerController, "_inputReader", inputReaderAsset, report);
|
||
AssignReference(inputBuffer, "_inputReader", inputReaderAsset, report);
|
||
}
|
||
else
|
||
{
|
||
report.Add("未找到 InputReader 资产,PlayerController/InputBuffer 的 _inputReader 未能绑定。");
|
||
}
|
||
|
||
AssignReference(playerController, "_movement", playerMovement, report);
|
||
AssignReference(playerController, "_animancer", playerAnimancer, report);
|
||
AssignReference(playerController, "_combat", playerCombat, report);
|
||
AssignReference(playerController, "_formController", formController, report);
|
||
AssignReference(playerController, "_weaponManager", weaponManager, report);
|
||
AssignReference(playerController, "_skillManager", skillManager, report);
|
||
AssignReference(playerController, "_springSystem", springSystem, report);
|
||
AssignReference(playerController, "_parrySystem", parrySystem, report);
|
||
AssignReference(playerController, "_shield", shieldComponent, report);
|
||
AssignReference(playerController, "_statsConfig", playerStatsConfigAsset, report);
|
||
AssignReference(playerController, "_movementConfig", playerMovementConfigAsset, report);
|
||
AssignReference(playerController, "_animConfig", playerAnimationConfigAsset, report);
|
||
AssignReference(playerStats, "_config", playerStatsConfigAsset, report);
|
||
AssignReference(playerMovement, "_config", playerMovementConfigAsset, report);
|
||
AssignReference(playerMovement, "_groundCheck", groundCheckGo.transform, report);
|
||
AssignLayerMask(playerMovement, "_groundLayer", "Ground", report);
|
||
AssignReference(playerCombat, "_weaponManager", weaponManager, report);
|
||
AssignReference(playerCombat, "_hitBoxGround", hitBoxGround, report);
|
||
AssignReference(playerCombat, "_hitBoxUp", hitBoxUp, report);
|
||
AssignReference(playerCombat, "_hitBoxDown", hitBoxDown, report);
|
||
AssignReference(playerCombat, "_hitBoxAir", hitBoxAir, report);
|
||
AssignAsset(playerController, "_onPlayerDied", report, false, "EVT_PlayerDied");
|
||
AssignAsset(playerController, "_onHPChanged", report, false, "EVT_HPChanged");
|
||
AssignReference(playerController, "_stats", playerStats);
|
||
AssignReference(playerController, "_hurtBox", playerHurtBox);
|
||
|
||
AssignAsset(playerStats, "_onHPChanged", report, false, "EVT_HPChanged");
|
||
AssignAsset(playerStats, "_onMaxHPChanged", report, false, "EVT_MaxHPChanged");
|
||
AssignAsset(playerStats, "_onSoulPowerChanged", report, false, "EVT_SoulPowerChanged");
|
||
AssignAsset(playerStats, "_onSpiritPowerChanged", report, false, "EVT_SpiritPowerChanged");
|
||
AssignAsset(playerStats, "_onSpringChargesChanged", report, false, "EVT_SpringChargesChanged");
|
||
AssignAsset(playerStats, "_onGeoChanged", report, false, "EVT_GeoChanged");
|
||
AssignAsset(playerStats, "_onAbilityUnlocked", report, false, "EVT_AbilityUnlocked");
|
||
AssignAsset(playerStats, "_onPlayerDied", report, false, "EVT_PlayerDied");
|
||
|
||
AssignAsset(playerHurtBox, "_onDamageDealt", report, false, "EVT_DamageDealt");
|
||
AssignAsset(playerHurtBox, "_onHitConfirmed", report, false, "EVT_HitConfirmed");
|
||
AssignAsset(enemyHurtBox, "_onDamageDealt", report, false, "EVT_DamageDealt");
|
||
AssignAsset(enemyHurtBox, "_onHitConfirmed", report, false, "EVT_HitConfirmed");
|
||
|
||
AssignReference(enemyBase, "_stats", enemyStats);
|
||
AssignAsset(enemyBase, "_onEnemyDied", report, false, "EVT_EnemyDied");
|
||
AssignAsset(enemyBase, "_statsSO", report, false, "BasicEnemyStats", "EnemyStatsSO");
|
||
|
||
AddScaffoldNote(playerGo, "Player 已生成基础控制节点。PlayerMovement、Combat、Form、Weapon、Skill 等复杂依赖需按实际 prefab/配置继续补齐。", report);
|
||
AddScaffoldNote(enemyGo, "Enemy 已生成基础节点。行为树、导航参数、动画配置和战斗组件仍需手工配置。", report);
|
||
|
||
MarkDirtyAndLog("TestRoom 场景脚手架", root, report);
|
||
}
|
||
|
||
private static void EnsureAudioSources(GameObject audioManagerGo, AudioManager audioManager, List<string> report)
|
||
{
|
||
GameObject bgmAGo = GetOrCreateChild(audioManagerGo.transform, "BGM Source A").gameObject;
|
||
GameObject bgmBGo = GetOrCreateChild(audioManagerGo.transform, "BGM Source B").gameObject;
|
||
GameObject sfxRootGo = GetOrCreateChild(audioManagerGo.transform, "SFX Sources").gameObject;
|
||
|
||
AudioSource bgmA = GetOrAddComponent<AudioSource>(bgmAGo);
|
||
AudioSource bgmB = GetOrAddComponent<AudioSource>(bgmBGo);
|
||
bgmA.playOnAwake = false;
|
||
bgmB.playOnAwake = false;
|
||
bgmA.loop = true;
|
||
bgmB.loop = true;
|
||
|
||
var sfxSources = new AudioSource[6];
|
||
for (int i = 0; i < sfxSources.Length; i++)
|
||
{
|
||
GameObject sfxGo = GetOrCreateChild(sfxRootGo.transform, $"SFX Source {i + 1}").gameObject;
|
||
AudioSource sfxSource = GetOrAddComponent<AudioSource>(sfxGo);
|
||
sfxSource.playOnAwake = false;
|
||
sfxSources[i] = sfxSource;
|
||
}
|
||
|
||
AssignReference(audioManager, "_bgmSourceA", bgmA);
|
||
AssignReference(audioManager, "_bgmSourceB", bgmB);
|
||
AssignArrayReferences(audioManager, "_sfxSources", sfxSources, report);
|
||
report.Add("AudioManager 已生成 2 个 BGM Source 和 6 个 SFX Source,AudioMixer 仍需手工指定。");
|
||
}
|
||
|
||
private static GameObject GetOrCreateRoot(string name)
|
||
{
|
||
Scene scene = SceneManager.GetActiveScene();
|
||
foreach (GameObject rootObject in scene.GetRootGameObjects())
|
||
{
|
||
if (rootObject.name == name)
|
||
return rootObject;
|
||
}
|
||
|
||
GameObject root = new GameObject(name);
|
||
Undo.RegisterCreatedObjectUndo(root, $"Create {name}");
|
||
return root;
|
||
}
|
||
|
||
private static Transform GetOrCreateChild(Transform parent, string name)
|
||
{
|
||
Transform child = parent.Find(name);
|
||
if (child != null)
|
||
return child;
|
||
|
||
GameObject go = new GameObject(name);
|
||
Undo.RegisterCreatedObjectUndo(go, $"Create {name}");
|
||
go.transform.SetParent(parent, false);
|
||
return go.transform;
|
||
}
|
||
|
||
private static T GetOrAddComponent<T>(GameObject go) where T : Component
|
||
{
|
||
T component = go.GetComponent<T>();
|
||
if (component != null)
|
||
return component;
|
||
|
||
return Undo.AddComponent<T>(go);
|
||
}
|
||
|
||
private static Component AddComponentIfTypeExists(GameObject go, string typeName, List<string> report)
|
||
{
|
||
System.Type type = FindType(typeName);
|
||
if (type == null)
|
||
{
|
||
report.Add($"未找到类型 {typeName},已跳过对应组件挂载。");
|
||
return null;
|
||
}
|
||
|
||
Component component = go.GetComponent(type);
|
||
if (component != null)
|
||
return component;
|
||
|
||
return Undo.AddComponent(go, type);
|
||
}
|
||
|
||
private static System.Type FindType(string typeName)
|
||
{
|
||
foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies())
|
||
{
|
||
System.Type type = assembly.GetType(typeName);
|
||
if (type != null)
|
||
return type;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private static GameObject GetOrCreateCanvas(Transform parent, string name, int sortOrder)
|
||
{
|
||
GameObject canvasGo = GetOrCreateChild(parent, name).gameObject;
|
||
Canvas canvas = GetOrAddComponent<Canvas>(canvasGo);
|
||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
canvas.sortingOrder = sortOrder;
|
||
GetOrAddComponent<CanvasScaler>(canvasGo);
|
||
GetOrAddComponent<GraphicRaycaster>(canvasGo);
|
||
return canvasGo;
|
||
}
|
||
|
||
private static void AssignReference(Object target, string propertyName, Object value)
|
||
{
|
||
AssignReference(target, propertyName, value, null);
|
||
}
|
||
|
||
private static void AssignReference(Object target, string propertyName, Object value, List<string> report)
|
||
{
|
||
SerializedObject serializedObject = new SerializedObject(target);
|
||
SerializedProperty property = serializedObject.FindProperty(propertyName);
|
||
if (property == null)
|
||
{
|
||
report?.Add($"{target.GetType().Name}.{propertyName} 字段不存在,未写入引用。");
|
||
return;
|
||
}
|
||
|
||
property.objectReferenceValue = value;
|
||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||
}
|
||
|
||
private static void AssignReferenceByCandidates(Object target, Object value, List<string> report, params string[] candidates)
|
||
{
|
||
if (target == null || candidates == null || candidates.Length == 0)
|
||
return;
|
||
|
||
foreach (string candidate in candidates)
|
||
{
|
||
if (TryAssignSerializedReference(target, candidate, value))
|
||
return;
|
||
|
||
if (TryAssignMemberReference(target, candidate, value))
|
||
return;
|
||
}
|
||
|
||
report?.Add($"{target.GetType().Name} 未找到可写引用字段: {string.Join(" / ", candidates)}");
|
||
}
|
||
|
||
private static bool TryAssignSerializedReference(Object target, string propertyName, Object value)
|
||
{
|
||
SerializedObject serializedObject = new SerializedObject(target);
|
||
SerializedProperty property = serializedObject.FindProperty(propertyName);
|
||
if (property == null || property.propertyType != SerializedPropertyType.ObjectReference)
|
||
return false;
|
||
|
||
property.objectReferenceValue = value;
|
||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||
return true;
|
||
}
|
||
|
||
private static bool TryAssignMemberReference(Object target, string memberName, Object value)
|
||
{
|
||
System.Type targetType = target.GetType();
|
||
|
||
PropertyInfo property = targetType.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
if (property != null && property.CanWrite && typeof(Object).IsAssignableFrom(property.PropertyType))
|
||
{
|
||
if (value == null || property.PropertyType.IsAssignableFrom(value.GetType()))
|
||
{
|
||
property.SetValue(target, value);
|
||
EditorUtility.SetDirty(target);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
FieldInfo field = targetType.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
if (field != null && typeof(Object).IsAssignableFrom(field.FieldType))
|
||
{
|
||
if (value == null || field.FieldType.IsAssignableFrom(value.GetType()))
|
||
{
|
||
field.SetValue(target, value);
|
||
EditorUtility.SetDirty(target);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private static void AssignArrayReferences(Object target, string propertyName, IReadOnlyList<Object> values, List<string> report)
|
||
{
|
||
SerializedObject serializedObject = new SerializedObject(target);
|
||
SerializedProperty property = serializedObject.FindProperty(propertyName);
|
||
if (property == null || !property.isArray)
|
||
{
|
||
report.Add($"{target.GetType().Name}.{propertyName} 不是可写数组字段。");
|
||
return;
|
||
}
|
||
|
||
property.arraySize = values.Count;
|
||
for (int i = 0; i < values.Count; i++)
|
||
property.GetArrayElementAtIndex(i).objectReferenceValue = values[i];
|
||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||
}
|
||
|
||
private static void AssignAsset(Object target, string propertyName, List<string> report, bool required, params string[] candidates)
|
||
{
|
||
Object asset = FindFirstAsset(candidates);
|
||
if (asset == null && required)
|
||
report.Add($"未找到 {target.GetType().Name}.{propertyName} 需要的资产: {string.Join(" / ", candidates)}");
|
||
|
||
AssignReference(target, propertyName, asset, report);
|
||
}
|
||
|
||
private static Object FindFirstAsset(params string[] candidates)
|
||
{
|
||
foreach (string candidate in candidates)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(candidate))
|
||
continue;
|
||
|
||
string[] guids = AssetDatabase.FindAssets(candidate);
|
||
foreach (string guid in guids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
Object asset = AssetDatabase.LoadMainAssetAtPath(path);
|
||
if (asset != null && asset.name == candidate)
|
||
return asset;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private static Object FindFirstAssetByType<T>(params string[] candidates) where T : Object
|
||
{
|
||
foreach (string candidate in candidates)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(candidate))
|
||
continue;
|
||
|
||
string[] guids = AssetDatabase.FindAssets(candidate);
|
||
foreach (string guid in guids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
T asset = AssetDatabase.LoadAssetAtPath<T>(path);
|
||
if (asset != null && asset.name == candidate)
|
||
return asset;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private static Object FindFirstAssetWithExtension(string extension, params string[] candidates)
|
||
{
|
||
foreach (string candidate in candidates)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(candidate))
|
||
continue;
|
||
|
||
string[] guids = AssetDatabase.FindAssets(candidate);
|
||
foreach (string guid in guids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
if (string.IsNullOrEmpty(path) || !path.EndsWith(extension, System.StringComparison.OrdinalIgnoreCase))
|
||
continue;
|
||
|
||
Object asset = AssetDatabase.LoadMainAssetAtPath(path);
|
||
if (asset != null && asset.name == candidate)
|
||
return asset;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private static Object EnsureInputReaderAsset(List<string> report)
|
||
{
|
||
string[] existing = AssetDatabase.FindAssets("t:InputReaderSO");
|
||
if (existing != null && existing.Length > 0)
|
||
{
|
||
string firstPath = AssetDatabase.GUIDToAssetPath(existing[0]);
|
||
Object found = AssetDatabase.LoadMainAssetAtPath(firstPath);
|
||
if (found != null)
|
||
return found;
|
||
}
|
||
|
||
const string inputFolder = "Assets/Data/Input";
|
||
if (!AssetDatabase.IsValidFolder("Assets/Data"))
|
||
AssetDatabase.CreateFolder("Assets", "Data");
|
||
if (!AssetDatabase.IsValidFolder(inputFolder))
|
||
AssetDatabase.CreateFolder("Assets/Data", "Input");
|
||
|
||
const string assetPath = "Assets/Data/Input/InputReader.asset";
|
||
InputReaderSO created = ScriptableObject.CreateInstance<InputReaderSO>();
|
||
AssetDatabase.CreateAsset(created, assetPath);
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
|
||
report?.Add("未找到 InputReaderSO,已自动创建 Assets/Data/Input/InputReader.asset。");
|
||
return created;
|
||
}
|
||
|
||
private static Object EnsurePlayerStatsConfigAsset(List<string> report)
|
||
{
|
||
Object existing = FindFirstAssetByType<PlayerStatsSO>("PlayerStats", "PLY_PlayerStats", "PlayerStatsSO");
|
||
if (existing != null)
|
||
return existing;
|
||
|
||
const string folder = "Assets/Data/Player";
|
||
EnsureFolder(folder);
|
||
const string assetPath = "Assets/Data/Player/PLY_PlayerStats.asset";
|
||
PlayerStatsSO created = ScriptableObject.CreateInstance<PlayerStatsSO>();
|
||
AssetDatabase.CreateAsset(created, assetPath);
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
report?.Add("未找到 PlayerStatsSO,已自动创建 Assets/Data/Player/PLY_PlayerStats.asset。");
|
||
return created;
|
||
}
|
||
|
||
private static Object EnsurePlayerMovementConfigAsset(List<string> report)
|
||
{
|
||
Object existing = FindFirstAssetByType<PlayerMovementConfigSO>("PlayerMovementConfig", "PLY_PlayerMovementConfig", "PlayerMovementConfigSO");
|
||
if (existing != null)
|
||
return existing;
|
||
|
||
const string folder = "Assets/Data/Player";
|
||
EnsureFolder(folder);
|
||
const string assetPath = "Assets/Data/Player/PLY_PlayerMovementConfig.asset";
|
||
PlayerMovementConfigSO created = ScriptableObject.CreateInstance<PlayerMovementConfigSO>();
|
||
AssetDatabase.CreateAsset(created, assetPath);
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
report?.Add("未找到 PlayerMovementConfigSO,已自动创建 Assets/Data/Player/PLY_PlayerMovementConfig.asset。");
|
||
return created;
|
||
}
|
||
|
||
private static Object EnsurePlayerAnimationConfigAsset(List<string> report)
|
||
{
|
||
Object existing = FindFirstAssetByType<PlayerAnimationConfigSO>("PlayerAnimationConfig", "PLY_PlayerAnimationConfig", "PlayerAnimationConfigSO");
|
||
if (existing != null)
|
||
return existing;
|
||
|
||
const string folder = "Assets/Data/Player";
|
||
EnsureFolder(folder);
|
||
const string assetPath = "Assets/Data/Player/PLY_PlayerAnimationConfig.asset";
|
||
PlayerAnimationConfigSO created = ScriptableObject.CreateInstance<PlayerAnimationConfigSO>();
|
||
AssetDatabase.CreateAsset(created, assetPath);
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
report?.Add("未找到 PlayerAnimationConfigSO,已自动创建 Assets/Data/Player/PLY_PlayerAnimationConfig.asset(动画片段需后续手工绑定)。");
|
||
return created;
|
||
}
|
||
|
||
private static void EnsureFolder(string fullPath)
|
||
{
|
||
string[] parts = fullPath.Split('/');
|
||
if (parts.Length == 0 || parts[0] != "Assets")
|
||
return;
|
||
|
||
string current = "Assets";
|
||
for (int i = 1; i < parts.Length; i++)
|
||
{
|
||
string next = current + "/" + parts[i];
|
||
if (!AssetDatabase.IsValidFolder(next))
|
||
AssetDatabase.CreateFolder(current, parts[i]);
|
||
current = next;
|
||
}
|
||
}
|
||
|
||
private static void RemoveMissingScripts(GameObject go, bool recursive, List<string> report)
|
||
{
|
||
if (go == null)
|
||
return;
|
||
|
||
int removed = 0;
|
||
if (!recursive)
|
||
{
|
||
removed = RemoveMissingScriptsOnSingleObject(go);
|
||
}
|
||
else
|
||
{
|
||
var stack = new Stack<Transform>();
|
||
stack.Push(go.transform);
|
||
while (stack.Count > 0)
|
||
{
|
||
Transform current = stack.Pop();
|
||
removed += RemoveMissingScriptsOnSingleObject(current.gameObject);
|
||
foreach (Transform child in current)
|
||
stack.Push(child);
|
||
}
|
||
}
|
||
|
||
if (removed > 0)
|
||
report?.Add($"{go.name}: 已清理 Missing Behaviour x{removed}。");
|
||
}
|
||
|
||
private static int RemoveMissingScriptsOnSingleObject(GameObject go)
|
||
{
|
||
int before = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(go);
|
||
if (before <= 0)
|
||
return 0;
|
||
|
||
Undo.RegisterCompleteObjectUndo(go, "Remove Missing Scripts");
|
||
GameObjectUtility.RemoveMonoBehavioursWithMissingScript(go);
|
||
int after = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(go);
|
||
return before - after;
|
||
}
|
||
|
||
private static void SetLayer(GameObject go, string layerName, List<string> report)
|
||
{
|
||
int layer = LayerMask.NameToLayer(layerName);
|
||
if (layer < 0)
|
||
{
|
||
report.Add($"Layer '{layerName}' 不存在,{go.name} 保持默认 Layer。");
|
||
return;
|
||
}
|
||
|
||
go.layer = layer;
|
||
}
|
||
|
||
private static void AssignLayerMask(Object target, string propertyName, string layerName, List<string> report)
|
||
{
|
||
int layer = LayerMask.NameToLayer(layerName);
|
||
if (layer < 0)
|
||
{
|
||
report?.Add($"Layer '{layerName}' 不存在,{target.GetType().Name}.{propertyName} 无法写入。");
|
||
return;
|
||
}
|
||
|
||
SerializedObject serializedObject = new SerializedObject(target);
|
||
SerializedProperty property = serializedObject.FindProperty(propertyName);
|
||
if (property == null)
|
||
{
|
||
report?.Add($"{target.GetType().Name}.{propertyName} 字段不存在,未写入 LayerMask。");
|
||
return;
|
||
}
|
||
|
||
property.intValue = 1 << layer;
|
||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||
}
|
||
|
||
private static void EnsureVisualSprite(Transform parent, string childName, Color color, Vector2 size, int sortingOrder)
|
||
{
|
||
Transform visualTransform = GetOrCreateChild(parent, childName);
|
||
SpriteRenderer renderer = GetOrAddComponent<SpriteRenderer>(visualTransform.gameObject);
|
||
renderer.sprite = GetBuiltinDefaultSprite();
|
||
renderer.color = color;
|
||
renderer.drawMode = SpriteDrawMode.Sliced;
|
||
renderer.size = size;
|
||
renderer.sortingOrder = sortingOrder;
|
||
visualTransform.localPosition = Vector3.zero;
|
||
visualTransform.localRotation = Quaternion.identity;
|
||
visualTransform.localScale = Vector3.one;
|
||
}
|
||
|
||
private static Sprite GetBuiltinDefaultSprite()
|
||
=> AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UISprite.psd");
|
||
|
||
private static void AddScaffoldNote(GameObject go, string message)
|
||
{
|
||
AddScaffoldNote(go, message, null);
|
||
}
|
||
|
||
private static void DisableRenderCamerasUnderRoot(Transform root, List<string> report)
|
||
{
|
||
if (root == null)
|
||
return;
|
||
|
||
UnityEngine.Camera[] cameras = root.GetComponentsInChildren<UnityEngine.Camera>(true);
|
||
foreach (UnityEngine.Camera camera in cameras)
|
||
{
|
||
if (camera == null)
|
||
continue;
|
||
|
||
if (camera.enabled)
|
||
{
|
||
camera.enabled = false;
|
||
report?.Add($"已禁用 TestRoom 内渲染相机: {camera.gameObject.name}");
|
||
}
|
||
}
|
||
}
|
||
|
||
private static void AddScaffoldNote(GameObject go, string message, List<string> report)
|
||
{
|
||
// 注意:不再添加 MonoBehaviour 组件,避免 Editor 程序集组件在 Play 模式下出现 Missing Script
|
||
report?.Add($"{go.name}: {message}");
|
||
Debug.Log($"[SceneScaffold] {go.name}: {message}");
|
||
}
|
||
|
||
private static void MarkDirtyAndLog(string scaffoldName, GameObject root, List<string> report)
|
||
{
|
||
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
||
Selection.activeGameObject = root;
|
||
|
||
if (report.Count == 0)
|
||
{
|
||
Debug.Log($"[SceneScaffoldTools] {scaffoldName} 完成。所有可自动补齐的对象与引用均已生成。", root);
|
||
return;
|
||
}
|
||
|
||
Debug.LogWarning($"[SceneScaffoldTools] {scaffoldName} 完成,但仍有 {report.Count} 项需要手工确认:\n- {string.Join("\n- ", report)}", root);
|
||
}
|
||
}
|
||
|
||
} |