Files
FreewayGamesTest/Assets/Synaptic AI Pro/Editor/NexusExecutor.cs
T

51045 lines
2.1 MiB
Plaintext

using System;
using Random = UnityEngine.Random;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using SynapticAIPro;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Rendering;
#endif
using System.IO;
using Newtonsoft.Json;
using UnityEngine.Profiling;
using SynapticPro.GOAP;
using SynapticPro.BehaviorTree;
using Synaptic.Editor;
#if ENABLE_INPUT_SYSTEM && INPUT_SYSTEM_PACKAGE
using UnityEngine.InputSystem;
#endif
// VFX Graph Support - requires com.unity.visualeffectgraph package
#if VFX_GRAPH_PACKAGE
using UnityEngine.VFX;
using UnityEditor.VFX;
#endif
namespace SynapticPro
{
public partial class NexusUnityExecutor
{
private GameObject lastCreatedObject;
private List<GameObject> createdObjects = new List<GameObject>();
// Cached rendering pipeline detection
private static string _cachedPipeline = null;
// Scene snapshot cache for incremental updates
private static Dictionary<string, SceneSnapshot> sceneSnapshots = new Dictionary<string, SceneSnapshot>();
private class SceneSnapshot
{
public DateTime timestamp;
public HashSet<int> gameObjectIds;
public Dictionary<int, int> gameObjectHashes;
}
// Log buffer for real-time log collection
private static List<LogEntry> logBuffer = new List<LogEntry>();
private static bool isLogCallbackRegistered = false;
private static readonly int maxLogBufferSize = 1000;
// File read tracking - files must be read before editing (like Claude Code)
private static HashSet<string> readFiles = new HashSet<string>();
private static Dictionary<string, DateTime> readFileTimestamps = new Dictionary<string, DateTime>();
private static readonly TimeSpan readFileCacheExpiry = TimeSpan.FromMinutes(30); // Cache expires after 30 minutes
// Screenshot capture state (using EditorPrefs for persistence across domain reload)
private static string PendingScreenshotPath
{
get => EditorPrefs.GetString("NexusExecutor_PendingScreenshotPath", null);
set => EditorPrefs.SetString("NexusExecutor_PendingScreenshotPath", value ?? "");
}
private static bool IsCapturingScreenshot
{
get => EditorPrefs.GetBool("NexusExecutor_IsCapturingScreenshot", false);
set => EditorPrefs.SetBool("NexusExecutor_IsCapturingScreenshot", value);
}
private static bool WasPlayingBeforeCapture
{
get => EditorPrefs.GetBool("NexusExecutor_WasPlayingBeforeCapture", false);
set => EditorPrefs.SetBool("NexusExecutor_WasPlayingBeforeCapture", value);
}
private static string ScreenshotCaptureResult
{
get => EditorPrefs.GetString("NexusExecutor_ScreenshotCaptureResult", null);
set => EditorPrefs.SetString("NexusExecutor_ScreenshotCaptureResult", value ?? "");
}
private static string PendingRegionParams
{
get => EditorPrefs.GetString("NexusExecutor_PendingRegionParams", null);
set => EditorPrefs.SetString("NexusExecutor_PendingRegionParams", value ?? "");
}
private static bool IsCapturingRegion
{
get => EditorPrefs.GetBool("NexusExecutor_IsCapturingRegion", false);
set => EditorPrefs.SetBool("NexusExecutor_IsCapturingRegion", value);
}
private struct LogEntry
{
public string condition;
public string stackTrace;
public LogType type;
public DateTime timestamp;
}
static NexusUnityExecutor()
{
// Register log callback
if (!isLogCallbackRegistered)
{
Application.logMessageReceived += OnLogMessageReceived;
isLogCallbackRegistered = true;
SynLog.Info("[NexusConsole] Log callback registered for real-time log collection");
}
#if UNITY_EDITOR
// Re-register screenshot callback after domain reload
EditorApplication.playModeStateChanged -= OnPlayModeStateChangedForScreenshot;
EditorApplication.playModeStateChanged += OnPlayModeStateChangedForScreenshot;
if (IsCapturingScreenshot || IsCapturingRegion)
{
var captureType = IsCapturingRegion ? "Region" : "Screenshot";
var capturePath = IsCapturingRegion ? "pending" : PendingScreenshotPath;
SynLog.Info($"[NexusExecutor] {captureType} capture state restored after domain reload. Path: {capturePath}");
SynLog.Info($"[NexusExecutor] Current play mode state: {(EditorApplication.isPlaying ? "Playing" : "Not Playing")}");
// If we're already in play mode, we missed the EnteredPlayMode event
// Start 120-frame wait for rendering to stabilize
if (EditorApplication.isPlaying)
{
SynLog.Info($"[NexusExecutor] Already in Play mode, starting 120-frame wait (~2 seconds) for {captureType} capture...");
screenshotFrameCounter = 0;
screenshotUpdateHandler = ScreenshotFrameUpdate;
EditorApplication.update += screenshotUpdateHandler;
}
}
#endif
}
private static void OnLogMessageReceived(string condition, string stackTrace, LogType type)
{
var entry = new LogEntry
{
condition = condition,
stackTrace = stackTrace,
type = type,
timestamp = DateTime.Now
};
logBuffer.Add(entry);
// Buffer size limit
if (logBuffer.Count > maxLogBufferSize)
{
logBuffer.RemoveRange(0, logBuffer.Count - maxLogBufferSize);
}
}
public async Task<string> ExecuteOperation(NexusUnityOperation operation)
{
try
{
switch (operation.type.ToUpper())
{
case "CREATE_GAMEOBJECT":
return CreateGameObject(operation.parameters);
case "UPDATE_GAMEOBJECT":
return UpdateGameObject(operation.parameters);
case "DELETE_GAMEOBJECT":
return DeleteGameObject(operation.parameters);
case "INSTANTIATE_PREFAB":
return InstantiatePrefab(operation.parameters);
case "SET_TRANSFORM":
return SetTransform(operation.parameters);
case "ADD_COMPONENT":
return AddComponent(operation.parameters);
case "UPDATE_COMPONENT":
return UpdateComponent(operation.parameters);
case "SET_PROPERTY":
return SetProperty(operation.parameters);
case "CREATE_UI":
return CreateUI(operation.parameters);
case "SETUP_UI_CANVAS":
return SetupUICanvas(operation.parameters);
case "SET_UI_ANCHOR":
return SetUIAnchor(operation.parameters);
case "CREATE_SCRIPT":
return await CreateScript(operation);
case "MODIFY_SCRIPT":
SynLog.Info($"[NexusExecutor] Executing MODIFY_SCRIPT with parameters: {JsonConvert.SerializeObject(operation.parameters)}");
return ModifyScript(operation.parameters);
case "EDIT_SCRIPT_LINE":
SynLog.Info($"[NexusExecutor] Executing EDIT_SCRIPT_LINE with parameters: {JsonConvert.SerializeObject(operation.parameters)}");
return EditScriptLine(operation.parameters);
case "ADD_SCRIPT_METHOD":
SynLog.Info($"[NexusExecutor] Executing ADD_SCRIPT_METHOD with parameters: {JsonConvert.SerializeObject(operation.parameters)}");
return AddScriptMethod(operation.parameters);
case "UPDATE_SCRIPT_VARIABLE":
SynLog.Info($"[NexusExecutor] Executing UPDATE_SCRIPT_VARIABLE with parameters: {JsonConvert.SerializeObject(operation.parameters)}");
return UpdateScriptVariable(operation.parameters);
case "READ_SCRIPT":
return ReadScript(operation.parameters);
case "GREP_SCRIPTS":
return GrepScripts(operation.parameters);
case "READ_SCRIPT_RANGE":
return ReadScriptRange(operation.parameters);
case "SEARCH_CODE":
return SearchCode(operation.parameters);
case "LIST_SCRIPT_FILES":
return ListScriptFiles(operation.parameters);
case "ANALYZE_SCRIPT":
return AnalyzeScript(operation.parameters);
case "ANALYZE_PERFORMANCE":
return AnalyzePerformance(operation.parameters);
case "CHECK_BEST_PRACTICES":
return CheckBestPractices(operation.parameters);
case "MONITOR_RUNTIME_ERRORS":
return MonitorRuntimeErrors(operation.parameters);
case "AUTO_ATTACH_UI":
return AutoAttachUI(operation.parameters);
case "CREATE_PREFAB":
return CreatePrefab(operation.parameters);
case "SETUP_PHYSICS":
return SetupPhysics(operation.parameters);
case "CREATE_MATERIAL":
return CreateMaterial(operation.parameters);
case "SETUP_CAMERA":
return SetupCamera(operation.parameters);
// Cinemachine Operations
case "CREATE_VIRTUAL_CAMERA":
return NexusCinemachineHelper.CreateVirtualCamera(operation.parameters);
case "CREATE_FREELOOK_CAMERA":
return NexusCinemachineHelper.CreateFreeLookCamera(operation.parameters);
case "SETUP_CINEMACHINE_BRAIN":
return NexusCinemachineHelper.SetupCinemachineBrain(operation.parameters);
case "UPDATE_VIRTUAL_CAMERA":
return NexusCinemachineHelper.UpdateVirtualCamera(operation.parameters);
case "CREATE_DOLLY_TRACK":
return NexusCinemachineHelper.CreateDollyTrack(operation.parameters);
case "ADD_COLLIDER_EXTENSION":
return NexusCinemachineHelper.AddColliderExtension(operation.parameters);
case "ADD_CONFINER_EXTENSION":
return NexusCinemachineHelper.AddConfinerExtension(operation.parameters);
case "CREATE_STATE_DRIVEN_CAMERA":
return NexusCinemachineHelper.CreateStateDrivenCamera(operation.parameters);
case "CREATE_CLEAR_SHOT_CAMERA":
return NexusCinemachineHelper.CreateClearShotCamera(operation.parameters);
case "CREATE_IMPULSE_SOURCE":
return NexusCinemachineHelper.CreateImpulseSource(operation.parameters);
case "ADD_IMPULSE_LISTENER":
return NexusCinemachineHelper.AddImpulseListener(operation.parameters);
case "CREATE_BLEND_LIST_CAMERA":
return NexusCinemachineHelper.CreateBlendListCamera(operation.parameters);
case "CREATE_TARGET_GROUP":
return NexusCinemachineHelper.CreateTargetGroup(operation.parameters);
case "ADD_TARGET_TO_GROUP":
return NexusCinemachineHelper.AddTargetToGroup(operation.parameters);
case "SET_CAMERA_PRIORITY":
return NexusCinemachineHelper.SetCameraPriority(operation.parameters);
case "SET_CAMERA_ENABLED":
return NexusCinemachineHelper.SetCameraEnabled(operation.parameters);
case "CREATE_MIXING_CAMERA":
return NexusCinemachineHelper.CreateMixingCamera(operation.parameters);
case "UPDATE_CAMERA_TARGET":
return NexusCinemachineHelper.UpdateCameraTarget(operation.parameters);
case "UPDATE_BRAIN_BLEND_SETTINGS":
return NexusCinemachineHelper.UpdateBrainBlendSettings(operation.parameters);
case "GET_ACTIVE_CAMERA_INFO":
return NexusCinemachineHelper.GetActiveCameraInfo(operation.parameters);
case "CREATE_PARTICLE_SYSTEM":
return CreateParticleSystem(operation.parameters);
case "SETUP_MATERIAL":
return SetupMaterial(operation.parameters);
case "SETUP_NAVMESH":
return SetupNavMesh(operation.parameters);
case "CREATE_AUDIO_MIXER":
return CreateAudioMixer(operation.parameters);
// New Audio tools
case "CREATE_AUDIO_SOURCE":
return CreateAudioSource(operation.parameters);
case "SETUP_3D_AUDIO":
return Setup3DAudio(operation.parameters);
case "CREATE_AUDIO_CLIP":
return CreateAudioClip(operation.parameters);
case "SETUP_AUDIO_EFFECTS":
return SetupAudioEffects(operation.parameters);
case "CREATE_REVERB_ZONES":
return CreateReverbZones(operation.parameters);
case "SETUP_AUDIO_OCCLUSION":
return SetupAudioOcclusion(operation.parameters);
case "CREATE_ADAPTIVE_MUSIC":
return CreateAdaptiveMusic(operation.parameters);
case "SETUP_AUDIO_TRIGGERS":
return SetupAudioTriggers(operation.parameters);
case "CREATE_SOUND_POOLS":
return CreateSoundPools(operation.parameters);
case "CREATE_AUDIO_MIXING":
return CreateAudioMixing(operation.parameters);
case "SETUP_SPATIAL_AUDIO":
return SetupSpatialAudio(operation.parameters);
case "CREATE_AUDIO_VISUALIZATION":
return CreateAudioVisualization(operation.parameters);
// ===== Advanced Input Tools =====
case "SETUP_CUSTOM_INPUT":
return SetupCustomInput(operation.parameters);
case "CREATE_GESTURE_RECOGNITION":
return CreateGestureRecognition(operation.parameters);
case "SETUP_HAPTIC_FEEDBACK":
return SetupHapticFeedback(operation.parameters);
case "CREATE_INPUT_VALIDATION":
return CreateInputValidation(operation.parameters);
case "SETUP_ACCESSIBILITY_INPUT":
return SetupAccessibilityInput(operation.parameters);
case "CREATE_INPUT_RECORDING":
return CreateInputRecording(operation.parameters);
// ===== Touch & Gesture Tools =====
case "SETUP_MULTITOUCH":
return SetupMultitouch(operation.parameters);
case "CREATE_PINCH_ZOOM":
return CreatePinchZoom(operation.parameters);
case "SETUP_SWIPE_DETECTION":
return SetupSwipeDetection(operation.parameters);
case "CREATE_DRAG_DROP":
return CreateDragDrop(operation.parameters);
case "SETUP_TOUCH_EFFECTS":
return SetupTouchEffects(operation.parameters);
// ===== Visual Enhancement Tools =====
case "CREATE_PARTICLE_PRESET":
return CreateParticlePreset(operation.parameters);
case "CREATE_ADVANCED_MATERIAL":
return CreateAdvancedMaterial(operation.parameters);
case "SETUP_LIGHTING_PRESET":
return SetupLightingPreset(operation.parameters);
case "CREATE_VISUAL_EFFECT":
return CreateVisualEffect(operation.parameters);
case "SETUP_REFLECTION_PROBE":
return SetupReflectionProbe(operation.parameters);
case "CREATE_LIGHT_PROBE_GROUP":
return CreateLightProbeGroup(operation.parameters);
case "SETUP_VOLUMETRIC_FOG":
return SetupVolumetricFog(operation.parameters);
case "CREATE_DECAL":
return CreateDecal(operation.parameters);
case "CREATE_CAUSTICS":
return CreateCausticsProjector(operation.parameters);
case "CREATE_WATER":
return CreateWaterSystem(operation.parameters);
case "SETUP_COLOR_GRADING":
return SetupColorGrading(operation.parameters);
case "CREATE_LENS_FLARE":
return CreateLensFlare(operation.parameters);
// Screen Effects
case "CREATE_SCREEN_SHAKE":
return CreateScreenShake(operation.parameters);
case "CREATE_SCREEN_FADE":
return CreateScreenFade(operation.parameters);
case "CREATE_VIGNETTE_EFFECT":
return CreateVignetteEffect(operation.parameters);
case "CREATE_CHROMATIC_ABERRATION":
return CreateChromaticAberration(operation.parameters);
// Shader Tools
case "CREATE_SHADER_PROPERTY_ANIMATOR":
return CreateShaderPropertyAnimator(operation.parameters);
case "CREATE_MATERIAL_PROPERTY_BLOCK":
return CreateMaterialPropertyBlock(operation.parameters);
case "ANIMATE_SHADER_TEXTURE":
return AnimateShaderTexture(operation.parameters);
case "CREATE_SHADER_GRADIENT":
return CreateShaderGradient(operation.parameters);
// Camera Effects - Restored with Auto-Generated Shaders
case "CREATE_BLOOM":
return CreateBloomEffect(operation.parameters);
case "CREATE_FILM_GRAIN":
return CreateFilmGrainEffect(operation.parameters);
case "CREATE_MOTION_BLUR":
return CreateMotionBlurEffect(operation.parameters);
case "CREATE_DEPTH_OF_FIELD":
return CreateDepthOfFieldEffect(operation.parameters);
case "CREATE_LENS_DISTORTION":
return CreateLensDistortionEffect(operation.parameters);
// ===== Phase 4: Advanced Rendering & VFX Systems =====
// Pipeline Settings
case "SETUP_URP_SETTINGS":
return SetupURPSettings(operation.parameters);
case "SETUP_HDRP_SETTINGS":
return SetupHDRPSettings(operation.parameters);
case "SETUP_POST_PROCESSING":
return SetupPostProcessing(operation.parameters);
// VFX Systems
case "CREATE_VFX_GRAPH":
return CreateVFXGraph(operation.parameters);
case "CREATE_SHADER_GRAPH":
return CreateShaderGraph(operation.parameters);
// External Shader Editing
case "READ_SHADER":
return ReadShader(operation.parameters);
case "MODIFY_SHADER":
return ModifyShader(operation.parameters);
case "ANALYZE_SHADER":
return AnalyzeShader(operation.parameters);
case "READ_SHADER_GRAPH":
return ReadShaderGraph(operation.parameters);
case "SETUP_LIGHTING_SCENARIOS":
return SetupLightingScenarios(operation.parameters);
// Weather System
case "CREATE_RAIN_EFFECT":
return CreateRainEffect(operation.parameters);
case "CREATE_SNOW_EFFECT":
return CreateSnowEffect(operation.parameters);
case "CREATE_WIND_EFFECT":
return CreateWindEffect(operation.parameters);
case "CREATE_LIGHTNING_EFFECT":
return CreateLightningEffect(operation.parameters);
case "CREATE_THUNDERSTORM":
return CreateThunderstorm(operation.parameters);
// Unified Weather System
case "CREATE_WEATHER_SYSTEM":
return CreateWeatherSystem(operation.parameters);
case "SET_WEATHER_PRESET":
return SetWeatherPreset(operation.parameters);
// Time of Day System
case "CREATE_TIME_OF_DAY":
return CreateTimeOfDay(operation.parameters);
case "SET_TIME_OF_DAY":
return SetTimeOfDay(operation.parameters);
case "CREATE_DAY_NIGHT_PRESET":
return CreateDayNightPreset(operation.parameters);
case "CREATE_SKYBOX_BLEND":
return CreateSkyboxBlend(operation.parameters);
case "CREATE_SKYBOX_FROM_IMAGE":
return CreateSkyboxFromImage(operation.parameters);
case "CREATE_TIME_EVENT":
return CreateTimeEvent(operation.parameters);
case "UNDO":
case "UNDO_OPERATION":
return UndoOperation();
case "REDO":
case "REDO_OPERATION":
return RedoOperation();
case "GET_HISTORY":
case "GET_OPERATION_HISTORY":
return GetOperationHistory();
case "CREATE_CHECKPOINT":
return CreateCheckpoint(operation.parameters);
case "RESTORE_CHECKPOINT":
return RestoreCheckpoint(operation.parameters);
// Real-time event monitoring
case "MONITOR_PLAY_STATE":
return StartPlayStateMonitoring(operation.parameters);
case "MONITOR_FILE_CHANGES":
return StartFileChangeMonitoring(operation.parameters);
case "MONITOR_COMPILE":
return StartCompileMonitoring(operation.parameters);
case "SUBSCRIBE_EVENTS":
return SubscribeToEvents(operation.parameters);
case "GET_EVENTS":
return GetRecentEvents(operation.parameters);
case "GET_MONITORING_STATUS":
return GetMonitoringStatus();
// Project settings
case "GET_BUILD_SETTINGS":
return NexusProjectSettings.GetBuildSettings();
case "GET_PLAYER_SETTINGS":
return NexusProjectSettings.GetPlayerSettings();
case "GET_QUALITY_SETTINGS":
return NexusProjectSettings.GetQualitySettings();
case "GET_INPUT_SETTINGS":
return NexusProjectSettings.GetInputSettings();
case "GET_PHYSICS_SETTINGS":
return NexusProjectSettings.GetPhysicsSettings();
case "GET_PROJECT_SUMMARY":
var projectSummary = NexusProjectSettings.GetProjectSettingsSummary();
SynLog.Info($"[NexusExecutor] GET_PROJECT_SUMMARY response length: {projectSummary.Length}");
return projectSummary;
case "BATCH_CREATE":
return await BatchCreate(operation);
// === Real-time execution state monitoring ===
case "GET_RUNTIME_STATUS":
return NexusRuntimeMonitor.GetRuntimeStatus(operation.parameters);
case "GET_PERFORMANCE_METRICS":
return NexusRuntimeMonitor.GetPerformanceMetrics(operation.parameters);
case "GET_MEMORY_USAGE":
return NexusRuntimeMonitor.GetMemoryUsage(operation.parameters);
case "GET_ERROR_STATUS":
return NexusRuntimeMonitor.GetErrorStatus(operation.parameters);
case "GET_BUILD_STATUS":
return NexusRuntimeMonitor.GetBuildStatus(operation.parameters);
// === Detailed asset information retrieval ===
case "GET_TEXTURE_DETAILS":
return NexusAssetAnalyzer.GetTextureDetails(operation.parameters);
case "GET_MESH_DETAILS":
return NexusAssetAnalyzer.GetMeshDetails(operation.parameters);
case "GET_AUDIO_DETAILS":
return NexusAssetAnalyzer.GetAudioDetails(operation.parameters);
case "GET_ANIMATION_DETAILS":
return NexusAssetAnalyzer.GetAnimationDetails(operation.parameters);
case "GET_MATERIAL_DETAILS":
return NexusAssetAnalyzer.GetMaterialDetails(operation.parameters);
case "GET_ASSET_FILE_INFO":
return NexusAssetAnalyzer.GetAssetFileInfo(operation.parameters);
case "ANALYZE_ASSET_USAGE":
return NexusAssetAnalyzer.AnalyzeAssetUsage(operation.parameters);
case "GET_ASSET_IMPORT_SETTINGS":
return GetAssetImportSettings(operation.parameters);
case "PLACE_OBJECTS":
return PlaceObjects(operation.parameters);
case "GET_GAMEOBJECT_DETAILS":
return GetGameObjectDetails(operation.parameters);
case "GET_SCENE_INFO":
return GetSceneInfo(operation.parameters);
case "GET_SCENE_SUMMARY":
return GetSceneSummary(operation.parameters);
case "GET_GAMEOBJECTS_LIST":
return GetGameObjectsList(operation.parameters);
case "GET_GAMEOBJECT_DETAIL":
return GetGameObjectDetail(operation.parameters);
case "GET_SCENE_CHANGES_SINCE":
return GetSceneChangesSince(operation.parameters);
case "CAPTURE_GAME_VIEW":
return CaptureGameView(operation.parameters);
case "CAPTURE_SCENE_VIEW":
return CaptureSceneView(operation.parameters);
case "CAPTURE_REGION":
return CaptureRegion(operation.parameters);
case "CAPTURE_GRID":
return CaptureGrid(operation.parameters);
case "CAPTURE_UI_ELEMENT":
return CaptureUIElement(operation.parameters);
case "GET_SCREENSHOT_RESULT":
return GetScreenshotResult();
case "FORCE_REFRESH_ASSETS":
return ForceRefreshAssets(operation.parameters);
case "INVOKE_CONTEXT_MENU":
return InvokeContextMenu(operation.parameters);
case "EXECUTE_MENU_ITEM":
return ExecuteMenuItem(operation.parameters);
case "RUN_CSHARP":
return NexusCSharpEval.Run(operation.parameters);
case "GET_INSPECTOR_INFO":
return GetInspectorInfo(operation.parameters);
case "GET_SELECTED_OBJECT_INFO":
return GetSelectedObjectInfo(operation.parameters);
case "GET_COMPONENT_DETAILS":
return GetComponentDetails(operation.parameters);
case "CREATE_TERRAIN":
return CreateTerrain(operation.parameters);
case "MODIFY_TERRAIN":
return ModifyTerrain(operation.parameters);
case "GET_CAMERA_INFO":
return GetCameraInfo(operation.parameters);
case "GET_TERRAIN_INFO":
return GetTerrainInfo(operation.parameters);
case "GET_LIGHTING_INFO":
return GetLightingInfo(operation.parameters);
case "GET_MATERIAL_INFO":
return GetMaterialInfo(operation.parameters);
case "GET_UI_INFO":
return GetUIInfo(operation.parameters);
case "GET_PHYSICS_INFO":
return GetPhysicsInfo(operation.parameters);
case "LIST_ASSETS":
return ListAssets(operation.parameters);
case "CHECK_FOLDER":
return CheckFolder(operation.parameters);
case "CREATE_FOLDER":
return CreateFolder(operation.parameters);
case "LIST_FOLDERS":
return ListFolders(operation.parameters);
case "DUPLICATE_GAMEOBJECT":
return DuplicateGameObject(operation.parameters);
case "FIND_BY_COMPONENT":
return FindGameObjectsByComponent(operation.parameters);
case "CLEANUP_EMPTY_OBJECTS":
return CleanupEmptyObjects(operation.parameters);
case "GET_PROJECT_STATS":
return GetProjectStats(operation.parameters);
case "MANAGE_PACKAGE":
return ManagePackage(operation.parameters);
case "MANAGE_SCENE":
return ManageScene(operation.parameters);
case "LOAD_SCENE":
return LoadScene(operation.parameters);
case "UNLOAD_SCENE":
return UnloadScene(operation.parameters);
case "SET_ACTIVE_SCENE":
return SetActiveScene(operation.parameters);
case "LIST_ALL_SCENES":
return ListAllScenes(operation.parameters);
case "ADD_SCENE_TO_BUILD":
return AddSceneToBuild(operation.parameters);
case "SEARCH_PREFABS_BY_COMPONENT":
return SearchPrefabsByComponent(operation.parameters);
case "FIND_MATERIAL_USAGE":
return FindMaterialUsage(operation.parameters);
case "FIND_TEXTURE_USAGE":
return FindTextureUsage(operation.parameters);
case "GET_ASSET_DEPENDENCIES":
return GetAssetDependencies(operation.parameters);
case "FIND_MISSING_REFERENCES":
return FindMissingReferences(operation.parameters);
case "CREATE_ANIMATION":
return CreateAnimation(operation.parameters);
case "SETUP_LIGHTING":
return SetupLighting(operation.parameters);
case "CONSOLE_OPERATION":
return ConsoleOperation(operation.parameters);
case "ANALYZE_CONSOLE_LOGS":
return AnalyzeConsoleLogs(operation.parameters);
// === Debug & Test tools ===
case "CONTROL_GAME_SPEED":
return ControlGameSpeed(operation.parameters);
case "PROFILE_PERFORMANCE":
return ProfilePerformance(operation.parameters);
case "DEBUG_DRAW":
return DebugDraw(operation.parameters);
case "RUN_UNITY_TESTS":
return RunUnityTests(operation.parameters);
case "MANAGE_BREAKPOINTS":
return ManageBreakpoints(operation.parameters);
// === Animation tools ===
case "CREATE_ANIMATOR_CONTROLLER":
return CreateAnimatorController(operation.parameters);
case "ADD_ANIMATION_STATE":
return AddAnimationState(operation.parameters);
case "CREATE_ANIMATION_CLIP":
return CreateAnimationClip(operation.parameters);
case "SETUP_BLEND_TREE":
return SetupBlendTree(operation.parameters);
case "ADD_ANIMATION_TRANSITION":
return AddAnimationTransition(operation.parameters);
case "SETUP_ANIMATION_LAYER":
return SetupAnimationLayer(operation.parameters);
case "CREATE_ANIMATION_EVENT":
return CreateAnimationEvent(operation.parameters);
case "SETUP_AVATAR":
return SetupAvatar(operation.parameters);
// === Advanced Animation Management ===
case "IMPORT_MIXAMO_ANIMATION":
return NexusAnimationHelper.ImportMixamoAnimation(operation.parameters);
case "ORGANIZE_ANIMATION_ASSETS":
return NexusAnimationHelper.OrganizeAnimationAssets(operation.parameters);
case "SETUP_CHARACTER_IK":
return NexusAnimationHelper.SetupCharacterIK(operation.parameters);
case "CREATE_ANIMATION_LAYER_MASK":
return NexusAnimationHelper.CreateAnimationLayerMask(operation.parameters);
case "SETUP_ADVANCED_BLEND_TREE":
return NexusAnimationHelper.SetupAdvancedBlendTree(operation.parameters);
case "RETARGET_ANIMATION":
return NexusAnimationHelper.RetargetAnimation(operation.parameters);
case "CREATE_TRANSITION_PRESET":
return NexusAnimationHelper.CreateTransitionPreset(operation.parameters);
case "ANALYZE_ANIMATION_PERFORMANCE":
return NexusAnimationHelper.AnalyzeAnimationPerformance(operation.parameters);
case "CREATE_TIMELINE":
return CreateTimeline(operation.parameters);
case "BAKE_ANIMATION":
return BakeAnimation(operation.parameters);
// === UI detailed construction tools ===
case "SETUP_UI_ANCHORS":
return SetupUIAnchors(operation.parameters);
case "CREATE_RESPONSIVE_UI":
return CreateResponsiveUI(operation.parameters);
case "SETUP_UI_ANIMATION":
return SetupUIAnimation(operation.parameters);
case "CREATE_UI_GRID":
return CreateUIGrid(operation.parameters);
case "SETUP_SCROLL_VIEW":
return SetupScrollView(operation.parameters);
case "CREATE_UI_NOTIFICATION":
return CreateUINotification(operation.parameters);
case "SETUP_UI_NAVIGATION":
return SetupUINavigation(operation.parameters);
case "CREATE_UI_DIALOG":
return CreateUIDialog(operation.parameters);
case "OPTIMIZE_UI_CANVAS":
return OptimizeUICanvas(operation.parameters);
case "SETUP_SAFE_AREA":
return SetupSafeArea(operation.parameters);
case "APPLY_UI_THEME":
return ApplyUITheme(ConvertParameters(operation.parameters));
case "SET_UI_COLORS":
return SetUIColors(ConvertParameters(operation.parameters));
case "STYLE_UI_ELEMENTS":
return StyleUIElements(ConvertParameters(operation.parameters));
case "ADD_UI_EFFECTS":
return AddUIEffects(ConvertParameters(operation.parameters));
case "SET_TYPOGRAPHY":
return SetTypography(ConvertParameters(operation.parameters));
case "EXECUTE_BATCH":
return await ExecuteBatch(operation.parameters);
case "BATCH_RENAME":
return BatchRename(operation.parameters);
case "BATCH_IMPORT_SETTINGS":
return BatchImportSettings(operation.parameters);
case "BATCH_PREFAB_UPDATE":
return BatchPrefabUpdate(operation.parameters);
case "BATCH_MATERIAL_APPLY":
return BatchMaterialApply(operation.parameters);
case "BATCH_PREFAB_CREATE":
return BatchPrefabCreate(operation.parameters);
case "UNITY_SEARCH":
case "SEARCH_OBJECTS":
return SearchObjects(operation.parameters);
case "SEND_CHAT_RESPONSE":
return SendChatResponse(operation.parameters);
case "CHECK_MESSAGES":
return CheckMessages(operation.parameters);
case "SEND_REALTIME_RESPONSE":
return SendRealtimeResponse(operation.parameters);
case "CHECK_ACTIVE_SESSIONS":
return CheckActiveSessions(operation.parameters);
case "GROUP_GAMEOBJECTS":
return GroupGameObjects(operation.parameters);
case "RENAME_ASSET":
return RenameAsset(operation.parameters);
case "MOVE_ASSET":
return MoveAsset(operation.parameters);
case "DELETE_ASSET":
return DeleteAsset(operation.parameters);
case "PAUSE_SCENE":
return PauseScene(operation.parameters);
case "OPTIMIZE_TEXTURES_BATCH":
return OptimizeTexturesBatch(operation.parameters);
case "ANALYZE_DRAW_CALLS":
return AnalyzeDrawCalls(operation.parameters);
case "CREATE_PROJECT_SNAPSHOT":
return CreateProjectSnapshot(operation.parameters);
case "ANALYZE_DEPENDENCIES":
return AnalyzeDependencies(operation.parameters);
case "EXPORT_PROJECT_STRUCTURE":
return ExportProjectStructure(operation.parameters);
case "EXPORT_PACKAGE":
return ExportPackage(operation.parameters);
case "VALIDATE_NAMING_CONVENTIONS":
return ValidateNamingConventions(operation.parameters);
case "EXTRACT_ALL_TEXT":
return ExtractAllText(operation.parameters);
case "FIND_UNUSED_ASSETS":
return FindUnusedAssets(operation.parameters);
case "ESTIMATE_BUILD_SIZE":
return EstimateBuildSize(operation.parameters);
case "PERFORMANCE_REPORT":
return PerformanceReport(operation.parameters);
case "AUTO_ORGANIZE_FOLDERS":
return AutoOrganizeFolders(operation.parameters);
case "GENERATE_LOD":
return GenerateLOD(operation.parameters);
case "AUTO_ATLAS_TEXTURES":
return AutoAtlasTextures(operation.parameters);
// ===== Package management =====
case "LIST_PACKAGES":
return ListPackages(operation.parameters);
case "INSTALL_PACKAGE":
return InstallPackage(operation.parameters);
case "REMOVE_PACKAGE":
return RemovePackage(operation.parameters);
case "CHECK_PACKAGE":
return CheckPackage(operation.parameters);
// ===== Game development specific features =====
case "CREATE_GAME_CONTROLLER":
return CreateGameController(operation.parameters);
case "SETUP_INPUT_SYSTEM":
return SetupInputSystem(operation.parameters);
case "CREATE_STATE_MACHINE":
return CreateStateMachine(operation.parameters);
case "SETUP_INVENTORY_SYSTEM":
return SetupInventorySystem(operation.parameters);
// ===== Prototyping features =====
case "CREATE_GAME_TEMPLATE":
return CreateGameTemplate(operation.parameters);
case "QUICK_PROTOTYPE":
return QuickPrototype(operation.parameters);
// AI & Machine Learning related
case "SETUP_ML_AGENT":
return SetupMLAgent(operation.parameters);
case "CREATE_NEURAL_NETWORK":
return CreateNeuralNetwork(operation.parameters);
case "SETUP_BEHAVIOR_TREE":
return SetupBehaviorTree(operation.parameters);
case "CREATE_AI_PATHFINDING":
return CreateAIPathfinding(operation.parameters);
// ===== GOAP AI system =====
case "CREATE_GOAP_AGENT":
return CreateGoapAgent(operation.parameters);
case "DEFINE_GOAP_GOAL":
return DefineGoapGoal(operation.parameters);
case "CREATE_GOAP_ACTION":
return CreateGoapAction(operation.parameters);
case "DEFINE_BEHAVIOR_LANGUAGE":
return DefineBehaviorLanguage(operation.parameters);
case "GENERATE_GOAP_ACTION_SET":
return GenerateGoapActionSet(operation.parameters);
case "SETUP_GOAP_WORLD_STATE":
return SetupGoapWorldState(operation.parameters);
case "CREATE_GOAP_TEMPLATE":
return CreateGoapTemplate(operation.parameters);
case "DEBUG_GOAP_DECISIONS":
return DebugGoapDecisions(operation.parameters);
case "OPTIMIZE_GOAP_PERFORMANCE":
return OptimizeGoapPerformance(operation.parameters);
// ===== Behavior Tree system =====
case "CREATE_BT_AGENT":
return CreateBTAgent(operation.parameters);
case "ADD_BT_NODE":
return AddBTNode(operation.parameters);
case "CREATE_BT_FROM_DESCRIPTION":
return CreateBTFromDescription(operation.parameters);
case "START_BT":
return StartBT(operation.parameters);
case "STOP_BT":
return StopBT(operation.parameters);
case "DEBUG_BT":
return DebugBT(operation.parameters);
// === Synaptic Pro Material Tools ===
case "CREATE_WATER_MATERIAL":
return CreateWaterMaterial(operation.parameters);
case "CREATE_TOON_MATERIAL":
return CreateToonMaterial(operation.parameters);
case "CREATE_HAIR_MATERIAL":
return CreateHairMaterial(operation.parameters);
case "CREATE_EYE_MATERIAL":
return CreateEyeMaterial(operation.parameters);
case "CREATE_SKY_MATERIAL":
return CreateSkyMaterial(operation.parameters);
case "CREATE_DISSOLVE_MATERIAL":
return CreateDissolveMaterial(operation.parameters);
case "CREATE_SHIELD_MATERIAL":
return CreateShieldMaterial(operation.parameters);
case "CREATE_GRASS_MATERIAL":
return CreateGrassMaterial(operation.parameters);
case "SET_MATERIAL_PROPERTY":
return SetMaterialProperty(operation.parameters);
case "GET_MATERIAL_PROPERTIES":
return GetMaterialProperties(operation.parameters);
case "CREATE_HDRP_WATER":
return CreateHDRPWater(operation.parameters);
case "SET_HDRP_WATER_PROPERTY":
return SetHDRPWaterProperty(operation.parameters);
case "SET_VFX_PROPERTY":
return SetVFXProperty(operation.parameters);
case "GET_VFX_PROPERTIES":
return GetVFXProperties(operation.parameters);
case "TRIGGER_VFX_EVENT":
return TriggerVFXEvent(operation.parameters);
// === VFX Graph Builder API ===
case "VFX_CREATE":
return VFXCreate(operation.parameters);
case "VFX_ADD_CONTEXT":
return VFXAddContext(operation.parameters);
case "VFX_ADD_BLOCK":
return VFXAddBlock(operation.parameters);
case "VFX_ADD_OPERATOR":
return VFXAddOperator(operation.parameters);
case "VFX_LINK_CONTEXTS":
return VFXLinkContexts(operation.parameters);
case "VFX_GET_STRUCTURE":
return VFXGetStructure(operation.parameters);
case "VFX_COMPILE":
return VFXCompile(operation.parameters);
case "VFX_GET_AVAILABLE_TYPES":
return VFXGetAvailableTypes(operation.parameters);
case "VFX_ADD_PARAMETER":
return VFXAddParameter(operation.parameters);
case "VFX_CONNECT_SLOTS":
return VFXConnectSlots(operation.parameters);
case "VFX_SET_ATTRIBUTE":
return VFXSetAttribute(operation.parameters);
case "VFX_CREATE_PRESET":
return VFXCreatePreset(operation.parameters);
case "VFX_CONFIGURE_OUTPUT":
return VFXConfigureOutput(operation.parameters);
case "VFX_SET_COLOR_GRADIENT":
return VFXSetColorGradient(operation.parameters);
// === VFX Graph Asset Editing ===
case "READ_VFX_GRAPH":
return ReadVFXGraph(operation.parameters);
case "MODIFY_VFX_GRAPH":
return ModifyVFXGraph(operation.parameters);
case "ANALYZE_VFX_GRAPH":
return AnalyzeVFXGraph(operation.parameters);
case "VFX_SET_OUTPUT":
return VFXSetOutput(operation.parameters);
case "VFX_SET_BLOCK_VALUE":
return VFXSetBlockValue(operation.parameters);
case "VFX_SET_SPAWN_RATE":
return VFXSetSpawnRate(operation.parameters);
case "VFX_LIST_BLOCKS":
return VFXListBlocks(operation.parameters);
case "VFX_REMOVE_BLOCK":
return VFXRemoveBlock(operation.parameters);
case "VFX_GET_BLOCK_INFO":
return VFXGetBlockInfo(operation.parameters);
// === Particle System Asset Editing ===
case "READ_PARTICLE_SYSTEM":
return ReadParticleSystem(operation.parameters);
case "MODIFY_PARTICLE_SYSTEM":
return ModifyParticleSystem(operation.parameters);
case "FIX_URP_PARTICLE_SHADERS":
return FixURPParticleShaders(operation.parameters);
case "FIX_PINK_MATERIALS":
return FixPinkMaterials(operation.parameters);
case "CREATE_OCEAN_SYSTEM":
return CreateOceanSystem(operation.parameters);
case "ADD_BUOYANCY":
return AddBuoyancy(operation.parameters);
// === Synaptic Pro Texture Tools ===
case "GENERATE_SDF_TEXTURE":
return GenerateSDFTexture(operation.parameters);
case "GENERATE_RAMP_TEXTURE":
return GenerateRampTexture(operation.parameters);
case "ADD_DISSOLVE_CONTROLLER":
return AddDissolveController(operation.parameters);
case "ADD_SHIELD_CONTROLLER":
return AddShieldController(operation.parameters);
case "ADD_GRASS_RENDERER":
return AddGrassRenderer(operation.parameters);
case "TRIGGER_DISSOLVE":
return TriggerDissolve(operation.parameters);
case "TRIGGER_SHIELD_HIT":
return TriggerShieldHit(operation.parameters);
case "GENERATE_CLOUD_NOISE":
return GenerateCloudNoiseTexture(operation.parameters);
// Dynamic Meta-Tools
case "DYNAMIC_INSPECT":
return DynamicInspect(operation.parameters);
case "DYNAMIC_MODIFY":
return DynamicModify(operation.parameters);
case "DYNAMIC_CREATE":
return DynamicCreate(operation.parameters);
default:
return $"Unknown operation: {operation.type}";
}
}
catch (Exception e)
{
return CreateErrorResponse("ExecuteOperation", e, operation.parameters);
}
}
private string CreateGameObject(Dictionary<string, string> parameters)
{
var name = parameters.GetValueOrDefault("name", "GameObject");
string result = "";
// Record operation to history
var historyParams = new Dictionary<string, object>();
foreach (var kvp in parameters)
{
historyParams[kvp.Key] = kvp.Value;
}
NexusOperationHistory.Instance.RecordOperation(
"CREATE_GAMEOBJECT",
$"Create GameObject: {name}",
historyParams,
() =>
{
GameObject go;
var type = parameters.GetValueOrDefault("type", "empty").ToLower();
// Parse color if specified
Color? primitiveColor = null;
if (parameters.TryGetValue("color", out var colorStr))
{
primitiveColor = ParseColor(colorStr);
}
// Set appropriate mesh for primitive types
switch (type)
{
case "cube":
go = CreatePrimitiveWithMaterial(PrimitiveType.Cube, primitiveColor);
go.name = name;
break;
case "sphere":
go = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, primitiveColor);
go.name = name;
break;
case "cylinder":
go = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, primitiveColor);
go.name = name;
break;
case "plane":
go = CreatePrimitiveWithMaterial(PrimitiveType.Plane, primitiveColor);
go.name = name;
break;
case "capsule":
go = CreatePrimitiveWithMaterial(PrimitiveType.Capsule, primitiveColor);
go.name = name;
break;
case "quad":
go = CreatePrimitiveWithMaterial(PrimitiveType.Quad, primitiveColor);
go.name = name;
break;
default:
go = new GameObject(name);
break;
}
// Register to UNDO
UnityEditor.Undo.RegisterCreatedObjectUndo(go, $"Create {name}");
// Position
if (parameters.TryGetValue("position", out var pos))
{
go.transform.position = ParseVector3(pos);
}
// Rotation
if (parameters.TryGetValue("rotation", out var rot))
{
go.transform.rotation = Quaternion.Euler(ParseVector3(rot));
}
// Scale
if (parameters.TryGetValue("scale", out var scale))
{
go.transform.localScale = ParseVector3(scale);
}
// Parent
if (parameters.TryGetValue("parent", out var parentName))
{
var parent = GameObject.Find(parentName);
if (parent != null)
{
go.transform.SetParent(parent.transform);
}
}
lastCreatedObject = go;
createdObjects.Add(go);
Selection.activeGameObject = go;
EditorGUIUtility.PingObject(go);
// Include detailed information of creation result
var components = go.GetComponents<Component>().Select(c => c.GetType().Name).ToArray();
result = JsonConvert.SerializeObject(new
{
success = true,
message = $"Created GameObject: {name}",
name = go.name,
type = type,
components = components,
hasMesh = go.GetComponent<MeshFilter>() != null,
hasRenderer = go.GetComponent<MeshRenderer>() != null
});
}
);
return result;
}
private string UpdateGameObject(Dictionary<string, string> parameters)
{
try
{
var gameObject = GetTargetGameObject(parameters);
if (gameObject == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "GameObject not found"
});
}
var changes = new List<string>();
// Register to UNDO
UnityEditor.Undo.RecordObject(gameObject, "Update GameObject");
// Rename - use "newName" parameter (not "name" which is used for target identification)
if (parameters.ContainsKey("newName"))
{
gameObject.name = parameters["newName"];
changes.Add($"Name changed to: {parameters["newName"]}");
}
// Change tag
if (parameters.ContainsKey("tag"))
{
try
{
gameObject.tag = parameters["tag"];
changes.Add($"Tag changed to: {parameters["tag"]}");
}
catch
{
changes.Add($"Warning: Tag '{parameters["tag"]}' not found");
}
}
// Change layer
if (parameters.ContainsKey("layer"))
{
var layer = int.Parse(parameters["layer"]);
gameObject.layer = layer;
changes.Add($"Layer changed to: {LayerMask.LayerToName(layer)} ({layer})");
}
// Active state (supports isActive or active parameter)
if (parameters.ContainsKey("isActive") || parameters.ContainsKey("active"))
{
var activeParam = parameters.ContainsKey("isActive") ? parameters["isActive"] : parameters["active"];
var isActive = activeParam.ToLower() == "true" || activeParam == "1";
gameObject.SetActive(isActive);
changes.Add($"Active state set to: {isActive}");
}
// Static state
if (parameters.ContainsKey("isStatic"))
{
var isStatic = parameters["isStatic"] == "true";
gameObject.isStatic = isStatic;
changes.Add($"Static state set to: {isStatic}");
}
EditorUtility.SetDirty(gameObject);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Updated GameObject '{gameObject.name}'",
changes = changes,
gameObject = new
{
name = gameObject.name,
tag = gameObject.tag,
layer = LayerMask.LayerToName(gameObject.layer),
active = gameObject.activeSelf,
isStatic = gameObject.isStatic,
path = GetFullPath(gameObject)
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string DeleteGameObject(Dictionary<string, string> parameters)
{
try
{
var gameObject = GetTargetGameObject(parameters);
if (gameObject == null)
{
var targetName = parameters.GetValueOrDefault("target") ??
parameters.GetValueOrDefault("name") ??
parameters.GetValueOrDefault("object") ?? "unknown";
return JsonConvert.SerializeObject(new
{
success = false,
error = $"GameObject '{targetName}' not found or already deleted"
});
}
// Check if object has already been deleted
if (gameObject == null || !gameObject)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "GameObject is already destroyed"
});
}
// Record number of child objects
int childCount = gameObject.transform.childCount;
string path = GetFullPath(gameObject);
string objectName = gameObject.name; // Save name before deletion
SynLog.Info($"[DeleteGameObject] Deleting object: {objectName} at path: {path}");
// Cleanup
if (lastCreatedObject == gameObject)
{
lastCreatedObject = null;
}
createdObjects.Remove(gameObject);
try
{
// Register to UNDO before deletion
UnityEditor.Undo.DestroyObjectImmediate(gameObject);
SynLog.Info($"[DeleteGameObject] Successfully deleted: {objectName}");
}
catch (System.Exception ex)
{
Debug.LogError($"[DeleteGameObject] Failed to delete {objectName}: {ex.Message}");
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Failed to delete object: {ex.Message}"
});
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Deleted GameObject '{objectName}'",
deletedPath = path,
childrenDeleted = childCount
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string InstantiatePrefab(Dictionary<string, string> parameters)
{
try
{
var assetPath = parameters.GetValueOrDefault("assetPath");
if (string.IsNullOrEmpty(assetPath))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "assetPath is required"
});
}
// Ensure path starts with Assets/
if (!assetPath.StartsWith("Assets/"))
{
assetPath = "Assets/" + assetPath;
}
// Load the asset
var prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
if (prefabAsset == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Asset not found at path: {assetPath}",
hint = "Make sure the path is correct and the asset exists. Example: Assets/Prefabs/Player.prefab"
});
}
// Instantiate the prefab
var instance = PrefabUtility.InstantiatePrefab(prefabAsset) as GameObject;
if (instance == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Failed to instantiate prefab from: {assetPath}"
});
}
// Set name if provided
var name = parameters.GetValueOrDefault("name");
if (!string.IsNullOrEmpty(name))
{
instance.name = name;
}
// Set position if provided
if (parameters.TryGetValue("position", out var posStr))
{
try
{
var posJson = JsonConvert.DeserializeObject<Dictionary<string, float>>(posStr);
instance.transform.position = new Vector3(
posJson.GetValueOrDefault("x", 0),
posJson.GetValueOrDefault("y", 0),
posJson.GetValueOrDefault("z", 0)
);
}
catch { }
}
// Set rotation if provided
if (parameters.TryGetValue("rotation", out var rotStr))
{
try
{
var rotJson = JsonConvert.DeserializeObject<Dictionary<string, float>>(rotStr);
instance.transform.rotation = Quaternion.Euler(
rotJson.GetValueOrDefault("x", 0),
rotJson.GetValueOrDefault("y", 0),
rotJson.GetValueOrDefault("z", 0)
);
}
catch { }
}
// Set scale if provided
if (parameters.TryGetValue("scale", out var scaleStr))
{
try
{
var scaleJson = JsonConvert.DeserializeObject<Dictionary<string, float>>(scaleStr);
instance.transform.localScale = new Vector3(
scaleJson.GetValueOrDefault("x", 1),
scaleJson.GetValueOrDefault("y", 1),
scaleJson.GetValueOrDefault("z", 1)
);
}
catch { }
}
// Set parent if provided
var parentName = parameters.GetValueOrDefault("parent");
if (!string.IsNullOrEmpty(parentName))
{
var parent = GameObject.Find(parentName);
if (parent != null)
{
instance.transform.SetParent(parent.transform, true);
}
}
// Register for undo
UnityEditor.Undo.RegisterCreatedObjectUndo(instance, "Instantiate Prefab");
// Track the created object
lastCreatedObject = instance;
createdObjects.Add(instance);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Instantiated prefab from '{assetPath}'",
gameObject = instance.name,
position = new { x = instance.transform.position.x, y = instance.transform.position.y, z = instance.transform.position.z },
assetPath = assetPath
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string SetTransform(Dictionary<string, string> parameters)
{
try
{
var gameObject = GetTargetGameObject(parameters);
if (gameObject == null)
{
string targetName = parameters.GetValueOrDefault("target") ??
parameters.GetValueOrDefault("gameObject") ??
parameters.GetValueOrDefault("object");
return JsonConvert.SerializeObject(new
{
success = false,
error = $"GameObject '{targetName}' not found"
});
}
var changes = new List<string>();
var transform = gameObject.transform;
// Register Transform changes to UNDO
UnityEditor.Undo.RecordObject(transform, "Set Transform");
// Position
if (parameters.ContainsKey("position"))
{
transform.position = ParseVector3(parameters["position"]);
changes.Add($"Position set to: {transform.position}");
}
// Rotation
if (parameters.ContainsKey("rotation"))
{
transform.eulerAngles = ParseVector3(parameters["rotation"]);
changes.Add($"Rotation set to: {transform.eulerAngles}");
}
// Scale
if (parameters.ContainsKey("scale"))
{
transform.localScale = ParseVector3(parameters["scale"]);
changes.Add($"Scale set to: {transform.localScale}");
}
// Local coordinate system
if (parameters.ContainsKey("localPosition"))
{
transform.localPosition = ParseVector3(parameters["localPosition"]);
changes.Add($"Local position set to: {transform.localPosition}");
}
if (parameters.ContainsKey("localRotation"))
{
transform.localEulerAngles = ParseVector3(parameters["localRotation"]);
changes.Add($"Local rotation set to: {transform.localEulerAngles}");
}
// Set parent
if (parameters.ContainsKey("parent"))
{
if (string.IsNullOrEmpty(parameters["parent"]) || parameters["parent"] == "null")
{
transform.SetParent(null);
changes.Add("Parent removed");
}
else
{
var parent = GameObject.Find(parameters["parent"]);
if (parent != null)
{
transform.SetParent(parent.transform);
changes.Add($"Parent set to: {parent.name}");
}
}
}
// Whether to keep world position
if (parameters.ContainsKey("worldPositionStays") && parameters.ContainsKey("parent"))
{
var worldPositionStays = parameters["worldPositionStays"] == "true";
var parent = GameObject.Find(parameters["parent"]);
if (parent != null)
{
transform.SetParent(parent.transform, worldPositionStays);
changes.Add($"World position stays: {worldPositionStays}");
}
}
EditorUtility.SetDirty(gameObject);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Transform updated for '{gameObject.name}'",
changes = changes,
transform = new
{
position = new { x = transform.position.x, y = transform.position.y, z = transform.position.z },
rotation = new { x = transform.eulerAngles.x, y = transform.eulerAngles.y, z = transform.eulerAngles.z },
scale = new { x = transform.localScale.x, y = transform.localScale.y, z = transform.localScale.z },
localPosition = new { x = transform.localPosition.x, y = transform.localPosition.y, z = transform.localPosition.z },
localRotation = new { x = transform.localEulerAngles.x, y = transform.localEulerAngles.y, z = transform.localEulerAngles.z },
parent = transform.parent != null ? transform.parent.name : "None"
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string UpdateComponent(Dictionary<string, string> parameters)
{
try
{
SynLog.Info($"[UpdateComponent] Called with {parameters.Count} parameters:");
foreach (var kvp in parameters)
{
SynLog.Info($"[UpdateComponent] - {kvp.Key}: {kvp.Value}");
}
var component = parameters.GetValueOrDefault("component", "");
if (string.IsNullOrEmpty(component))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "component parameter is required",
receivedParameters = parameters.Keys.ToArray()
});
}
var gameObject = GetTargetGameObject(parameters);
SynLog.Info($"[UpdateComponent] Found GameObject: {gameObject?.name ?? "null"}");
if (gameObject == null)
{
var availableObjects = GameObject.FindObjectsOfType<GameObject>().Take(10).Select(o => o.name);
return JsonConvert.SerializeObject(new
{
success = false,
error = "GameObject not found",
availableObjects = availableObjects.ToArray()
});
}
var componentType = GetComponentType(component);
SynLog.Info($"[UpdateComponent] Component type: {componentType?.Name ?? "null"}");
if (componentType == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Component type '{component}' not found",
suggestedComponents = new[] { "Transform", "Rigidbody", "Collider", "Renderer", "Light", "Camera" }
});
}
var comp = gameObject.GetComponent(componentType);
SynLog.Info($"[UpdateComponent] Found component: {comp != null}");
if (comp == null)
{
var availableComponents = gameObject.GetComponents<Component>().Select(c => c.GetType().Name);
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Component '{component}' not found on '{gameObject.name}'",
availableComponents = availableComponents.ToArray()
});
}
var changes = new List<string>();
var properties = new Dictionary<string, object>();
// Register to UNDO
UnityEditor.Undo.RecordObject(comp, "Update Component");
// Process property parameters (properties object or direct parameters)
var propertyParams = new List<KeyValuePair<string, string>>();
var excludedKeys = new[] { "target", "component", "gameObject", "object", "targetObject", "name", "objectName", "targetName", "source", "sourceName", "properties", "operationId" };
// If properties parameter exists
if (parameters.ContainsKey("properties"))
{
try
{
var propertiesJson = parameters["properties"];
SynLog.Info($"[UpdateComponent] Raw properties parameter: {propertiesJson}");
// Unescape if escaped JSON string
if (propertiesJson.StartsWith("{\\\"") || propertiesJson.StartsWith("[\\\""))
{
// Unescape escaped JSON string
propertiesJson = System.Text.RegularExpressions.Regex.Unescape(propertiesJson);
SynLog.Info($"[UpdateComponent] Unescaped JSON: {propertiesJson}");
}
Dictionary<string, object> propertiesDict;
// Attempt to parse as JSON string
try
{
propertiesDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(propertiesJson);
}
catch
{
// If JSON parsing fails, attempt to parse as key=value format
SynLog.Warn($"[UpdateComponent] JSON parsing failed, attempting key=value parsing");
propertiesDict = ParseKeyValueString(propertiesJson);
}
foreach (var kvp in propertiesDict)
{
var valueStr = kvp.Value?.ToString() ?? "";
propertyParams.Add(new KeyValuePair<string, string>(kvp.Key, valueStr));
SynLog.Info($"[UpdateComponent] Added property: {kvp.Key} = {valueStr}");
}
SynLog.Info($"[UpdateComponent] Successfully parsed {propertyParams.Count} properties from JSON");
}
catch (Exception ex)
{
Debug.LogError($"[UpdateComponent] Failed to parse properties: {ex.Message}");
Debug.LogError($"[UpdateComponent] Raw properties value: {parameters["properties"]}");
}
}
// Add direct parameters as well
var directParams = parameters.Where(p => !excludedKeys.Contains(p.Key)).ToList();
propertyParams.AddRange(directParams);
SynLog.Info($"[UpdateComponent] Total property parameters to update: {propertyParams.Count}");
foreach (var param in propertyParams)
{
SynLog.Info($"[UpdateComponent] - Will update: {param.Key} = {param.Value}");
}
if (propertyParams.Count == 0)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "No property parameters found to update",
excludedKeys = excludedKeys,
allParameters = parameters.Keys.ToArray(),
hint = "Use 'properties' parameter with JSON object or pass properties directly"
});
}
foreach (var kvp in propertyParams)
{
try
{
SynLog.Info($"[UpdateComponent] Setting property {kvp.Key} to {kvp.Value}");
SetComponentProperty(comp, kvp.Key, kvp.Value);
changes.Add($"{kvp.Key} = {kvp.Value}");
// Get value after update (prevent circular references like Vector3)
var propInfo = componentType.GetProperty(kvp.Key);
if (propInfo != null && propInfo.CanRead)
{
var value = propInfo.GetValue(comp);
properties[kvp.Key] = ConvertValueForSerialization(value);
}
else
{
var fieldInfo = componentType.GetField(kvp.Key);
if (fieldInfo != null)
{
var value = fieldInfo.GetValue(comp);
properties[kvp.Key] = ConvertValueForSerialization(value);
}
}
SynLog.Info($"[UpdateComponent] Successfully set {kvp.Key}");
}
catch (Exception ex)
{
Debug.LogError($"[UpdateComponent] Failed to set {kvp.Key}: {ex.Message}");
changes.Add($"Failed to set {kvp.Key}: {ex.Message}");
}
}
EditorUtility.SetDirty(comp);
SynLog.Info($"[UpdateComponent] Completed with {changes.Count} changes");
// Check for errors even on success
var failedChanges = changes.Where(c => c.StartsWith("Failed")).ToList();
var successChanges = changes.Where(c => !c.StartsWith("Failed")).ToList();
// Provide useful information even on partial success
var isFullSuccess = failedChanges.Count == 0;
var hasAnySuccess = successChanges.Count > 0;
return JsonConvert.SerializeObject(new
{
success = isFullSuccess || hasAnySuccess, // Treat partial success as success
fullSuccess = isFullSuccess,
partialSuccess = hasAnySuccess && !isFullSuccess,
message = isFullSuccess ?
$"Successfully updated {component} on '{gameObject.name}'" :
hasAnySuccess ?
$"Partially updated {component} on '{gameObject.name}' - {successChanges.Count} successful, {failedChanges.Count} failed" :
$"Failed to update {component} on '{gameObject.name}'",
successfulChanges = successChanges,
failedChanges = failedChanges,
totalChanges = changes.Count,
successCount = successChanges.Count,
failureCount = failedChanges.Count,
properties = properties,
componentType = componentType.Name,
targetObject = gameObject.name,
availableProperties = componentType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite)
.Select(p => p.Name)
.Take(10) // Only first 10
.ToArray()
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[UpdateComponent] Fatal error: {e.Message}");
Debug.LogError($"[UpdateComponent] Stack trace: {e.StackTrace}");
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message,
errorType = e.GetType().Name,
stackTrace = e.StackTrace,
parameters = parameters.Keys.ToArray(),
debugInfo = "Check Unity Console for detailed debug logs"
}, Formatting.Indented);
}
}
private string AddComponent(Dictionary<string, string> parameters)
{
var componentType = parameters.GetValueOrDefault("componentType") ??
parameters.GetValueOrDefault("component") ??
parameters.GetValueOrDefault("type");
GameObject target = GetTargetGameObject(parameters);
if (target == null)
{
string targetName = parameters.GetValueOrDefault("target") ??
parameters.GetValueOrDefault("gameObject") ??
parameters.GetValueOrDefault("object");
return $"Target object not found: {targetName}";
}
// Try to find the component type
Type type = GetComponentType(componentType);
if (type == null)
{
return $"Component type not found: {componentType}";
}
// Register to UNDO before adding component
var component = UnityEditor.Undo.AddComponent(target, type);
// Set component properties if provided (exclude system parameters)
var excludedKeys = new[] { "target", "type", "componentType", "component", "gameObject", "object", "targetObject", "name", "objectName", "targetName", "source", "sourceName", "operationId" };
var propertyParams = parameters.Where(p => !excludedKeys.Contains(p.Key)).ToList();
SynLog.Info($"[AddComponent] Property parameters to set: {propertyParams.Count}");
foreach (var param in propertyParams)
{
SynLog.Info($"[AddComponent] - Will set: {param.Key} = {param.Value}");
}
foreach (var kvp in propertyParams)
{
try
{
SetComponentProperty(component, kvp.Key, kvp.Value);
SynLog.Info($"[AddComponent] Successfully set property {kvp.Key}");
}
catch (Exception ex)
{
Debug.LogError($"[AddComponent] Failed to set property {kvp.Key}: {ex.Message}");
}
}
return $"Added {componentType} to {target.name}";
}
private string SetProperty(Dictionary<string, string> parameters)
{
var componentType = parameters.GetValueOrDefault("component");
var property = parameters.GetValueOrDefault("property");
var value = parameters.GetValueOrDefault("value");
// Validate required parameters
if (string.IsNullOrEmpty(property))
{
return "Error: 'property' parameter is required";
}
var target = GetTargetGameObject(parameters);
if (target == null) {
string targetName = parameters.GetValueOrDefault("target") ??
parameters.GetValueOrDefault("gameObject") ??
parameters.GetValueOrDefault("object");
return $"Object not found: {targetName}";
}
Component component = null;
if (!string.IsNullOrEmpty(componentType))
{
var type = GetComponentType(componentType);
component = target.GetComponent(type);
}
else
{
component = target.transform;
}
if (component == null) return $"Component not found: {componentType}";
// Register to UNDO
UnityEditor.Undo.RecordObject(component, "Set Property");
SetComponentProperty(component, property, value);
return $"Set {property} = {value} on {target.name}";
}
private string CreateUI(Dictionary<string, string> parameters)
{
var uiType = parameters.GetValueOrDefault("type", "button");
var name = parameters.GetValueOrDefault("name", uiType);
// Ensure Canvas exists
var canvas = GameObject.FindObjectOfType<Canvas>();
if (canvas == null)
{
var canvasGO = new GameObject("Canvas");
canvas = canvasGO.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvasGO.AddComponent<CanvasScaler>();
canvasGO.AddComponent<GraphicRaycaster>();
// Add EventSystem
if (GameObject.FindObjectOfType<EventSystem>() == null)
{
var eventSystem = new GameObject("EventSystem");
eventSystem.AddComponent<EventSystem>();
eventSystem.AddComponent<StandaloneInputModule>();
}
}
GameObject uiElement = null;
switch (uiType.ToLower())
{
case "button":
uiElement = CreateButton(name, parameters);
break;
case "text":
uiElement = CreateText(name, parameters);
break;
case "inputfield":
uiElement = CreateInputField(name, parameters);
break;
case "panel":
uiElement = CreatePanel(name, parameters);
break;
case "image":
uiElement = CreateImage(name, parameters);
break;
case "slider":
uiElement = CreateSlider(name, parameters);
break;
case "toggle":
uiElement = CreateToggle(name, parameters);
break;
case "dropdown":
uiElement = CreateDropdown(name, parameters);
break;
}
if (uiElement != null)
{
// Register to UNDO
UnityEditor.Undo.RegisterCreatedObjectUndo(uiElement, $"Create UI {uiType}");
uiElement.transform.SetParent(canvas.transform, false);
// Position
if (parameters.TryGetValue("position", out var pos))
{
var rt = uiElement.GetComponent<RectTransform>();
rt.anchoredPosition = ParseVector2(pos);
}
// Size
if (parameters.TryGetValue("size", out var size))
{
var rt = uiElement.GetComponent<RectTransform>();
rt.sizeDelta = ParseVector2(size);
}
lastCreatedObject = uiElement;
createdObjects.Add(uiElement);
return $"Created UI {uiType}: {name}";
}
return $"Unknown UI type: {uiType}";
}
/// <summary>
/// Detailed UI canvas setup
/// </summary>
private string SetupUICanvas(Dictionary<string, string> parameters)
{
try
{
var canvasName = parameters.GetValueOrDefault("canvasName", "Canvas");
var renderMode = parameters.GetValueOrDefault("renderMode", "overlay");
var sortingOrder = int.Parse(parameters.GetValueOrDefault("sortingOrder", "0"));
var pixelPerfect = bool.Parse(parameters.GetValueOrDefault("pixelPerfect", "false"));
// Find existing Canvas or create new
Canvas canvas = null;
GameObject canvasGO = GameObject.Find(canvasName);
if (canvasGO != null)
{
canvas = canvasGO.GetComponent<Canvas>();
}
if (canvas == null)
{
canvasGO = new GameObject(canvasName);
canvas = canvasGO.AddComponent<Canvas>();
canvasGO.AddComponent<CanvasScaler>();
canvasGO.AddComponent<GraphicRaycaster>();
// Register Undo when creating new
UnityEditor.Undo.RegisterCreatedObjectUndo(canvasGO, "Create UI Canvas");
}
else
{
// Record Undo when editing existing Canvas
UnityEditor.Undo.RecordObject(canvas, "Setup UI Canvas");
UnityEditor.Undo.RecordObject(canvasGO.transform, "Setup UI Canvas Transform");
}
// RenderMode settings
switch (renderMode.ToLower())
{
case "overlay":
case "screenspace-overlay":
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = sortingOrder;
canvas.pixelPerfect = pixelPerfect;
break;
case "camera":
case "screenspace-camera":
canvas.renderMode = RenderMode.ScreenSpaceCamera;
// Camera settings
var cameraName = parameters.GetValueOrDefault("camera", "Main Camera");
Camera uiCamera = null;
if (cameraName == "Main Camera")
{
uiCamera = Camera.main;
}
else if (cameraName == "new")
{
// Create dedicated UI camera
var uiCameraGO = new GameObject("UI Camera");
uiCamera = uiCameraGO.AddComponent<Camera>();
UnityEditor.Undo.RegisterCreatedObjectUndo(uiCameraGO, "Create UI Camera");
uiCamera.clearFlags = CameraClearFlags.Depth;
uiCamera.cullingMask = LayerMask.GetMask("UI");
uiCamera.orthographic = true;
uiCamera.depth = 1; // Render above Main Camera
}
else
{
var cameraGO = GameObject.Find(cameraName);
if (cameraGO != null) uiCamera = cameraGO.GetComponent<Camera>();
}
if (uiCamera != null)
{
canvas.worldCamera = uiCamera;
}
canvas.planeDistance = float.Parse(parameters.GetValueOrDefault("planeDistance", "100"));
canvas.sortingOrder = sortingOrder;
canvas.pixelPerfect = pixelPerfect;
break;
case "world":
case "worldspace":
canvas.renderMode = RenderMode.WorldSpace;
// World Space settings
var position = parameters.GetValueOrDefault("position", "0,0,0");
var rotation = parameters.GetValueOrDefault("rotation", "0,0,0");
var scale = parameters.GetValueOrDefault("scale", "0.01,0.01,0.01");
canvasGO.transform.position = ParseVector3(position);
canvasGO.transform.rotation = Quaternion.Euler(ParseVector3(rotation));
canvasGO.transform.localScale = ParseVector3(scale);
// RectTransform size settings
var rectTransform = canvasGO.GetComponent<RectTransform>();
if (parameters.TryGetValue("sizeDelta", out var sizeDelta))
{
rectTransform.sizeDelta = ParseVector2(sizeDelta);
}
else
{
rectTransform.sizeDelta = new Vector2(1920, 1080); // Default size
}
break;
}
// Canvas Scaler settings
var canvasScaler = canvasGO.GetComponent<CanvasScaler>();
if (canvasScaler != null && canvas.renderMode != RenderMode.WorldSpace)
{
var scaleMode = parameters.GetValueOrDefault("scaleMode", "scale-with-screen");
switch (scaleMode.ToLower())
{
case "constant-pixel":
canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize;
canvasScaler.scaleFactor = float.Parse(parameters.GetValueOrDefault("scaleFactor", "1"));
break;
case "scale-with-screen":
canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
canvasScaler.referenceResolution = ParseVector2(parameters.GetValueOrDefault("referenceResolution", "1920,1080"));
canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
canvasScaler.matchWidthOrHeight = float.Parse(parameters.GetValueOrDefault("match", "0.5"));
break;
case "constant-physical":
canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ConstantPhysicalSize;
canvasScaler.physicalUnit = CanvasScaler.Unit.Points;
canvasScaler.referencePixelsPerUnit = float.Parse(parameters.GetValueOrDefault("pixelsPerUnit", "100"));
break;
}
}
// Check and create EventSystem
if (GameObject.FindObjectOfType<EventSystem>() == null)
{
var eventSystem = new GameObject("EventSystem");
eventSystem.AddComponent<EventSystem>();
eventSystem.AddComponent<StandaloneInputModule>();
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Canvas '{canvasName}' configured successfully",
canvasName = canvasName,
renderMode = canvas.renderMode.ToString(),
settings = new
{
sortingOrder = canvas.sortingOrder,
pixelPerfect = canvas.pixelPerfect,
worldCamera = canvas.worldCamera?.name,
planeDistance = canvas.planeDistance,
position = canvasGO.transform.position,
rotation = canvasGO.transform.rotation.eulerAngles,
scale = canvasGO.transform.localScale
}
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Error setting up UI Canvas: {e.Message}");
}
}
/// <summary>
/// Set UI anchor
/// </summary>
private string SetUIAnchor(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target", "");
var preset = parameters.GetValueOrDefault("preset", "");
if (string.IsNullOrEmpty(targetName))
{
return CreateErrorResponse("Target UI element name must be provided");
}
GameObject target = GameObject.Find(targetName);
if (target == null)
{
return CreateErrorResponse($"GameObject '{targetName}' not found");
}
RectTransform rectTransform = target.GetComponent<RectTransform>();
if (rectTransform == null)
{
return CreateErrorResponse($"GameObject '{targetName}' does not have RectTransform component");
}
// Record Undo
UnityEditor.Undo.RecordObject(rectTransform, "Set UI Anchor");
// For custom values
if (string.IsNullOrEmpty(preset) || preset.ToLower() == "custom")
{
// anchorMin
if (parameters.TryGetValue("anchorMin", out var anchorMin))
{
rectTransform.anchorMin = ParseVector2(anchorMin);
}
// anchorMax
if (parameters.TryGetValue("anchorMax", out var anchorMax))
{
rectTransform.anchorMax = ParseVector2(anchorMax);
}
// pivot
if (parameters.TryGetValue("pivot", out var pivot))
{
rectTransform.pivot = ParseVector2(pivot);
}
// anchoredPosition
if (parameters.TryGetValue("anchoredPosition", out var anchoredPos))
{
rectTransform.anchoredPosition = ParseVector2(anchoredPos);
}
// sizeDelta
if (parameters.TryGetValue("sizeDelta", out var sizeDelta))
{
rectTransform.sizeDelta = ParseVector2(sizeDelta);
}
}
else
{
// Apply preset
ApplyAnchorPreset(rectTransform, preset, parameters);
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"UI anchor set for {targetName}",
target = targetName,
preset = string.IsNullOrEmpty(preset) ? "custom" : preset,
anchorMin = new { x = rectTransform.anchorMin.x, y = rectTransform.anchorMin.y },
anchorMax = new { x = rectTransform.anchorMax.x, y = rectTransform.anchorMax.y },
pivot = new { x = rectTransform.pivot.x, y = rectTransform.pivot.y },
anchoredPosition = new { x = rectTransform.anchoredPosition.x, y = rectTransform.anchoredPosition.y },
sizeDelta = new { x = rectTransform.sizeDelta.x, y = rectTransform.sizeDelta.y }
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Error setting UI anchor: {e.Message}");
}
}
/// <summary>
/// Apply anchor preset
/// </summary>
private void ApplyAnchorPreset(RectTransform rectTransform, string preset, Dictionary<string, string> parameters)
{
bool keepPosition = bool.Parse(parameters.GetValueOrDefault("keepPosition", "false"));
bool keepSize = bool.Parse(parameters.GetValueOrDefault("keepSize", "false"));
Vector2 currentPos = rectTransform.anchoredPosition;
Vector2 currentSize = rectTransform.sizeDelta;
switch (preset.ToLower())
{
// Center
case "center":
case "middle-center":
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
rectTransform.pivot = new Vector2(0.5f, 0.5f);
break;
// Top
case "top-left":
rectTransform.anchorMin = new Vector2(0, 1);
rectTransform.anchorMax = new Vector2(0, 1);
rectTransform.pivot = new Vector2(0, 1);
break;
case "top-center":
rectTransform.anchorMin = new Vector2(0.5f, 1);
rectTransform.anchorMax = new Vector2(0.5f, 1);
rectTransform.pivot = new Vector2(0.5f, 1);
break;
case "top-right":
rectTransform.anchorMin = new Vector2(1, 1);
rectTransform.anchorMax = new Vector2(1, 1);
rectTransform.pivot = new Vector2(1, 1);
break;
// Middle
case "middle-left":
rectTransform.anchorMin = new Vector2(0, 0.5f);
rectTransform.anchorMax = new Vector2(0, 0.5f);
rectTransform.pivot = new Vector2(0, 0.5f);
break;
case "middle-right":
rectTransform.anchorMin = new Vector2(1, 0.5f);
rectTransform.anchorMax = new Vector2(1, 0.5f);
rectTransform.pivot = new Vector2(1, 0.5f);
break;
// Bottom
case "bottom-left":
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0, 0);
rectTransform.pivot = new Vector2(0, 0);
break;
case "bottom-center":
rectTransform.anchorMin = new Vector2(0.5f, 0);
rectTransform.anchorMax = new Vector2(0.5f, 0);
rectTransform.pivot = new Vector2(0.5f, 0);
break;
case "bottom-right":
rectTransform.anchorMin = new Vector2(1, 0);
rectTransform.anchorMax = new Vector2(1, 0);
rectTransform.pivot = new Vector2(1, 0);
break;
// Stretch
case "stretch-horizontal":
rectTransform.anchorMin = new Vector2(0, 0.5f);
rectTransform.anchorMax = new Vector2(1, 0.5f);
rectTransform.pivot = new Vector2(0.5f, 0.5f);
break;
case "stretch-vertical":
rectTransform.anchorMin = new Vector2(0.5f, 0);
rectTransform.anchorMax = new Vector2(0.5f, 1);
rectTransform.pivot = new Vector2(0.5f, 0.5f);
break;
case "stretch-all":
case "fill":
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(1, 1);
rectTransform.pivot = new Vector2(0.5f, 0.5f);
// Set offset for stretch
if (parameters.TryGetValue("offset", out var offsetStr))
{
float offset = float.Parse(offsetStr);
rectTransform.offsetMin = new Vector2(offset, offset);
rectTransform.offsetMax = new Vector2(-offset, -offset);
}
else
{
rectTransform.offsetMin = Vector2.zero;
rectTransform.offsetMax = Vector2.zero;
}
break;
// Special presets
case "top-stretch":
rectTransform.anchorMin = new Vector2(0, 1);
rectTransform.anchorMax = new Vector2(1, 1);
rectTransform.pivot = new Vector2(0.5f, 1);
break;
case "bottom-stretch":
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(1, 0);
rectTransform.pivot = new Vector2(0.5f, 0);
break;
case "left-stretch":
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(0, 1);
rectTransform.pivot = new Vector2(0, 0.5f);
break;
case "right-stretch":
rectTransform.anchorMin = new Vector2(1, 0);
rectTransform.anchorMax = new Vector2(1, 1);
rectTransform.pivot = new Vector2(1, 0.5f);
break;
}
// Keep position and size
if (keepPosition && preset.ToLower() != "stretch-all" && preset.ToLower() != "fill")
{
rectTransform.anchoredPosition = currentPos;
}
if (keepSize && preset.ToLower() != "stretch-all" && preset.ToLower() != "fill")
{
rectTransform.sizeDelta = currentSize;
}
}
private GameObject CreateButton(string name, Dictionary<string, string> parameters)
{
var button = new GameObject(name);
var rt = button.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(160, 40);
var image = button.AddComponent<Image>();
image.color = new Color(0.9f, 0.9f, 0.9f);
var btn = button.AddComponent<Button>();
btn.targetGraphic = image;
// Add text
var textGO = new GameObject("Text");
textGO.transform.SetParent(button.transform, false);
var text = textGO.AddComponent<Text>();
text.text = parameters.GetValueOrDefault("text", name);
text.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
text.fontSize = 16;
text.color = Color.black;
text.alignment = TextAnchor.MiddleCenter;
var textRT = textGO.GetComponent<RectTransform>();
textRT.anchorMin = Vector2.zero;
textRT.anchorMax = Vector2.one;
textRT.offsetMin = Vector2.zero;
textRT.offsetMax = Vector2.zero;
return button;
}
private GameObject CreateText(string name, Dictionary<string, string> parameters)
{
var textGO = new GameObject(name);
var rt = textGO.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(200, 50);
var text = textGO.AddComponent<Text>();
text.text = parameters.GetValueOrDefault("text", "Text");
text.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
text.fontSize = int.Parse(parameters.GetValueOrDefault("fontSize", "16"));
text.color = ParseColor(parameters.GetValueOrDefault("color", "black"));
text.alignment = TextAnchor.MiddleCenter;
return textGO;
}
private GameObject CreateInputField(string name, Dictionary<string, string> parameters)
{
var inputGO = new GameObject(name);
var rt = inputGO.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(300, 40);
var image = inputGO.AddComponent<Image>();
image.color = Color.white;
var input = inputGO.AddComponent<InputField>();
// Create text components
var textGO = new GameObject("Text");
textGO.transform.SetParent(inputGO.transform, false);
var text = textGO.AddComponent<Text>();
text.supportRichText = false;
text.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
text.color = Color.black;
input.textComponent = text;
var textRT = textGO.GetComponent<RectTransform>();
textRT.anchorMin = Vector2.zero;
textRT.anchorMax = Vector2.one;
textRT.offsetMin = new Vector2(10, 6);
textRT.offsetMax = new Vector2(-10, -7);
// Placeholder
var placeholderGO = new GameObject("Placeholder");
placeholderGO.transform.SetParent(inputGO.transform, false);
var placeholder = placeholderGO.AddComponent<Text>();
placeholder.text = parameters.GetValueOrDefault("placeholder", "Enter text...");
placeholder.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
placeholder.fontStyle = FontStyle.Italic;
placeholder.color = new Color(0.5f, 0.5f, 0.5f, 0.5f);
input.placeholder = placeholder;
var placeholderRT = placeholderGO.GetComponent<RectTransform>();
placeholderRT.anchorMin = Vector2.zero;
placeholderRT.anchorMax = Vector2.one;
placeholderRT.offsetMin = new Vector2(10, 6);
placeholderRT.offsetMax = new Vector2(-10, -7);
return inputGO;
}
private GameObject CreatePanel(string name, Dictionary<string, string> parameters)
{
var panel = new GameObject(name);
var rt = panel.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(400, 300);
var image = panel.AddComponent<Image>();
image.color = new Color(0.2f, 0.2f, 0.2f, 0.8f);
return panel;
}
private GameObject CreateImage(string name, Dictionary<string, string> parameters)
{
var imageGO = new GameObject(name);
var rt = imageGO.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(100, 100);
var image = imageGO.AddComponent<Image>();
image.color = ParseColor(parameters.GetValueOrDefault("color", "white"));
return imageGO;
}
private GameObject CreateSlider(string name, Dictionary<string, string> parameters)
{
var slider = new GameObject(name);
var rt = slider.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(200, 20);
var sliderComp = slider.AddComponent<Slider>();
// Background
var background = new GameObject("Background");
background.transform.SetParent(slider.transform, false);
var bgImage = background.AddComponent<Image>();
bgImage.color = new Color(0.3f, 0.3f, 0.3f);
var bgRT = background.GetComponent<RectTransform>();
bgRT.anchorMin = new Vector2(0, 0.25f);
bgRT.anchorMax = new Vector2(1, 0.75f);
bgRT.offsetMin = Vector2.zero;
bgRT.offsetMax = Vector2.zero;
// Fill Area
var fillArea = new GameObject("Fill Area");
fillArea.transform.SetParent(slider.transform, false);
var fillAreaRT = fillArea.GetComponent<RectTransform>();
fillAreaRT.anchorMin = new Vector2(0, 0.25f);
fillAreaRT.anchorMax = new Vector2(1, 0.75f);
fillAreaRT.offsetMin = new Vector2(5, 0);
fillAreaRT.offsetMax = new Vector2(-15, 0);
sliderComp.fillRect = fillAreaRT;
// Fill
var fill = new GameObject("Fill");
fill.transform.SetParent(fillArea.transform, false);
var fillImage = fill.AddComponent<Image>();
fillImage.color = new Color(0.3f, 0.6f, 1f);
var fillRT = fill.GetComponent<RectTransform>();
fillRT.anchorMin = Vector2.zero;
fillRT.anchorMax = new Vector2(1, 1);
fillRT.offsetMin = Vector2.zero;
fillRT.offsetMax = Vector2.zero;
// Handle
var handle = new GameObject("Handle");
handle.transform.SetParent(slider.transform, false);
var handleImage = handle.AddComponent<Image>();
handleImage.color = Color.white;
var handleRT = handle.GetComponent<RectTransform>();
handleRT.sizeDelta = new Vector2(20, 20);
sliderComp.handleRect = handleRT;
sliderComp.targetGraphic = handleImage;
return slider;
}
private GameObject CreateToggle(string name, Dictionary<string, string> parameters)
{
var toggle = new GameObject(name);
var rt = toggle.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(200, 30);
var toggleComp = toggle.AddComponent<Toggle>();
// Background
var background = new GameObject("Background");
background.transform.SetParent(toggle.transform, false);
var bgImage = background.AddComponent<Image>();
bgImage.color = Color.white;
var bgRT = background.GetComponent<RectTransform>();
bgRT.anchorMin = new Vector2(0, 0.5f);
bgRT.anchorMax = new Vector2(0, 0.5f);
bgRT.anchoredPosition = new Vector2(10, 0);
bgRT.sizeDelta = new Vector2(20, 20);
toggleComp.targetGraphic = bgImage;
// Checkmark
var checkmark = new GameObject("Checkmark");
checkmark.transform.SetParent(background.transform, false);
var checkImage = checkmark.AddComponent<Image>();
checkImage.color = new Color(0.3f, 0.6f, 1f);
var checkRT = checkmark.GetComponent<RectTransform>();
checkRT.anchorMin = Vector2.zero;
checkRT.anchorMax = Vector2.one;
checkRT.offsetMin = new Vector2(2, 2);
checkRT.offsetMax = new Vector2(-2, -2);
toggleComp.graphic = checkImage;
// Label
var label = new GameObject("Label");
label.transform.SetParent(toggle.transform, false);
var labelText = label.AddComponent<Text>();
labelText.text = parameters.GetValueOrDefault("text", name);
labelText.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
labelText.color = Color.black;
var labelRT = label.GetComponent<RectTransform>();
labelRT.anchorMin = new Vector2(0, 0.5f);
labelRT.anchorMax = new Vector2(1, 0.5f);
labelRT.anchoredPosition = new Vector2(40, 0);
labelRT.sizeDelta = new Vector2(-40, 30);
return toggle;
}
private GameObject CreateDropdown(string name, Dictionary<string, string> parameters)
{
var dropdown = new GameObject(name);
var rt = dropdown.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(200, 30);
var image = dropdown.AddComponent<Image>();
image.color = Color.white;
var dropdownComp = dropdown.AddComponent<Dropdown>();
dropdownComp.targetGraphic = image;
// Template (hidden by default)
var template = new GameObject("Template");
template.transform.SetParent(dropdown.transform, false);
template.SetActive(false);
// Add options
if (parameters.TryGetValue("options", out var optionsStr))
{
var options = optionsStr.Split(',');
dropdownComp.options.Clear();
foreach (var option in options)
{
dropdownComp.options.Add(new Dropdown.OptionData(option.Trim()));
}
}
return dropdown;
}
private Task<string> CreateScript(NexusUnityOperation operation)
{
var name = operation.parameters.GetValueOrDefault("name", "NewScript");
var content = operation.parameters.GetValueOrDefault("content", "");
var code = !string.IsNullOrEmpty(content) ? content : operation.code ?? GenerateDefaultScript(name);
// Use path parameter if provided, otherwise default to Synaptic_Generated
var folderPath = operation.parameters.GetValueOrDefault("path", "Assets/Synaptic_Generated");
// Normalize path - remove trailing slash and ensure it starts with Assets
folderPath = folderPath.TrimEnd('/').TrimEnd('\\');
if (!folderPath.StartsWith("Assets"))
{
folderPath = "Assets/" + folderPath;
}
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
var scriptPath = $"{folderPath}/{name}.cs";
// Check for existing file
if (File.Exists(scriptPath))
{
scriptPath = $"{folderPath}/{name}_{DateTime.Now:HHmmss}.cs";
}
// Register script creation to UNDO (only if something is selected)
if (Selection.activeObject != null)
{
UnityEditor.Undo.RegisterCompleteObjectUndo(Selection.activeObject, $"Create Script {name}");
}
File.WriteAllText(scriptPath, code);
AssetDatabase.Refresh();
// Return script creation success response
string message = $"Script '{name}' created successfully";
object result;
// Attach process is done separately (must attach manually after compilation)
if (operation.parameters.TryGetValue("attach", out var attachTo))
{
message = $"Script '{name}' created successfully. Note: Script needs to be compiled before attaching to GameObject.";
result = new
{
success = true,
message = message,
scriptPath = scriptPath,
scriptName = name,
attachTo = attachTo
};
}
else
{
result = new
{
success = true,
message = message,
scriptPath = scriptPath,
scriptName = name
};
}
return Task.FromResult(JsonConvert.SerializeObject(result));
}
private string GenerateDefaultScript(string className)
{
return $@"using UnityEngine;
public class {className} : MonoBehaviour
{{
void Start()
{{
}}
void Update()
{{
}}
}}";
}
private string ModifyScript(Dictionary<string, string> parameters)
{
try
{
string scriptPath = parameters.GetValueOrDefault("scriptPath", "");
string fileName = parameters.GetValueOrDefault("fileName", "");
string operation = parameters.GetValueOrDefault("operation", "replace"); // replace, insert, append, prepend
string content = parameters.GetValueOrDefault("content", "");
string searchText = parameters.GetValueOrDefault("searchText", "");
int lineNumber = Convert.ToInt32(parameters.GetValueOrDefault("lineNumber", "0"));
SynLog.Info($"[NexusExecutor] ModifyScript - Operation: {operation}, SearchText: '{searchText}', LineNumber: {lineNumber}");
// Parameter validation
if (string.IsNullOrEmpty(scriptPath) && string.IsNullOrEmpty(fileName))
{
return CreateErrorResponse("Either scriptPath or fileName must be provided");
}
// Determine script path (using improved GetScriptPath method)
string fullPath = GetScriptPath(scriptPath, fileName);
if (string.IsNullOrEmpty(fullPath))
{
string debugInfo = GetScriptSearchInfo(scriptPath, fileName);
return CreateErrorResponse($"Script file not found\n\n{debugInfo}");
}
SynLog.Info($"[NexusExecutor] ModifyScript - Found script at: {fullPath}");
if (!System.IO.File.Exists(fullPath))
{
return CreateErrorResponse($"Script file does not exist: {fullPath}");
}
// Check if file has been read first
if (!HasFileBeenRead(fullPath))
{
return GetFileNotReadError(fullPath);
}
// Load current file content
string originalContent = System.IO.File.ReadAllText(fullPath);
string newContent = originalContent;
string operationResult = "";
switch (operation.ToLower())
{
case "replace":
if (!string.IsNullOrEmpty(searchText))
{
if (originalContent.Contains(searchText))
{
newContent = originalContent.Replace(searchText, content);
operationResult = $"Replaced '{searchText}' with new content";
}
else
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Search text not found: '{searchText}'"
}
}
});
}
}
else if (lineNumber > 0)
{
var lines = originalContent.Split('\n');
if (lineNumber <= lines.Length)
{
lines[lineNumber - 1] = content;
newContent = string.Join("\n", lines);
operationResult = $"Replaced line {lineNumber}";
}
else
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Line number {lineNumber} is out of range (file has {lines.Length} lines)"
}
}
});
}
}
else
{
// Replace entire content
newContent = content;
operationResult = "Replaced entire file content";
}
break;
case "insert":
if (lineNumber > 0)
{
var lines = originalContent.Split('\n').ToList();
if (lineNumber <= lines.Count + 1)
{
lines.Insert(lineNumber - 1, content);
newContent = string.Join("\n", lines);
operationResult = $"Inserted content at line {lineNumber}";
}
else
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Line number {lineNumber} is out of range"
}
}
});
}
}
else if (!string.IsNullOrEmpty(searchText))
{
int index = originalContent.IndexOf(searchText);
if (index >= 0)
{
newContent = originalContent.Insert(index, content);
operationResult = $"Inserted content before '{searchText}'";
}
else
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Search text not found: '{searchText}'"
}
}
});
}
}
break;
case "append":
newContent = originalContent + "\n" + content;
operationResult = "Appended content to end of file";
break;
case "prepend":
newContent = content + "\n" + originalContent;
operationResult = "Prepended content to beginning of file";
break;
default:
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Unknown operation: {operation}. Supported operations: replace, insert, append, prepend"
}
}
});
}
// Syntax validation
string syntaxError;
if (!ValidateCSharpSyntax(newContent, out syntaxError))
{
// Warn about syntax errors but continue as user may be doing it intentionally
SynLog.Warn($"[NexusExecutor] Possible syntax error: {syntaxError}");
// Return response including error content
return JsonConvert.SerializeObject(new
{
success = true,
warning = $"Possible syntax error: {syntaxError}",
message = $"Script modified with warnings: {fullPath}\nOperation: {operationResult}",
syntaxCheck = new
{
passed = false,
error = syntaxError
},
suggestion = "Please fix the syntax error. Use Ctrl+Z (Cmd+Z) to undo changes."
});
}
// Check class structure preservation
string structureError;
if (!ValidateClassStructurePreservation(originalContent, newContent, out structureError))
{
Debug.LogError($"[NexusExecutor] Class structure broken: {structureError}");
return CreateErrorResponse($"Critical error: {structureError}\nThe modification would break the class structure. Operation cancelled.");
}
// Write to file (register to UNDO)
var scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(fullPath);
if (scriptAsset != null) UnityEditor.Undo.RegisterCompleteObjectUndo(scriptAsset, "Modify Script");
System.IO.File.WriteAllText(fullPath, newContent);
AssetDatabase.ImportAsset(fullPath);
AssetDatabase.Refresh();
SynLog.Info($"[NexusExecutor] ModifyScript - Success: {operationResult}");
return CreateSuccessResponse($"Script modified successfully: {fullPath}\nOperation: {operationResult}\nFile refreshed in Unity");
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] ModifyScript - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error modifying script: {e.Message}");
}
}
/// <summary>
/// Edit specific line only
/// </summary>
private string EditScriptLine(Dictionary<string, string> parameters)
{
try
{
string scriptPath = parameters.GetValueOrDefault("scriptPath", "");
string fileName = parameters.GetValueOrDefault("fileName", "");
int lineNumber = Convert.ToInt32(parameters.GetValueOrDefault("lineNumber", "0"));
string newContent = parameters.GetValueOrDefault("newContent", "");
string fullPath = GetScriptPath(scriptPath, fileName);
if (string.IsNullOrEmpty(fullPath))
{
string debugInfo = GetScriptSearchInfo(scriptPath, fileName);
return CreateErrorResponse($"Script file not found\\n\\n{debugInfo}");
}
// Check if file has been read first
if (!HasFileBeenRead(fullPath))
{
return GetFileNotReadError(fullPath);
}
var lines = System.IO.File.ReadAllLines(fullPath).ToList();
if (lineNumber < 1 || lineNumber > lines.Count)
{
return CreateErrorResponse($"Line number {lineNumber} is out of range (file has {lines.Count} lines)");
}
string oldContent = lines[lineNumber - 1];
lines[lineNumber - 1] = newContent;
System.IO.File.WriteAllLines(fullPath, lines);
AssetDatabase.ImportAsset(fullPath);
AssetDatabase.Refresh();
return CreateSuccessResponse($"Line {lineNumber} updated successfully\\nOld: {oldContent}\\nNew: {newContent}\\nFile: {fullPath}");
}
catch (Exception e)
{
return CreateErrorResponse($"Error editing script line: {e.Message}");
}
}
/// <summary>
/// Add method
/// </summary>
private string AddScriptMethod(Dictionary<string, string> parameters)
{
try
{
string scriptPath = parameters.GetValueOrDefault("scriptPath", "");
string fileName = parameters.GetValueOrDefault("fileName", "");
string methodName = parameters.GetValueOrDefault("methodName", "");
string methodContent = parameters.GetValueOrDefault("methodContent", "");
string insertAfter = parameters.GetValueOrDefault("insertAfter", "");
string fullPath = GetScriptPath(scriptPath, fileName);
if (string.IsNullOrEmpty(fullPath))
{
string debugInfo = GetScriptSearchInfo(scriptPath, fileName);
return CreateErrorResponse($"Script file not found\\n\\n{debugInfo}");
}
// Check if file has been read first
if (!HasFileBeenRead(fullPath))
{
return GetFileNotReadError(fullPath);
}
string content = System.IO.File.ReadAllText(fullPath);
string newContent;
if (!string.IsNullOrEmpty(insertAfter))
{
// Insert after specified pattern
int insertIndex = content.LastIndexOf(insertAfter);
if (insertIndex >= 0)
{
insertIndex = content.IndexOf('\n', insertIndex) + 1;
newContent = content.Insert(insertIndex, $"\n{methodContent}\n");
}
else
{
return CreateErrorResponse($"Pattern '{insertAfter}' not found in script");
}
}
else
{
// Add at end of class (before last })
int lastBraceIndex = content.LastIndexOf('}');
if (lastBraceIndex > 0)
{
newContent = content.Insert(lastBraceIndex, $"\n{methodContent}\n");
}
else
{
return CreateErrorResponse("Could not find class closing brace");
}
}
// Syntax validation
string syntaxError;
if (!ValidateCSharpSyntax(newContent, out syntaxError))
{
SynLog.Warn($"[NexusExecutor] AddScriptMethod - Syntax error: {syntaxError}");
return CreateErrorResponse($"Syntax error detected after adding method: {syntaxError}\nUse Ctrl+Z to undo.");
}
var scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(fullPath);
if (scriptAsset != null) UnityEditor.Undo.RegisterCompleteObjectUndo(scriptAsset, "Modify Script");
System.IO.File.WriteAllText(fullPath, newContent);
AssetDatabase.ImportAsset(fullPath);
AssetDatabase.Refresh();
return CreateSuccessResponse($"Method '{methodName}' added successfully to {fullPath}");
}
catch (Exception e)
{
return CreateErrorResponse($"Error adding script method: {e.Message}");
}
}
/// <summary>
/// Change variable value
/// </summary>
private string UpdateScriptVariable(Dictionary<string, string> parameters)
{
try
{
string scriptPath = parameters.GetValueOrDefault("scriptPath", "");
string fileName = parameters.GetValueOrDefault("fileName", "");
string variableName = parameters.GetValueOrDefault("variableName", "");
string newDeclaration = parameters.GetValueOrDefault("newDeclaration", "");
string updateType = parameters.GetValueOrDefault("updateType", "declaration");
string fullPath = GetScriptPath(scriptPath, fileName);
if (string.IsNullOrEmpty(fullPath))
{
string debugInfo = GetScriptSearchInfo(scriptPath, fileName);
return CreateErrorResponse($"Script file not found\\n\\n{debugInfo}");
}
// Check if file has been read first
if (!HasFileBeenRead(fullPath))
{
return GetFileNotReadError(fullPath);
}
string content = System.IO.File.ReadAllText(fullPath);
string newContent = content;
string operationResult = "";
// Search for variable declaration (support multiple patterns)
string[] searchPatterns = {
$@"\b(public|private|protected|internal)?\s*(static)?\s*\w+\s+{variableName}\s*[=;].*?;",
$@"\b{variableName}\s*=.*?;",
$@"\[\w+\]\s*(public|private|protected|internal)?\s*(static)?\s*\w+\s+{variableName}\s*[=;].*?;"
};
bool found = false;
foreach (string pattern in searchPatterns)
{
var regex = new System.Text.RegularExpressions.Regex(pattern, System.Text.RegularExpressions.RegexOptions.Multiline);
var match = regex.Match(content);
if (match.Success)
{
string oldDeclaration = match.Value;
if (updateType == "value")
{
// Update only value (part after =)
var valueRegex = new System.Text.RegularExpressions.Regex($@"({variableName}\s*=\s*)([^;]+)(;)");
newContent = valueRegex.Replace(content, $"$1{newDeclaration}$3");
}
else
{
// Replace entire declaration
newContent = regex.Replace(content, newDeclaration);
}
operationResult = $"Updated variable '{variableName}'\\nOld: {oldDeclaration}\\nNew: {newDeclaration}";
found = true;
break;
}
}
if (!found)
{
return CreateErrorResponse($"Variable '{variableName}' not found in script");
}
// Syntax validation
string syntaxError;
if (!ValidateCSharpSyntax(newContent, out syntaxError))
{
SynLog.Warn($"[NexusExecutor] UpdateScriptVariable - Syntax error: {syntaxError}");
return CreateErrorResponse($"Syntax error detected after updating variable: {syntaxError}\nUse Ctrl+Z to undo.");
}
var scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(fullPath);
if (scriptAsset != null) UnityEditor.Undo.RegisterCompleteObjectUndo(scriptAsset, "Modify Script");
System.IO.File.WriteAllText(fullPath, newContent);
AssetDatabase.ImportAsset(fullPath);
AssetDatabase.Refresh();
return CreateSuccessResponse($"Variable updated successfully\\n{operationResult}\\nFile: {fullPath}");
}
catch (Exception e)
{
return CreateErrorResponse($"Error updating script variable: {e.Message}");
}
}
/// <summary>
/// Read script content
/// </summary>
private string ReadScript(Dictionary<string, string> parameters)
{
try
{
string scriptPath = parameters.GetValueOrDefault("scriptPath", "");
string fileName = parameters.GetValueOrDefault("fileName", "");
SynLog.Info($"[NexusExecutor] ReadScript - scriptPath: '{scriptPath}', fileName: '{fileName}'");
// Parameter validation
if (string.IsNullOrEmpty(scriptPath) && string.IsNullOrEmpty(fileName))
{
return CreateErrorResponse("Either scriptPath or fileName must be provided");
}
// Determine script path
string fullPath = GetScriptPath(scriptPath, fileName);
if (string.IsNullOrEmpty(fullPath))
{
string debugInfo = GetScriptSearchInfo(scriptPath, fileName);
return CreateErrorResponse($"Script file not found\n\n{debugInfo}");
}
SynLog.Info($"[NexusExecutor] ReadScript - Found script at: {fullPath}");
if (!System.IO.File.Exists(fullPath))
{
return CreateErrorResponse($"Script file does not exist: {fullPath}");
}
// Read file content
string content = System.IO.File.ReadAllText(fullPath);
// Track that this file has been read (for edit validation)
MarkFileAsRead(fullPath);
// Return with line numbers
string[] lines = content.Split('\n');
var numberedLines = new List<string>();
for (int i = 0; i < lines.Length; i++)
{
numberedLines.Add($"{i + 1,4}: {lines[i]}");
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Script read successfully: {fullPath}",
scriptPath = fullPath,
fileName = System.IO.Path.GetFileName(fullPath),
lineCount = lines.Length,
content = content,
numberedContent = string.Join("\n", numberedLines)
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] ReadScript - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error reading script: {e.Message}");
}
}
/// <summary>
/// Mark a file as read (for edit validation)
/// </summary>
private void MarkFileAsRead(string filePath)
{
string normalizedPath = NormalizeFilePath(filePath);
readFiles.Add(normalizedPath);
readFileTimestamps[normalizedPath] = DateTime.Now;
SynLog.Info($"[NexusExecutor] File marked as read: {normalizedPath}");
}
/// <summary>
/// Check if a file has been read recently
/// </summary>
private bool HasFileBeenRead(string filePath)
{
string normalizedPath = NormalizeFilePath(filePath);
if (!readFiles.Contains(normalizedPath))
{
return false;
}
// Check if the read has expired
if (readFileTimestamps.TryGetValue(normalizedPath, out DateTime readTime))
{
if (DateTime.Now - readTime > readFileCacheExpiry)
{
// Cache expired, remove and return false
readFiles.Remove(normalizedPath);
readFileTimestamps.Remove(normalizedPath);
return false;
}
}
return true;
}
/// <summary>
/// Normalize file path for consistent comparison
/// </summary>
private string NormalizeFilePath(string filePath)
{
if (string.IsNullOrEmpty(filePath)) return "";
// Convert to absolute path if relative
if (!System.IO.Path.IsPathRooted(filePath))
{
filePath = System.IO.Path.GetFullPath(System.IO.Path.Combine(Application.dataPath, "..", filePath));
}
// Normalize path separators and case (on case-insensitive systems)
return filePath.Replace("\\", "/").ToLowerInvariant();
}
/// <summary>
/// Get error message for file not read
/// </summary>
private string GetFileNotReadError(string filePath)
{
string fileName = System.IO.Path.GetFileName(filePath);
return JsonConvert.SerializeObject(new
{
success = false,
error = $"File must be read before editing: {fileName}",
errorCode = "FILE_NOT_READ",
message = $"You must use READ_SCRIPT to read '{fileName}' before you can edit it. This ensures you understand the current content before making changes.",
hint = $"Use READ_SCRIPT with scriptPath=\"{filePath}\" first, then retry the edit.",
filePath = filePath
}, Formatting.Indented);
}
/// <summary>
/// Execute grep search across multiple script files
/// </summary>
private string GrepScripts(Dictionary<string, string> parameters)
{
try
{
string pattern = parameters.GetValueOrDefault("pattern", "");
string filePattern = parameters.GetValueOrDefault("filePattern", "**/*.cs");
bool caseSensitive = parameters.GetValueOrDefault("caseSensitive", "false").ToLower() == "true";
int contextLines = int.Parse(parameters.GetValueOrDefault("contextLines", "0"));
bool showLineNumbers = parameters.GetValueOrDefault("showLineNumbers", "true").ToLower() == "true";
int maxResults = int.Parse(parameters.GetValueOrDefault("maxResults", "100"));
if (string.IsNullOrEmpty(pattern))
{
return CreateErrorResponse("Search pattern is required");
}
// File search
var allFiles = Directory.GetFiles(Application.dataPath, "*.cs", SearchOption.AllDirectories);
var results = new List<object>();
int totalMatches = 0;
// Create regex
var regexOptions = caseSensitive ? System.Text.RegularExpressions.RegexOptions.None : System.Text.RegularExpressions.RegexOptions.IgnoreCase;
System.Text.RegularExpressions.Regex regex;
try
{
regex = new System.Text.RegularExpressions.Regex(pattern, regexOptions);
}
catch (Exception ex)
{
return CreateErrorResponse($"Invalid regex pattern: {ex.Message}");
}
foreach (var filePath in allFiles)
{
if (totalMatches >= maxResults) break;
// File pattern matching (simplified version)
string relativePath = "Assets" + filePath.Substring(Application.dataPath.Length);
if (!MatchesFilePattern(relativePath, filePattern))
continue;
try
{
string[] lines = System.IO.File.ReadAllLines(filePath);
var fileMatches = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
if (totalMatches >= maxResults) break;
if (regex.IsMatch(lines[i]))
{
var match = new
{
lineNumber = i + 1,
line = lines[i].Trim(),
contextBefore = contextLines > 0 ? GetContextLines(lines, i - contextLines, i - 1) : null,
contextAfter = contextLines > 0 ? GetContextLines(lines, i + 1, i + contextLines) : null
};
fileMatches.Add(match);
totalMatches++;
}
}
if (fileMatches.Count > 0)
{
results.Add(new
{
file = relativePath,
matchCount = fileMatches.Count,
matches = fileMatches
});
}
}
catch (Exception ex)
{
SynLog.Warn($"[GrepScripts] Error reading file {filePath}: {ex.Message}");
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {totalMatches} match(es) across {results.Count} file(s)",
pattern = pattern,
totalMatches = totalMatches,
fileCount = results.Count,
results = results
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] GrepScripts - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error during grep search: {e.Message}");
}
}
/// <summary>
/// Read specified range of script
/// </summary>
private string ReadScriptRange(Dictionary<string, string> parameters)
{
try
{
string scriptPath = parameters.GetValueOrDefault("scriptPath", "");
string fileName = parameters.GetValueOrDefault("fileName", "");
int startLine = int.Parse(parameters.GetValueOrDefault("startLine", "0"));
int endLine = int.Parse(parameters.GetValueOrDefault("endLine", "0"));
int limit = int.Parse(parameters.GetValueOrDefault("limit", "0"));
int offset = int.Parse(parameters.GetValueOrDefault("offset", "0"));
// Determine script path
string fullPath = GetScriptPath(scriptPath, fileName);
if (string.IsNullOrEmpty(fullPath) || !System.IO.File.Exists(fullPath))
{
return CreateErrorResponse($"Script file not found");
}
// Read file content
string[] allLines = System.IO.File.ReadAllLines(fullPath);
int totalLines = allLines.Length;
// Track that this file has been read (for edit validation)
MarkFileAsRead(fullPath);
// Determine range
int start = 0;
int end = totalLines;
if (startLine > 0 && endLine > 0)
{
start = startLine - 1; // 1-based to 0-based
end = Math.Min(endLine, totalLines);
}
else if (limit > 0)
{
start = offset;
end = Math.Min(start + limit, totalLines);
}
else if (offset > 0)
{
start = offset;
}
start = Math.Max(0, Math.Min(start, totalLines));
end = Math.Max(start, Math.Min(end, totalLines));
// Get lines in range
var selectedLines = new List<string>();
for (int i = start; i < end; i++)
{
selectedLines.Add($"{i + 1,4}: {allLines[i]}");
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Read lines {start + 1}-{end} from {System.IO.Path.GetFileName(fullPath)}",
scriptPath = fullPath,
fileName = System.IO.Path.GetFileName(fullPath),
totalLines = totalLines,
startLine = start + 1,
endLine = end,
lineCount = selectedLines.Count,
content = string.Join("\n", selectedLines)
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] ReadScriptRange - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error reading script range: {e.Message}");
}
}
/// <summary>
/// Search code across entire project
/// </summary>
private string SearchCode(Dictionary<string, string> parameters)
{
try
{
string query = parameters.GetValueOrDefault("query", "");
bool caseSensitive = parameters.GetValueOrDefault("caseSensitive", "false").ToLower() == "true";
bool groupByFile = parameters.GetValueOrDefault("groupByFile", "true").ToLower() == "true";
bool includeMetaFiles = parameters.GetValueOrDefault("includeMetaFiles", "false").ToLower() == "true";
int maxResults = int.Parse(parameters.GetValueOrDefault("maxResults", "200"));
if (string.IsNullOrEmpty(query))
{
return CreateErrorResponse("Search query is required");
}
// Parse file types
var fileTypes = new List<string> { ".cs" };
if (parameters.ContainsKey("fileTypes"))
{
try
{
var parsed = JsonConvert.DeserializeObject<List<string>>(parameters["fileTypes"]);
if (parsed != null && parsed.Count > 0)
fileTypes = parsed;
}
catch { }
}
// Parse search directories
var searchDirs = new List<string>();
if (parameters.ContainsKey("searchIn"))
{
try
{
var parsed = JsonConvert.DeserializeObject<List<string>>(parameters["searchIn"]);
if (parsed != null && parsed.Count > 0)
searchDirs = parsed.Select(d => Path.Combine(Application.dataPath, d)).ToList();
}
catch { }
}
if (searchDirs.Count == 0)
{
searchDirs.Add(Application.dataPath);
}
var results = new List<object>();
int totalMatches = 0;
// Create regex
var regexOptions = caseSensitive ? System.Text.RegularExpressions.RegexOptions.None : System.Text.RegularExpressions.RegexOptions.IgnoreCase;
System.Text.RegularExpressions.Regex regex;
try
{
regex = new System.Text.RegularExpressions.Regex(query, regexOptions);
}
catch (Exception ex)
{
return CreateErrorResponse($"Invalid search pattern: {ex.Message}");
}
foreach (var searchDir in searchDirs)
{
if (!Directory.Exists(searchDir)) continue;
foreach (var ext in fileTypes)
{
var files = Directory.GetFiles(searchDir, $"*{ext}", SearchOption.AllDirectories);
foreach (var filePath in files)
{
if (totalMatches >= maxResults) break;
// Skip .meta files
if (!includeMetaFiles && filePath.EndsWith(".meta"))
continue;
try
{
string[] lines = System.IO.File.ReadAllLines(filePath);
var fileMatches = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
if (totalMatches >= maxResults) break;
if (regex.IsMatch(lines[i]))
{
fileMatches.Add(new
{
lineNumber = i + 1,
line = lines[i].Trim()
});
totalMatches++;
}
}
if (fileMatches.Count > 0)
{
string relativePath = "Assets" + filePath.Substring(Application.dataPath.Length);
results.Add(new
{
file = relativePath,
matchCount = fileMatches.Count,
matches = fileMatches
});
}
}
catch (Exception ex)
{
SynLog.Warn($"[SearchCode] Error reading file {filePath}: {ex.Message}");
}
}
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {totalMatches} match(es) across {results.Count} file(s)",
query = query,
totalMatches = totalMatches,
fileCount = results.Count,
results = results
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] SearchCode - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error during code search: {e.Message}");
}
}
/// <summary>
/// Get list of script files
/// </summary>
private string ListScriptFiles(Dictionary<string, string> parameters)
{
try
{
string directory = parameters.GetValueOrDefault("directory", "Assets");
string pattern = parameters.GetValueOrDefault("pattern", "**/*.cs");
bool recursive = parameters.GetValueOrDefault("recursive", "true").ToLower() == "true";
string sortBy = parameters.GetValueOrDefault("sortBy", "name");
bool includeMetaFiles = parameters.GetValueOrDefault("includeMetaFiles", "false").ToLower() == "true";
string fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), directory);
if (!Directory.Exists(fullPath))
{
return CreateErrorResponse($"Directory not found: {directory}");
}
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
var files = Directory.GetFiles(fullPath, "*.cs", searchOption);
// Filter .meta files
if (!includeMetaFiles)
{
files = files.Where(f => !f.EndsWith(".meta")).ToArray();
}
// Collect file information
var fileInfos = files.Select(f =>
{
var info = new FileInfo(f);
string relativePath = f.Replace(Application.dataPath.Replace("/Assets", ""), "");
if (relativePath.StartsWith("/") || relativePath.StartsWith("\\"))
relativePath = relativePath.Substring(1);
return new
{
path = relativePath,
name = info.Name,
size = info.Length,
modified = info.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss"),
directory = Path.GetDirectoryName(relativePath)
};
}).ToList();
// Sort
switch (sortBy.ToLower())
{
case "size":
fileInfos = fileInfos.OrderBy(f => f.size).ToList();
break;
case "modified":
fileInfos = fileInfos.OrderBy(f => f.modified).ToList();
break;
case "name":
default:
fileInfos = fileInfos.OrderBy(f => f.name).ToList();
break;
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {fileInfos.Count} script file(s) in {directory}",
directory = directory,
fileCount = fileInfos.Count,
files = fileInfos
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] ListScriptFiles - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error listing script files: {e.Message}");
}
}
// Helper methods
private bool MatchesFilePattern(string filePath, string pattern)
{
// Simple glob pattern matching
if (pattern == "**/*.cs" || pattern == "**/*")
return filePath.EndsWith(".cs");
// Convert wildcards to regex
string regexPattern = "^" + System.Text.RegularExpressions.Regex.Escape(pattern)
.Replace("\\*\\*/", ".*")
.Replace("\\*", "[^/]*")
.Replace("\\?", ".") + "$";
return System.Text.RegularExpressions.Regex.IsMatch(filePath, regexPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
}
private List<string> GetContextLines(string[] lines, int start, int end)
{
var result = new List<string>();
start = Math.Max(0, start);
end = Math.Min(lines.Length - 1, end);
for (int i = start; i <= end; i++)
{
result.Add($"{i + 1,4}: {lines[i]}");
}
return result;
}
/// <summary>
/// Analyze script syntax and potential errors
/// </summary>
private string AnalyzeScript(Dictionary<string, string> parameters)
{
try
{
string scriptPath = parameters.GetValueOrDefault("scriptPath", "");
string fileName = parameters.GetValueOrDefault("fileName", "");
SynLog.Info($"[NexusExecutor] AnalyzeScript - scriptPath: '{scriptPath}', fileName: '{fileName}'");
// Parameter validation
if (string.IsNullOrEmpty(scriptPath) && string.IsNullOrEmpty(fileName))
{
return CreateErrorResponse("Either scriptPath or fileName must be provided");
}
// Determine script path
string fullPath = GetScriptPath(scriptPath, fileName);
if (string.IsNullOrEmpty(fullPath) || !System.IO.File.Exists(fullPath))
{
return CreateErrorResponse($"Script file not found: {scriptPath ?? fileName}");
}
// Read file content
string content = System.IO.File.ReadAllText(fullPath);
string[] lines = content.Split('\n');
var analysis = new
{
success = true,
message = $"Script analysis completed for: {System.IO.Path.GetFileName(fullPath)}",
scriptPath = fullPath,
fileName = System.IO.Path.GetFileName(fullPath),
lineCount = lines.Length,
syntaxChecks = AnalyzeSyntax(content, lines),
codeQuality = AnalyzeCodeQuality(content, lines),
unitySpecific = AnalyzeUnitySpecific(content, lines),
suggestions = GenerateCodeSuggestions(content, lines)
};
return JsonConvert.SerializeObject(analysis, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] AnalyzeScript - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error analyzing script: {e.Message}");
}
}
/// <summary>
/// Check syntax errors
/// </summary>
private object AnalyzeSyntax(string content, string[] lines)
{
var issues = new List<object>();
// Check bracket matching
var bracketBalance = CheckBracketBalance(content, lines);
if (bracketBalance.Any())
issues.AddRange(bracketBalance);
// Semicolon check removed - already checked in Unity console
// Check using statements
var usingIssues = CheckUsingStatements(lines);
if (usingIssues.Any())
issues.AddRange(usingIssues);
// Check string literals
var stringIssues = CheckStringLiterals(lines);
if (stringIssues.Any())
issues.AddRange(stringIssues);
return new
{
hasIssues = issues.Any(),
issueCount = issues.Count,
issues = issues
};
}
/// <summary>
/// Analyze code quality
/// </summary>
private object AnalyzeCodeQuality(string content, string[] lines)
{
var suggestions = new List<object>();
// Detect methods that are too long
var longMethods = DetectLongMethods(lines);
if (longMethods.Any())
suggestions.AddRange(longMethods);
// Detect unused variables
var unusedVariables = DetectUnusedVariables(content, lines);
if (unusedVariables.Any())
suggestions.AddRange(unusedVariables);
// Check naming conventions
var namingIssues = CheckNamingConventions(lines);
if (namingIssues.Any())
suggestions.AddRange(namingIssues);
return new
{
qualityScore = CalculateQualityScore(suggestions.Count, lines.Length),
suggestionCount = suggestions.Count,
suggestions = suggestions
};
}
/// <summary>
/// Check Unity-specific issues
/// </summary>
private object AnalyzeUnitySpecific(string content, string[] lines)
{
var issues = new List<object>();
// Check expensive operations in Update
var updateIssues = CheckUpdateMethod(lines);
if (updateIssues.Any())
issues.AddRange(updateIssues);
// Check for potential null references
var nullRefIssues = CheckPotentialNullReferences(lines);
if (nullRefIssues.Any())
issues.AddRange(nullRefIssues);
// Check SerializeField and public fields
var fieldIssues = CheckFieldUsage(lines);
if (fieldIssues.Any())
issues.AddRange(fieldIssues);
return new
{
hasIssues = issues.Any(),
issueCount = issues.Count,
issues = issues
};
}
/// <summary>
/// Generate code improvement suggestions
/// </summary>
private object GenerateCodeSuggestions(string content, string[] lines)
{
var suggestions = new List<object>();
// Performance improvement suggestions
var perfSuggestions = GeneratePerformanceSuggestions(lines);
if (perfSuggestions.Any())
suggestions.AddRange(perfSuggestions);
// Readability improvement suggestions
var readabilitySuggestions = GenerateReadabilitySuggestions(lines);
if (readabilitySuggestions.Any())
suggestions.AddRange(readabilitySuggestions);
return new
{
suggestionCount = suggestions.Count,
categories = new
{
performance = perfSuggestions.Count(),
readability = readabilitySuggestions.Count()
},
suggestions = suggestions
};
}
/// <summary>
/// Check bracket matching
/// </summary>
private List<object> CheckBracketBalance(string content, string[] lines)
{
var issues = new List<object>();
var stack = new Stack<(char bracket, int line, int position)>();
var bracketPairs = new Dictionary<char, char> { {'(', ')'}, {'{', '}'}, {'[', ']'} };
for (int lineNum = 0; lineNum < lines.Length; lineNum++)
{
var line = lines[lineNum];
bool inString = false;
bool inComment = false;
for (int pos = 0; pos < line.Length; pos++)
{
char c = line[pos];
// Skip inside string literals
if (c == '"' && (pos == 0 || line[pos - 1] != '\\'))
inString = !inString;
// Skip inside comments
if (!inString && pos < line.Length - 1 && line.Substring(pos, 2) == "//")
inComment = true;
if (inString || inComment) continue;
if (bracketPairs.ContainsKey(c))
{
stack.Push((c, lineNum + 1, pos + 1));
}
else if (bracketPairs.ContainsValue(c))
{
if (stack.Count == 0)
{
issues.Add(new
{
type = "syntax",
severity = "error",
line = lineNum + 1,
position = pos + 1,
message = $"Unmatched closing bracket '{c}'"
});
}
else
{
var (openBracket, openLine, openPos) = stack.Pop();
if (bracketPairs[openBracket] != c)
{
issues.Add(new
{
type = "syntax",
severity = "error",
line = lineNum + 1,
position = pos + 1,
message = $"Mismatched bracket: expected '{bracketPairs[openBracket]}' but found '{c}' (opened at line {openLine})"
});
}
}
}
}
}
// Check unclosed brackets
while (stack.Count > 0)
{
var (bracket, line, pos) = stack.Pop();
issues.Add(new
{
type = "syntax",
severity = "error",
line = line,
position = pos,
message = $"Unclosed bracket '{bracket}'"
});
}
return issues;
}
/// <summary>
/// Check using statements
/// </summary>
private List<object> CheckUsingStatements(string[] lines)
{
var issues = new List<object>();
var usingLines = new List<int>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line.StartsWith("using ") && line.EndsWith(";"))
{
usingLines.Add(i);
}
}
// Warning if using statements are after class declaration
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line.StartsWith("public class ") || line.StartsWith("class "))
{
foreach (var usingLine in usingLines)
{
if (usingLine > i)
{
issues.Add(new
{
type = "structure",
severity = "warning",
line = usingLine + 1,
message = "Using statement should be at the top of the file",
suggestion = "Move using statements to the beginning of the file"
});
}
}
break;
}
}
return issues;
}
/// <summary>
/// Check string literals
/// </summary>
private List<object> CheckStringLiterals(string[] lines)
{
var issues = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i];
bool inString = false;
for (int pos = 0; pos < line.Length; pos++)
{
if (line[pos] == '"' && (pos == 0 || line[pos - 1] != '\\'))
{
if (!inString)
{
inString = true;
}
else
{
inString = false;
}
}
}
if (inString)
{
issues.Add(new
{
type = "syntax",
severity = "error",
line = i + 1,
message = "Unclosed string literal",
suggestion = "Add closing quote or escape sequence"
});
}
}
return issues;
}
/// <summary>
/// Detect long methods
/// </summary>
private List<object> DetectLongMethods(string[] lines)
{
var issues = new List<object>();
var methodStartPattern = new System.Text.RegularExpressions.Regex(@"^\s*(public|private|protected|internal)?\s*(static)?\s*\w+\s+\w+\s*\(");
int methodStart = -1;
string methodName = "";
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (methodStartPattern.IsMatch(line))
{
methodStart = i;
var match = System.Text.RegularExpressions.Regex.Match(line, @"\w+\s*\(");
if (match.Success)
{
methodName = match.Value.Replace("(", "").Trim();
}
}
else if (line == "}" && methodStart != -1)
{
int methodLength = i - methodStart + 1;
if (methodLength > 50) // Methods with 50 or more lines
{
issues.Add(new
{
type = "quality",
severity = "warning",
line = methodStart + 1,
message = $"Method '{methodName}' is too long ({methodLength} lines)",
suggestion = "Consider breaking this method into smaller methods"
});
}
methodStart = -1;
}
}
return issues;
}
/// <summary>
/// Detect unused variables
/// </summary>
private List<object> DetectUnusedVariables(string content, string[] lines)
{
var issues = new List<object>();
var variablePattern = new System.Text.RegularExpressions.Regex(@"^\s*(public|private|protected)?\s*\w+\s+(\w+)\s*[=;]");
for (int i = 0; i < lines.Length; i++)
{
var match = variablePattern.Match(lines[i]);
if (match.Success)
{
var variableName = match.Groups[2].Value;
// Check if variable is used elsewhere
var usageCount = System.Text.RegularExpressions.Regex.Matches(content, @"\b" + variableName + @"\b").Count;
if (usageCount <= 1) // Only at declaration
{
issues.Add(new
{
type = "quality",
severity = "info",
line = i + 1,
message = $"Variable '{variableName}' appears to be unused",
suggestion = "Remove unused variable or implement its usage"
});
}
}
}
return issues;
}
/// <summary>
/// Check naming conventions
/// </summary>
private List<object> CheckNamingConventions(string[] lines)
{
var issues = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
// Check class name (PascalCase)
var classMatch = System.Text.RegularExpressions.Regex.Match(line, @"class\s+(\w+)");
if (classMatch.Success)
{
var className = classMatch.Groups[1].Value;
if (!char.IsUpper(className[0]))
{
issues.Add(new
{
type = "naming",
severity = "warning",
line = i + 1,
message = $"Class name '{className}' should start with uppercase letter",
suggestion = "Use PascalCase for class names"
});
}
}
// Check method name (PascalCase)
var methodMatch = System.Text.RegularExpressions.Regex.Match(line, @"(public|private|protected)?\s*\w+\s+(\w+)\s*\(");
if (methodMatch.Success)
{
var methodName = methodMatch.Groups[2].Value;
if (!char.IsUpper(methodName[0]) && methodName != "Start" && methodName != "Update" && methodName != "Awake")
{
issues.Add(new
{
type = "naming",
severity = "info",
line = i + 1,
message = $"Method name '{methodName}' should start with uppercase letter",
suggestion = "Use PascalCase for method names"
});
}
}
}
return issues;
}
/// <summary>
/// Check expensive operations in Update method
/// </summary>
private List<object> CheckUpdateMethod(string[] lines)
{
var issues = new List<object>();
bool inUpdateMethod = false;
int updateStartLine = -1;
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line.Contains("void Update()"))
{
inUpdateMethod = true;
updateStartLine = i;
continue;
}
if (inUpdateMethod)
{
if (line == "}")
{
inUpdateMethod = false;
continue;
}
// Check for expensive operation patterns
if (line.Contains("Find(") || line.Contains("FindObjectOfType") || line.Contains("GetComponent"))
{
issues.Add(new
{
type = "performance",
severity = "warning",
line = i + 1,
message = "Expensive operation in Update method",
suggestion = "Cache references in Start() or use object pooling"
});
}
if (line.Contains("new ") && !line.Contains("Vector"))
{
issues.Add(new
{
type = "performance",
severity = "warning",
line = i + 1,
message = "Object allocation in Update method",
suggestion = "Consider object pooling or move allocation outside Update"
});
}
}
}
return issues;
}
/// <summary>
/// Check for potential null references
/// </summary>
private List<object> CheckPotentialNullReferences(string[] lines)
{
var issues = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
// GetComponent without null check
if (line.Contains("GetComponent") && !line.Contains("if") && !line.Contains("!=") && !line.Contains("== null"))
{
issues.Add(new
{
type = "nullreference",
severity = "warning",
line = i + 1,
message = "GetComponent call without null check",
suggestion = "Add null check: if (component != null)"
});
}
// GameObject.Find without null check
if (line.Contains("GameObject.Find") && !line.Contains("if") && !line.Contains("!=") && !line.Contains("== null"))
{
issues.Add(new
{
type = "nullreference",
severity = "warning",
line = i + 1,
message = "GameObject.Find call without null check",
suggestion = "Add null check: if (gameObject != null)"
});
}
}
return issues;
}
/// <summary>
/// Check field usage
/// </summary>
private List<object> CheckFieldUsage(string[] lines)
{
var issues = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
// Detect public fields
if (line.StartsWith("public ") && !line.Contains("class") && !line.Contains("void") && !line.Contains("("))
{
issues.Add(new
{
type = "encapsulation",
severity = "info",
line = i + 1,
message = "Public field detected",
suggestion = "Consider using [SerializeField] private field with public property"
});
}
}
return issues;
}
/// <summary>
/// Generate performance improvement suggestions
/// </summary>
private List<object> GeneratePerformanceSuggestions(string[] lines)
{
var suggestions = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line.Contains("string +") || line.Contains("+ string"))
{
suggestions.Add(new
{
type = "performance",
line = i + 1,
message = "String concatenation in loop",
suggestion = "Use StringBuilder for multiple string concatenations"
});
}
if (line.Contains("Vector3.Distance") && lines.Any(l => l.Contains("Update()")))
{
suggestions.Add(new
{
type = "performance",
line = i + 1,
message = "Vector3.Distance in Update",
suggestion = "Use sqrMagnitude instead of Distance for performance"
});
}
}
return suggestions;
}
/// <summary>
/// Generate readability improvement suggestions
/// </summary>
private List<object> GenerateReadabilitySuggestions(string[] lines)
{
var suggestions = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i];
// Line too long
if (line.Length > 120)
{
suggestions.Add(new
{
type = "readability",
line = i + 1,
message = "Line too long",
suggestion = "Break long lines into multiple lines"
});
}
// Lack of comments
if (line.Trim().StartsWith("public ") && line.Contains("(") && !lines[Math.Max(0, i - 1)].Trim().StartsWith("//"))
{
suggestions.Add(new
{
type = "readability",
line = i + 1,
message = "Public method without documentation",
suggestion = "Add XML documentation comments"
});
}
}
return suggestions;
}
/// <summary>
/// Calculate code quality score
/// </summary>
private int CalculateQualityScore(int issueCount, int lineCount)
{
if (lineCount == 0) return 100;
var issueRatio = (double)issueCount / lineCount;
return Math.Max(0, 100 - (int)(issueRatio * 100));
}
/// <summary>
/// Execute performance diagnostics
/// </summary>
private string AnalyzePerformance(Dictionary<string, string> parameters)
{
try
{
string scriptPath = parameters.GetValueOrDefault("scriptPath", "");
string fileName = parameters.GetValueOrDefault("fileName", "");
string target = parameters.GetValueOrDefault("target", ""); // GameObject or Component name
var analysis = new
{
success = true,
message = "Performance analysis completed",
scriptAnalysis = !string.IsNullOrEmpty(scriptPath) || !string.IsNullOrEmpty(fileName) ?
AnalyzeScriptPerformance(scriptPath, fileName) : null,
sceneAnalysis = AnalyzeScenePerformance(target),
recommendations = GeneratePerformanceRecommendations()
};
return JsonConvert.SerializeObject(analysis, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] AnalyzePerformance - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error analyzing performance: {e.Message}");
}
}
/// <summary>
/// Analyze script performance
/// </summary>
private object AnalyzeScriptPerformance(string scriptPath, string fileName)
{
if (string.IsNullOrEmpty(scriptPath) && string.IsNullOrEmpty(fileName))
return null;
string fullPath = GetScriptPath(scriptPath, fileName);
if (string.IsNullOrEmpty(fullPath) || !System.IO.File.Exists(fullPath))
return new { error = "Script file not found" };
string content = System.IO.File.ReadAllText(fullPath);
string[] lines = content.Split('\n');
var issues = new List<object>();
// Detect expensive operations in Update
issues.AddRange(DetectExpensiveUpdateOperations(lines));
// Excessive use of GameObject.Find()
issues.AddRange(DetectExcessiveFindUsage(lines));
// Inefficient coroutines
issues.AddRange(DetectInEfficientCoroutines(lines));
// String operation optimization opportunities
issues.AddRange(DetectStringPerformanceIssues(lines));
// LINQ usage warnings
issues.AddRange(DetectLinqPerformanceIssues(lines));
return new
{
scriptPath = fullPath,
fileName = System.IO.Path.GetFileName(fullPath),
issueCount = issues.Count,
performanceScore = CalculatePerformanceScore(issues.Count, lines.Length),
issues = issues
};
}
/// <summary>
/// Analyze scene-level performance
/// </summary>
private object AnalyzeScenePerformance(string target)
{
var issues = new List<object>();
GameObject targetObject = null;
if (!string.IsNullOrEmpty(target))
{
targetObject = GameObject.Find(target);
if (targetObject == null)
return new { error = $"Target object '{target}' not found" };
}
// Get GameObjects to analyze
var gameObjects = targetObject != null ?
new[] { targetObject }.Concat(targetObject.GetComponentsInChildren<Transform>().Select(t => t.gameObject)) :
UnityEngine.Object.FindObjectsOfType<GameObject>();
// Analyze draw calls
issues.AddRange(AnalyzeDrawCalls(gameObjects));
// Analyze meshes and textures
issues.AddRange(AnalyzeMeshTextures(gameObjects));
// Analyze lighting
issues.AddRange(AnalyzeLighting());
// UI optimization
issues.AddRange(AnalyzeUIPerformance(gameObjects));
return new
{
targetObject = target ?? "Entire Scene",
analyzedObjectCount = gameObjects.Count(),
issueCount = issues.Count,
issues = issues
};
}
/// <summary>
/// Detect expensive operations in Update
/// </summary>
private List<object> DetectExpensiveUpdateOperations(string[] lines)
{
var issues = new List<object>();
bool inUpdateMethod = false;
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line.Contains("void Update()") || line.Contains("void FixedUpdate()") || line.Contains("void LateUpdate()"))
{
inUpdateMethod = true;
continue;
}
if (inUpdateMethod && line == "}")
{
inUpdateMethod = false;
continue;
}
if (inUpdateMethod)
{
// Expensive operation patterns
var expensiveOperations = new[]
{
new { pattern = "Find(", message = "GameObject.Find() is expensive", suggestion = "Cache reference in Start() or use tags" },
new { pattern = "FindObjectOfType", message = "FindObjectOfType is very expensive", suggestion = "Use cached references or singleton pattern" },
new { pattern = "GetComponent", message = "GetComponent in Update can be expensive", suggestion = "Cache component reference in Start()" },
new { pattern = "Camera.main", message = "Camera.main uses FindObjectWithTag internally", suggestion = "Cache Camera reference" },
new { pattern = "new ", message = "Object allocation in Update causes GC pressure", suggestion = "Use object pooling or pre-allocate objects" },
new { pattern = "Instantiate(", message = "Instantiate in Update is very expensive", suggestion = "Use object pooling" },
new { pattern = "Resources.Load", message = "Resources.Load is expensive", suggestion = "Load in Start() or use Addressable system" },
new { pattern = "raycast", message = "Raycasting in Update can be expensive", suggestion = "Limit frequency or use coroutines" }
};
foreach (var op in expensiveOperations)
{
if (line.ToLower().Contains(op.pattern.ToLower()))
{
issues.Add(new
{
type = "performance",
severity = "warning",
line = i + 1,
message = op.message,
suggestion = op.suggestion,
impact = "high"
});
}
}
}
}
return issues;
}
/// <summary>
/// Detect excessive use of GameObject.Find()
/// </summary>
private List<object> DetectExcessiveFindUsage(string[] lines)
{
var issues = new List<object>();
int findCount = 0;
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line.Contains("GameObject.Find") || line.Contains("FindObjectOfType"))
{
findCount++;
if (findCount > 3) // If used more than 3 times
{
issues.Add(new
{
type = "performance",
severity = "warning",
line = i + 1,
message = $"Excessive use of Find operations ({findCount} times in this script)",
suggestion = "Consider using a reference manager or dependency injection",
impact = "high"
});
}
}
}
return issues;
}
/// <summary>
/// Detect inefficient coroutines
/// </summary>
private List<object> DetectInEfficientCoroutines(string[] lines)
{
var issues = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
// Usage of yield return new WaitForSeconds
if (line.Contains("new WaitForSeconds"))
{
issues.Add(new
{
type = "performance",
severity = "info",
line = i + 1,
message = "Creating new WaitForSeconds causes allocation",
suggestion = "Cache WaitForSeconds instances as static readonly fields",
impact = "low"
});
}
// Frequent use of yield return null
if (line.Contains("yield return null") || line.Contains("yield return 0"))
{
issues.Add(new
{
type = "performance",
severity = "info",
line = i + 1,
message = "Consider using WaitForEndOfFrame or WaitForFixedUpdate for specific timing",
suggestion = "Use more specific yield instructions when appropriate",
impact = "low"
});
}
}
return issues;
}
/// <summary>
/// Detect string performance issues
/// </summary>
private List<object> DetectStringPerformanceIssues(string[] lines)
{
var issues = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
// Detect string concatenation
if (line.Contains("string") && (line.Contains(" + ") || line.Contains("+=")))
{
issues.Add(new
{
type = "performance",
severity = "info",
line = i + 1,
message = "String concatenation can be inefficient",
suggestion = "Use StringBuilder for multiple concatenations",
impact = "medium"
});
}
// Frequent use of ToString()
if (line.Contains(".ToString()"))
{
issues.Add(new
{
type = "performance",
severity = "info",
line = i + 1,
message = "ToString() creates string allocations",
suggestion = "Cache strings or use string interpolation",
impact = "low"
});
}
}
return issues;
}
/// <summary>
/// Detect LINQ performance issues
/// </summary>
private List<object> DetectLinqPerformanceIssues(string[] lines)
{
var issues = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
// Detect LINQ usage
var linqMethods = new[] { ".Where(", ".Select(", ".FirstOrDefault(", ".Any(", ".Count(" };
foreach (var method in linqMethods)
{
if (line.Contains(method))
{
issues.Add(new
{
type = "performance",
severity = "info",
line = i + 1,
message = $"LINQ method {method.Replace("(", "")} can cause allocation",
suggestion = "Consider using for loops for performance-critical code",
impact = "medium"
});
break;
}
}
}
return issues;
}
/// <summary>
/// Draw Call analysis
/// </summary>
private List<object> AnalyzeDrawCalls(IEnumerable<GameObject> gameObjects)
{
var issues = new List<object>();
var renderers = gameObjects.SelectMany(go => go.GetComponentsInChildren<Renderer>()).ToArray();
var materials = renderers.SelectMany(r => r.sharedMaterials).Where(m => m != null).ToArray();
// Check for duplicate materials
var materialGroups = materials.GroupBy(m => m.name).Where(g => g.Count() > 5);
foreach (var group in materialGroups)
{
issues.Add(new
{
type = "performance",
severity = "warning",
message = $"Material '{group.Key}' used {group.Count()} times",
suggestion = "Consider material atlasing or batching",
impact = "high"
});
}
// Check number of renderers
if (renderers.Length > 100)
{
issues.Add(new
{
type = "performance",
severity = "warning",
message = $"High number of renderers: {renderers.Length}",
suggestion = "Consider LOD system or culling optimizations",
impact = "high"
});
}
return issues;
}
/// <summary>
/// Analyze meshes and textures
/// </summary>
private List<object> AnalyzeMeshTextures(IEnumerable<GameObject> gameObjects)
{
var issues = new List<object>();
foreach (var go in gameObjects)
{
var meshRenderer = go.GetComponent<MeshRenderer>();
var meshFilter = go.GetComponent<MeshFilter>();
if (meshFilter?.sharedMesh != null)
{
var mesh = meshFilter.sharedMesh;
// Detect high polygon meshes
if (mesh.triangles.Length > 10000)
{
issues.Add(new
{
type = "performance",
severity = "warning",
gameObject = go.name,
message = $"High poly mesh: {mesh.triangles.Length / 3} triangles",
suggestion = "Consider LOD or mesh optimization",
impact = "medium"
});
}
}
// Check texture size
if (meshRenderer?.sharedMaterials != null)
{
foreach (var material in meshRenderer.sharedMaterials)
{
if (material?.mainTexture != null)
{
var texture = material.mainTexture;
if (texture.width > 2048 || texture.height > 2048)
{
issues.Add(new
{
type = "performance",
severity = "info",
gameObject = go.name,
message = $"Large texture: {texture.width}x{texture.height}",
suggestion = "Consider texture compression or resolution reduction",
impact = "medium"
});
}
}
}
}
}
return issues;
}
/// <summary>
/// Analyze lighting
/// </summary>
private List<object> AnalyzeLighting()
{
var issues = new List<object>();
var lights = UnityEngine.Object.FindObjectsOfType<Light>();
// Check number of realtime lights
var realtimeLights = lights.Where(l => l.lightmapBakeType == LightmapBakeType.Realtime).ToArray();
if (realtimeLights.Length > 8)
{
issues.Add(new
{
type = "performance",
severity = "warning",
message = $"Too many realtime lights: {realtimeLights.Length}",
suggestion = "Consider using baked lighting or reducing light count",
impact = "high"
});
}
// Number of lights casting shadows
var shadowLights = lights.Where(l => l.shadows != LightShadows.None).ToArray();
if (shadowLights.Length > 4)
{
issues.Add(new
{
type = "performance",
severity = "warning",
message = $"Too many shadow-casting lights: {shadowLights.Length}",
suggestion = "Reduce shadow-casting lights or use shadow cascades",
impact = "high"
});
}
return issues;
}
/// <summary>
/// Analyze UI performance
/// </summary>
private List<object> AnalyzeUIPerformance(IEnumerable<GameObject> gameObjects)
{
var issues = new List<object>();
var canvases = gameObjects.SelectMany(go => go.GetComponentsInChildren<Canvas>()).ToArray();
foreach (var canvas in canvases)
{
// Canvas GraphicRaycaster is unnecessarily enabled
var raycaster = canvas.GetComponent<UnityEngine.UI.GraphicRaycaster>();
if (raycaster != null && raycaster.enabled)
{
var interactableComponents = canvas.GetComponentsInChildren<UnityEngine.UI.Selectable>();
if (interactableComponents.Length == 0)
{
issues.Add(new
{
type = "performance",
severity = "info",
gameObject = canvas.name,
message = "GraphicRaycaster enabled but no interactive UI elements found",
suggestion = "Disable GraphicRaycaster if not needed",
impact = "low"
});
}
}
// Too many Canvas layers
var nestedCanvases = canvas.GetComponentsInChildren<Canvas>().Length;
if (nestedCanvases > 3)
{
issues.Add(new
{
type = "performance",
severity = "warning",
gameObject = canvas.name,
message = $"Too many nested canvases: {nestedCanvases}",
suggestion = "Flatten canvas hierarchy for better performance",
impact = "medium"
});
}
}
return issues;
}
/// <summary>
/// Generate performance improvement recommendations
/// </summary>
private object GeneratePerformanceRecommendations()
{
return new
{
generalTips = new[]
{
"Use object pooling for frequently instantiated objects",
"Cache component references in Start() instead of using GetComponent in Update",
"Use coroutines or timers instead of performing heavy operations every frame",
"Consider using Unity's Job System for multi-threaded operations",
"Profile your game regularly using Unity Profiler",
"Use occlusion culling and frustum culling to reduce rendering load",
"Optimize texture sizes and use appropriate compression",
"Use LOD (Level of Detail) for complex meshes"
},
codingTips = new[]
{
"Avoid using Find() methods in Update loops",
"Use StringBuilder for multiple string concatenations",
"Cache frequently accessed components and references",
"Use events instead of polling for state changes",
"Minimize allocations in performance-critical code paths"
}
};
}
/// <summary>
/// Calculate performance score
/// </summary>
private int CalculatePerformanceScore(int issueCount, int lineCount)
{
if (lineCount == 0) return 100;
var issueRatio = (double)issueCount / lineCount * 100;
return Math.Max(0, 100 - (int)issueRatio);
}
/// <summary>
/// Check best practices
/// </summary>
private string CheckBestPractices(Dictionary<string, string> parameters)
{
try
{
string scriptPath = parameters.GetValueOrDefault("scriptPath", "");
string fileName = parameters.GetValueOrDefault("fileName", "");
if (string.IsNullOrEmpty(scriptPath) && string.IsNullOrEmpty(fileName))
{
return CreateErrorResponse("Either scriptPath or fileName must be provided");
}
string fullPath = GetScriptPath(scriptPath, fileName);
if (string.IsNullOrEmpty(fullPath) || !System.IO.File.Exists(fullPath))
{
return CreateErrorResponse($"Script file not found: {scriptPath ?? fileName}");
}
string content = System.IO.File.ReadAllText(fullPath);
string[] lines = content.Split('\n');
var violations = new List<object>();
// Check Unity official guidelines
violations.AddRange(CheckUnityOfficialGuidelines(lines));
// Security check
violations.AddRange(CheckSecurityIssues(content, lines));
// Check architecture patterns
violations.AddRange(CheckArchitecturePatterns(content, lines));
var analysis = new
{
success = true,
message = $"Best practices analysis completed for: {System.IO.Path.GetFileName(fullPath)}",
scriptPath = fullPath,
fileName = System.IO.Path.GetFileName(fullPath),
violationCount = violations.Count,
complianceScore = CalculateComplianceScore(violations.Count, lines.Length),
violations = violations,
recommendations = GetBestPracticeRecommendations()
};
return JsonConvert.SerializeObject(analysis, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] CheckBestPractices - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error checking best practices: {e.Message}");
}
}
/// <summary>
/// Check Unity official guidelines
/// </summary>
private List<object> CheckUnityOfficialGuidelines(string[] lines)
{
var violations = new List<object>();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
// Warning for Resources.Load() usage
if (line.Contains("Resources.Load"))
{
violations.Add(new
{
type = "unity_guideline",
severity = "warning",
line = i + 1,
message = "Resources.Load() usage detected",
suggestion = "Use Addressable Asset System instead of Resources folder",
guideline = "Unity recommends avoiding Resources folder for better memory management"
});
}
// Detect magic numbers
if (System.Text.RegularExpressions.Regex.IsMatch(line, @"\b\d{2,}\b") && !line.Contains("//"))
{
violations.Add(new
{
type = "coding_standard",
severity = "info",
line = i + 1,
message = "Magic number detected",
suggestion = "Use named constants or [SerializeField] for maintainability",
guideline = "Avoid hard-coded values in your scripts"
});
}
// Singleton pattern issues
if (line.Contains("static") && line.Contains("Instance") && line.Contains("="))
{
violations.Add(new
{
type = "architecture",
severity = "info",
line = i + 1,
message = "Singleton pattern detected",
suggestion = "Consider using ScriptableObject or dependency injection",
guideline = "Singletons can create tight coupling and testing difficulties"
});
}
}
return violations;
}
/// <summary>
/// Check security issues
/// </summary>
private List<object> CheckSecurityIssues(string content, string[] lines)
{
var violations = new List<object>();
// Detect hardcoded API keys
var apiKeyPatterns = new[]
{
@"[""']([A-Za-z0-9]{20,})[""']", // Long alphanumeric string
@"api[_-]?key\s*=\s*[""']([^""']+)[""']",
@"secret\s*=\s*[""']([^""']+)[""']"
};
foreach (var pattern in apiKeyPatterns)
{
var matches = System.Text.RegularExpressions.Regex.Matches(content, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
foreach (System.Text.RegularExpressions.Match match in matches)
{
var lineNumber = content.Substring(0, match.Index).Split('\n').Length;
violations.Add(new
{
type = "security",
severity = "error",
line = lineNumber,
message = "Potential API key or secret hardcoded",
suggestion = "Move sensitive data to external configuration or use Unity's Cloud Build secrets",
guideline = "Never hardcode API keys or secrets in source code"
});
}
}
// Warning for plain text storage in PlayerPrefs
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].Contains("PlayerPrefs.SetString") &&
(lines[i].ToLower().Contains("password") || lines[i].ToLower().Contains("token")))
{
violations.Add(new
{
type = "security",
severity = "warning",
line = i + 1,
message = "Sensitive data stored in PlayerPrefs without encryption",
suggestion = "Encrypt sensitive data before storing in PlayerPrefs",
guideline = "PlayerPrefs are stored in plain text and should not contain sensitive information"
});
}
}
return violations;
}
/// <summary>
/// Check architecture patterns
/// </summary>
private List<object> CheckArchitecturePatterns(string content, string[] lines)
{
var violations = new List<object>();
// Detect tight coupling
int directReferenceCount = System.Text.RegularExpressions.Regex.Matches(content, @"\.GetComponent<\w+>\(\)").Count;
if (directReferenceCount > 5)
{
violations.Add(new
{
type = "architecture",
severity = "warning",
message = $"High coupling detected: {directReferenceCount} GetComponent calls",
suggestion = "Consider using interfaces, events, or dependency injection",
guideline = "Reduce coupling between components for better maintainability"
});
}
// Detect large classes
if (lines.Length > 300)
{
violations.Add(new
{
type = "architecture",
severity = "warning",
message = $"Large class detected: {lines.Length} lines",
suggestion = "Consider breaking this class into smaller, focused classes",
guideline = "Follow Single Responsibility Principle"
});
}
return violations;
}
/// <summary>
/// Calculate compliance score
/// </summary>
private int CalculateComplianceScore(int violationCount, int lineCount)
{
if (lineCount == 0) return 100;
var violationRatio = (double)violationCount / lineCount * 50; // Stricter calculation
return Math.Max(0, 100 - (int)violationRatio);
}
/// <summary>
/// Get best practice recommendations
/// </summary>
private object GetBestPracticeRecommendations()
{
return new
{
unity_specific = new[]
{
"Use Addressable Asset System instead of Resources folder",
"Prefer composition over inheritance",
"Use ScriptableObjects for configuration data",
"Implement proper object pooling for frequently created objects",
"Use Unity Events for decoupled communication"
},
coding_standards = new[]
{
"Follow Unity's C# coding standards",
"Use meaningful variable and method names",
"Keep methods focused and small (< 20 lines)",
"Use constants instead of magic numbers",
"Add XML documentation for public methods"
},
architecture = new[]
{
"Apply SOLID principles",
"Use dependency injection for better testability",
"Implement proper error handling",
"Separate concerns with appropriate design patterns",
"Use interfaces for loose coupling"
}
};
}
/// <summary>
/// Monitor and analyze runtime errors
/// </summary>
private string MonitorRuntimeErrors(Dictionary<string, string> parameters)
{
try
{
var timeWindow = int.Parse(parameters.GetValueOrDefault("timeWindow", "60")); // In seconds
var includeWarnings = bool.Parse(parameters.GetValueOrDefault("includeWarnings", "true"));
var analyzeStackTrace = bool.Parse(parameters.GetValueOrDefault("analyzeStackTrace", "true"));
// Collect errors from console log
var errorAnalysis = CollectRuntimeErrors(timeWindow, includeWarnings);
// Analyze error patterns
var patterns = AnalyzeErrorPatterns(errorAnalysis.errors);
// Cause analysis and fix suggestions
var diagnostics = GenerateErrorDiagnostics(errorAnalysis.errors, analyzeStackTrace);
// Detect potential issues
var potentialIssues = DetectPotentialRuntimeIssues();
var result = new
{
success = true,
message = "Runtime error monitoring completed",
summary = new
{
totalErrors = errorAnalysis.errorCount,
totalWarnings = errorAnalysis.warningCount,
uniqueErrors = errorAnalysis.uniqueErrorCount,
timeWindowSeconds = timeWindow,
mostFrequentError = GetMostFrequentError(patterns)
},
errors = errorAnalysis.errors.Take(20), // Latest 20 entries
patterns = patterns,
diagnostics = diagnostics,
potentialIssues = potentialIssues,
recommendations = GenerateRuntimeRecommendations(errorAnalysis, patterns)
};
return JsonConvert.SerializeObject(result, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] MonitorRuntimeErrors - Error: {e.Message}\n{e.StackTrace}");
return CreateErrorResponse($"Error monitoring runtime errors: {e.Message}");
}
}
/// <summary>
/// Collect runtime errors from console log
/// </summary>
private (List<RuntimeError> errors, int errorCount, int warningCount, int uniqueErrorCount) CollectRuntimeErrors(int timeWindowSeconds, bool includeWarnings)
{
var errors = new List<RuntimeError>();
var currentTime = System.DateTime.Now;
var uniqueErrors = new HashSet<string>();
int errorCount = 0;
int warningCount = 0;
// Get Unity Editor log entries (using reflection)
#if UNITY_EDITOR
try
{
var logEntries = System.Type.GetType("UnityEditor.LogEntries, UnityEditor");
var logEntry = System.Type.GetType("UnityEditor.LogEntry, UnityEditor");
if (logEntries != null && logEntry != null)
{
var getCountMethod = logEntries.GetMethod("GetCount", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
var startGettingEntriesMethod = logEntries.GetMethod("StartGettingEntries", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
var getEntryInternalMethod = logEntries.GetMethod("GetEntryInternal", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
var endGettingEntriesMethod = logEntries.GetMethod("EndGettingEntries", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
if (getCountMethod != null && startGettingEntriesMethod != null && getEntryInternalMethod != null)
{
int count = (int)getCountMethod.Invoke(null, null);
startGettingEntriesMethod.Invoke(null, null);
for (int i = 0; i < count; i++)
{
var entry = Activator.CreateInstance(logEntry);
getEntryInternalMethod.Invoke(null, new object[] { i, entry });
// Get properties via reflection
var messageField = logEntry.GetField("message", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
var fileField = logEntry.GetField("file", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
var lineField = logEntry.GetField("line", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
var instanceIDField = logEntry.GetField("instanceID", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
var messageFlagsField = logEntry.GetField("messageFlags", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (messageField != null && messageFlagsField != null)
{
var message = (string)messageField.GetValue(entry);
var messageFlags = (int)messageFlagsField.GetValue(entry);
// Flag check (Error = 1, Warning = 2)
bool isError = (messageFlags & 1) != 0;
bool isWarning = (messageFlags & 2) != 0;
if (isError || (includeWarnings && isWarning))
{
var error = new RuntimeError
{
message = message ?? "",
stackTrace = "", // Stack trace needs to be retrieved separately
type = isError ? "error" : "warning",
file = fileField != null ? (string)fileField.GetValue(entry) ?? "" : "",
line = lineField != null ? (int)lineField.GetValue(entry) : 0,
instanceId = instanceIDField != null ? (int)instanceIDField.GetValue(entry) : 0,
timestamp = currentTime.AddSeconds(-timeWindowSeconds + i),
errorCategory = CategorizeError(message ?? "")
};
errors.Add(error);
uniqueErrors.Add(error.errorCategory + ":" + ExtractErrorSignature(error.message));
if (error.type == "error") errorCount++;
else warningCount++;
}
}
}
if (endGettingEntriesMethod != null)
endGettingEntriesMethod.Invoke(null, null);
}
}
}
catch (Exception e)
{
SynLog.Warn($"[NexusExecutor] Failed to access Unity log entries: {e.Message}");
// Fallback: Collect basic error information
errors.Add(new RuntimeError
{
message = "Unable to access Unity console logs directly",
stackTrace = "",
type = "info",
file = "",
line = 0,
instanceId = 0,
timestamp = currentTime,
errorCategory = "System"
});
}
#endif
return (errors, errorCount, warningCount, uniqueErrors.Count);
}
/// <summary>
/// Get most frequent error
/// </summary>
private string GetMostFrequentError(object patterns)
{
try
{
var json = JsonConvert.SerializeObject(patterns);
var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
if (dict != null && dict.ContainsKey("mostFrequent"))
{
return dict["mostFrequent"].ToString();
}
}
catch { }
return "Unknown";
}
/// <summary>
/// Runtime error data structure
/// </summary>
private class RuntimeError
{
public string message { get; set; }
public string stackTrace { get; set; }
public string type { get; set; }
public string file { get; set; }
public int line { get; set; }
public int instanceId { get; set; }
public DateTime timestamp { get; set; }
public string errorCategory { get; set; }
}
/// <summary>
/// Categorize errors
/// </summary>
private string CategorizeError(string message)
{
if (message.Contains("NullReferenceException"))
return "NullReference";
else if (message.Contains("IndexOutOfRangeException") || message.Contains("ArgumentOutOfRangeException"))
return "IndexOutOfRange";
else if (message.Contains("MissingComponentException") || message.Contains("MissingReferenceException"))
return "MissingReference";
else if (message.Contains("UnassignedReferenceException"))
return "UnassignedReference";
else if (message.Contains("InvalidOperationException"))
return "InvalidOperation";
else if (message.Contains("FormatException"))
return "FormatException";
else if (message.Contains("ArgumentException"))
return "ArgumentException";
else if (message.Contains("KeyNotFoundException"))
return "KeyNotFound";
else if (message.Contains("NotImplementedException"))
return "NotImplemented";
else if (message.Contains("StackOverflowException"))
return "StackOverflow";
else if (message.Contains("OutOfMemoryException"))
return "OutOfMemory";
else
return "Other";
}
/// <summary>
/// Extract error signature (for duplicate detection)
/// </summary>
private string ExtractErrorSignature(string message)
{
// Generate signature excluding object names and numbers
var signature = System.Text.RegularExpressions.Regex.Replace(message, @"'[^']*'", "'*'");
signature = System.Text.RegularExpressions.Regex.Replace(signature, @"\b\d+\b", "#");
signature = System.Text.RegularExpressions.Regex.Replace(signature, @"\([^)]*\)", "(*)");
return signature;
}
/// <summary>
/// Analyze error patterns
/// </summary>
private object AnalyzeErrorPatterns(List<RuntimeError> errors)
{
if (!errors.Any())
{
return new { mostFrequent = "No errors", patterns = new List<object>() };
}
// Aggregate by error category
var categoryGroups = errors.GroupBy(e => e.errorCategory)
.OrderByDescending(g => g.Count())
.Select(g => new
{
category = g.Key,
count = g.Count(),
percentage = (double)g.Count() / errors.Count * 100,
examples = g.Take(3).Select(e => e.message).ToList()
});
// Error aggregation by file
var fileGroups = errors.Where(e => !string.IsNullOrEmpty(e.file))
.GroupBy(e => e.file)
.OrderByDescending(g => g.Count())
.Take(5)
.Select(g => new
{
file = g.Key,
errorCount = g.Count(),
errorTypes = g.Select(e => e.errorCategory).Distinct().ToList()
});
// Time series pattern (burst detection)
var timeBuckets = errors.GroupBy(e => e.timestamp.ToString("yyyy-MM-dd HH:mm"))
.OrderBy(g => g.Key)
.Select(g => new
{
time = g.Key,
count = g.Count()
});
return new
{
mostFrequent = categoryGroups.FirstOrDefault()?.category ?? "Unknown",
categoryDistribution = categoryGroups,
problematicFiles = fileGroups,
timePattern = timeBuckets,
burstDetected = timeBuckets.Any(b => b.count > 10) // More than 10 errors per minute
};
}
/// <summary>
/// Generate error diagnostics and fix suggestions
/// </summary>
private List<object> GenerateErrorDiagnostics(List<RuntimeError> errors, bool analyzeStackTrace)
{
var diagnostics = new List<object>();
// Diagnose by category
var categoryGroups = errors.GroupBy(e => e.errorCategory);
foreach (var group in categoryGroups)
{
var diagnostic = new
{
errorType = group.Key,
occurrences = group.Count(),
diagnosis = GetDiagnosisForCategory(group.Key),
solutions = GetSolutionsForCategory(group.Key),
affectedFiles = group.Where(e => !string.IsNullOrEmpty(e.file))
.Select(e => new { file = e.file, line = e.line })
.Distinct()
.Take(5)
.ToList()
};
diagnostics.Add(diagnostic);
}
// Stack trace analysis
if (analyzeStackTrace)
{
var stackTracePatterns = AnalyzeStackTraces(errors);
diagnostics.Add(new
{
errorType = "StackTraceAnalysis",
patterns = stackTracePatterns
});
}
return diagnostics;
}
/// <summary>
/// Get diagnosis for error category
/// </summary>
private string GetDiagnosisForCategory(string category)
{
switch (category)
{
case "NullReference":
return "Object references are not properly initialized. This often happens when GameObjects are destroyed or components are missing.";
case "IndexOutOfRange":
return "Array or list is being accessed with an invalid index. Check array bounds before accessing elements.";
case "MissingReference":
return "Referenced objects have been destroyed or removed from the scene. This commonly happens with prefab instances.";
case "UnassignedReference":
return "Required references are not assigned in the Inspector. Check all public/SerializeField references.";
case "InvalidOperation":
return "Operations are being performed in invalid states or sequences.";
default:
return "Various runtime errors detected that need specific investigation.";
}
}
/// <summary>
/// Get solutions for error category
/// </summary>
private List<string> GetSolutionsForCategory(string category)
{
switch (category)
{
case "NullReference":
return new List<string>
{
"Add null checks before accessing objects: if (obj != null)",
"Initialize references in Awake() or Start()",
"Use [RequireComponent] attribute for dependent components",
"Implement safe navigation with ?. operator (C# 6.0+)"
};
case "IndexOutOfRange":
return new List<string>
{
"Check array length before access: if (index < array.Length)",
"Use for loops instead of hardcoded indices",
"Validate input parameters that are used as indices",
"Consider using List<T> with bounds checking"
};
case "MissingReference":
return new List<string>
{
"Check if object exists before use: if (gameObject != null)",
"Use object pooling to avoid frequent instantiation/destruction",
"Implement proper cleanup in OnDestroy()",
"Consider using events for decoupled communication"
};
case "UnassignedReference":
return new List<string>
{
"Assign all references in the Inspector",
"Use [RequireComponent] for automatic component assignment",
"Implement fallback logic for missing references",
"Add validation in OnValidate() method"
};
default:
return new List<string> { "Review error messages for specific solutions" };
}
}
/// <summary>
/// Analyze stack traces
/// </summary>
private List<object> AnalyzeStackTraces(List<RuntimeError> errors)
{
var patterns = new List<object>();
// Detect common call paths
var stackTraces = errors.Where(e => !string.IsNullOrEmpty(e.stackTrace))
.Select(e => e.stackTrace.Split('\n').Take(5)) // Only top 5 lines
.ToList();
if (stackTraces.Any())
{
// Most frequently occurring call patterns
var commonPaths = stackTraces
.SelectMany(st => st)
.Where(line => line.Contains(".cs:"))
.GroupBy(line => ExtractMethodFromStackLine(line))
.Where(g => g.Count() > 1)
.OrderByDescending(g => g.Count())
.Take(5)
.Select(g => new
{
method = g.Key,
count = g.Count(),
suggestion = $"Method '{g.Key}' appears in {g.Count()} error stack traces"
});
patterns.AddRange(commonPaths);
}
return patterns;
}
/// <summary>
/// Extract method name from stack trace line
/// </summary>
private string ExtractMethodFromStackLine(string stackLine)
{
var match = System.Text.RegularExpressions.Regex.Match(stackLine, @"(\w+\.\w+)\s*\(");
return match.Success ? match.Groups[1].Value : stackLine.Split(' ')[0];
}
/// <summary>
/// Detect potential runtime issues
/// </summary>
private List<object> DetectPotentialRuntimeIssues()
{
var issues = new List<object>();
// Analyze objects in scene
var allGameObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();
// 1. Inactive objects with heavy components
var inactiveHeavyObjects = allGameObjects
.Where(go => !go.activeInHierarchy &&
(go.GetComponent<Camera>() != null ||
go.GetComponent<Light>() != null ||
go.GetComponentsInChildren<Renderer>().Length > 10))
.Take(5)
.Select(go => new
{
type = "InactiveHeavyObject",
gameObject = go.name,
message = "Inactive object with heavy components",
suggestion = "Consider removing components from inactive objects"
});
issues.AddRange(inactiveHeavyObjects);
// 2. Excessively deep hierarchies
var deepHierarchies = allGameObjects
.Where(go => GetHierarchyDepth(go.transform) > 10)
.Take(5)
.Select(go => new
{
type = "DeepHierarchy",
gameObject = go.name,
depth = GetHierarchyDepth(go.transform),
message = "Extremely deep hierarchy detected",
suggestion = "Flatten hierarchy for better performance"
});
issues.AddRange(deepHierarchies);
// 3. Potential circular references
var components = allGameObjects.SelectMany(go => go.GetComponents<MonoBehaviour>());
foreach (var component in components.Take(50)) // Only check first 50
{
var fields = component.GetType().GetFields(
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
foreach (var field in fields)
{
if (typeof(UnityEngine.Object).IsAssignableFrom(field.FieldType))
{
var value = field.GetValue(component) as UnityEngine.Object;
if (value != null && value == component)
{
issues.Add(new
{
type = "SelfReference",
gameObject = component.gameObject.name,
component = component.GetType().Name,
field = field.Name,
message = "Component has self-reference",
suggestion = "Remove self-references to prevent circular dependencies"
});
}
}
}
}
return issues;
}
/// <summary>
/// Get hierarchy depth
/// </summary>
private int GetHierarchyDepth(Transform transform)
{
int depth = 0;
var current = transform;
while (current.parent != null)
{
depth++;
current = current.parent;
}
return depth;
}
/// <summary>
/// Generate runtime recommendations
/// </summary>
private object GenerateRuntimeRecommendations(
(List<RuntimeError> errors, int errorCount, int warningCount, int uniqueErrorCount) errorAnalysis,
object patterns)
{
var recommendations = new List<string>();
// Recommendations based on error count
if (errorAnalysis.errorCount > 50)
{
recommendations.Add("High error count detected. Consider implementing comprehensive error handling.");
}
// When there are many NullReferences
var nullRefCount = errorAnalysis.errors.Count(e => e.errorCategory == "NullReference");
if (nullRefCount > errorAnalysis.errorCount * 0.3)
{
recommendations.Add("Many NullReferenceExceptions detected. Implement defensive programming with null checks.");
recommendations.Add("Consider using [RequireComponent] attributes and initialization validation.");
}
// When concentrated in same file
var fileGroups = errorAnalysis.errors.GroupBy(e => e.file).Where(g => g.Count() > 5);
if (fileGroups.Any())
{
recommendations.Add($"Errors concentrated in specific files: {string.Join(", ", fileGroups.Select(g => g.Key).Take(3))}");
recommendations.Add("Focus debugging efforts on these problematic files.");
}
// General recommendations
recommendations.AddRange(new[]
{
"Enable 'Error Pause' in Console to break on errors during play mode",
"Use try-catch blocks for operations that might fail",
"Implement proper logging for better error tracking",
"Consider using Unity Test Framework for automated testing",
"Profile memory usage to detect potential memory leaks"
});
return new
{
immediate = recommendations.Take(3),
general = recommendations.Skip(3)
};
}
/// <summary>
/// Automatically attach UI components
/// </summary>
private string AutoAttachUI(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target", "");
var componentName = parameters.GetValueOrDefault("component", "");
if (string.IsNullOrEmpty(targetName))
{
return CreateErrorResponse("Target GameObject name must be provided");
}
GameObject target = GameObject.Find(targetName);
if (target == null)
{
return CreateErrorResponse($"GameObject '{targetName}' not found");
}
// Get component
Component component = null;
if (!string.IsNullOrEmpty(componentName))
{
component = target.GetComponent(componentName);
}
else
{
// Find first custom component that inherits MonoBehaviour
var components = target.GetComponents<MonoBehaviour>();
foreach (var comp in components)
{
var type = comp.GetType();
if (type.Namespace != "UnityEngine" && type.Namespace != "UnityEngine.UI")
{
component = comp;
componentName = type.Name;
break;
}
}
}
if (component == null)
{
return CreateErrorResponse($"No suitable component found on '{targetName}'");
}
// Record Undo
UnityEditor.Undo.RecordObject(component, "Auto Attach UI");
var attachedFields = new List<string>();
var failedFields = new List<string>();
// Get fields via reflection
var componentType = component.GetType();
var fields = componentType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
SynLog.Info($"[AutoAttachUI] Found {fields.Length} fields in {componentType.Name}");
foreach (var field in fields)
{
// Only target SerializeField or public fields
bool isSerializable = field.IsPublic || field.GetCustomAttribute<SerializeField>() != null;
if (!isSerializable || field.IsStatic)
{
SynLog.Info($"[AutoAttachUI] Skipping field {field.Name} - Serializable: {isSerializable}, Static: {field.IsStatic}");
continue;
}
// Only target types that inherit UnityEngine.Object (GameObject, Component, ScriptableObject, etc.)
var fieldType = field.FieldType;
if (!typeof(UnityEngine.Object).IsAssignableFrom(fieldType))
{
// For arrays or lists, check element type
if (fieldType.IsArray)
{
var elementType = fieldType.GetElementType();
if (!typeof(UnityEngine.Object).IsAssignableFrom(elementType))
{
continue;
}
}
else if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List<>))
{
var elementType = fieldType.GetGenericArguments()[0];
if (!typeof(UnityEngine.Object).IsAssignableFrom(elementType))
{
continue;
}
}
else
{
continue;
}
}
SynLog.Info($"[AutoAttachUI] Processing field {field.Name} of type {fieldType.Name}");
// Skip if value is already set
var currentValue = field.GetValue(component);
if (currentValue != null)
{
SynLog.Info($"[AutoAttachUI] Field {field.Name} already has value: {currentValue}");
continue;
}
// Generate search names from field name
var searchNames = GenerateSearchNames(field.Name);
bool attached = false;
foreach (var searchName in searchNames)
{
// Search within scene
GameObject foundObj = GameObject.Find(searchName);
if (foundObj != null)
{
// For GameObject field
if (fieldType == typeof(GameObject))
{
field.SetValue(component, foundObj);
attachedFields.Add($"{field.Name} -> {foundObj.name}");
attached = true;
break;
}
// For Transform field
else if (fieldType == typeof(Transform))
{
field.SetValue(component, foundObj.transform);
attachedFields.Add($"{field.Name} -> {foundObj.name} (Transform)");
attached = true;
break;
}
// For other component types
else
{
var targetComponent = foundObj.GetComponent(fieldType);
if (targetComponent != null)
{
field.SetValue(component, targetComponent);
attachedFields.Add($"{field.Name} -> {foundObj.name} ({fieldType.Name})");
attached = true;
break;
}
}
}
}
if (!attached)
{
failedFields.Add($"{field.Name} ({fieldType.Name})");
}
}
// Save changes with SetDirty
UnityEditor.EditorUtility.SetDirty(component);
UnityEditor.EditorUtility.SetDirty(target);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"UI auto-attach completed for {componentName} on {targetName}",
attached = attachedFields.Count,
failed = failedFields.Count,
attachedFields = attachedFields,
failedFields = failedFields
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Error in AutoAttachUI: {e.Message}");
}
}
/// <summary>
/// Generate search candidate names from field name
/// </summary>
private List<string> GenerateSearchNames(string fieldName)
{
var searchNames = new List<string>();
// Name as-is
searchNames.Add(fieldName);
// Remove "Text" suffix
if (fieldName.EndsWith("Text"))
{
searchNames.Add(fieldName.Substring(0, fieldName.Length - 4));
}
// Convert camelCase to space-separated
var spacedName = System.Text.RegularExpressions.Regex.Replace(
fieldName,
@"(?<!^)(?=[A-Z])",
" "
);
searchNames.Add(spacedName);
// Replace underscores with spaces
searchNames.Add(fieldName.Replace("_", " "));
// Capitalize first letter
searchNames.Add(char.ToUpper(fieldName[0]) + fieldName.Substring(1));
// Common UI name patterns
if (fieldName.ToLower().Contains("score"))
{
searchNames.AddRange(new[] { "ScoreText", "Score Text", "Score", "ScoreLabel" });
}
if (fieldName.ToLower().Contains("level"))
{
searchNames.AddRange(new[] { "LevelText", "Level Text", "Level", "LevelLabel" });
}
if (fieldName.ToLower().Contains("lines"))
{
searchNames.AddRange(new[] { "LinesText", "Lines Text", "Lines", "LinesLabel" });
}
if (fieldName.ToLower().Contains("next"))
{
searchNames.AddRange(new[] { "NextPieceText", "Next Piece Text", "Next", "NextText" });
}
if (fieldName.ToLower().Contains("hold"))
{
searchNames.AddRange(new[] { "HoldText", "Hold Text", "Hold", "HoldLabel" });
}
// Remove duplicates
return searchNames.Distinct().ToList();
}
/// <summary>
/// Get script path (improved version)
/// </summary>
private string GetScriptPath(string scriptPath, string fileName)
{
SynLog.Info($"[NexusExecutor] GetScriptPath - scriptPath: '{scriptPath}', fileName: '{fileName}'");
// 1. When direct path is specified
if (!string.IsNullOrEmpty(scriptPath))
{
// Try multiple path patterns
string[] pathPatterns = new string[]
{
scriptPath,
scriptPath.StartsWith("Assets/") ? scriptPath : $"Assets/{scriptPath}",
$"Assets/Scripts/{scriptPath}",
$"Assets/{scriptPath}.cs",
$"Assets/Scripts/{scriptPath}.cs"
};
foreach (string pattern in pathPatterns)
{
if (System.IO.File.Exists(pattern))
{
SynLog.Info($"[NexusExecutor] Found script at: {pattern}");
return pattern;
}
}
SynLog.Warn($"[NexusExecutor] Script not found with path patterns for: {scriptPath}");
}
// 2. Search by file name
if (!string.IsNullOrEmpty(fileName))
{
// Ensure .cs is included
string searchName = fileName.EndsWith(".cs") ? fileName.Replace(".cs", "") : fileName;
SynLog.Info($"[NexusExecutor] Searching for script: {searchName}");
// More flexible search with AssetDatabase
string[] searchPatterns = new string[]
{
$"{searchName} t:Script",
$"t:Script {searchName}",
searchName
};
foreach (string pattern in searchPatterns)
{
string[] foundFiles = AssetDatabase.FindAssets(pattern);
if (foundFiles.Length > 0)
{
// Prioritize exact match
foreach (string guid in foundFiles)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
string fileNameOnly = System.IO.Path.GetFileNameWithoutExtension(path);
if (string.Equals(fileNameOnly, searchName, StringComparison.OrdinalIgnoreCase))
{
SynLog.Info($"[NexusExecutor] Found exact match: {path}");
return path;
}
}
// Use first result if no exact match
string foundPath = AssetDatabase.GUIDToAssetPath(foundFiles[0]);
SynLog.Info($"[NexusExecutor] Found partial match: {foundPath}");
return foundPath;
}
}
// If not found in AssetDatabase, search directly in file system
SynLog.Info("[NexusExecutor] AssetDatabase search failed, trying file system search");
try
{
string[] allCsFiles = System.IO.Directory.GetFiles("Assets", "*.cs", System.IO.SearchOption.AllDirectories);
// Find exact match
foreach (string file in allCsFiles)
{
string fileNameOnly = System.IO.Path.GetFileNameWithoutExtension(file);
if (string.Equals(fileNameOnly, searchName, StringComparison.OrdinalIgnoreCase))
{
string unityPath = file.Replace("\\", "/");
SynLog.Info($"[NexusExecutor] Found file system match: {unityPath}");
return unityPath;
}
}
// Try partial match
foreach (string file in allCsFiles)
{
string fileNameOnly = System.IO.Path.GetFileNameWithoutExtension(file);
if (fileNameOnly.IndexOf(searchName, StringComparison.OrdinalIgnoreCase) >= 0)
{
string unityPath = file.Replace("\\", "/");
SynLog.Warn($"[NexusExecutor] Found partial file system match: {unityPath}");
return unityPath;
}
}
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] File system search error: {e.Message}");
}
}
Debug.LogError($"[NexusExecutor] Failed to find script - scriptPath: '{scriptPath}', fileName: '{fileName}'");
return null;
}
/// <summary>
/// Get detailed information for script file search
/// </summary>
private string GetScriptSearchInfo(string scriptPath, string fileName)
{
var info = new System.Text.StringBuilder();
info.AppendLine("=== Script Search Debug Info ===");
info.AppendLine($"- Provided scriptPath: '{scriptPath}'");
info.AppendLine($"- Provided fileName: '{fileName}'");
info.AppendLine($"- Current working directory: {System.IO.Directory.GetCurrentDirectory()}");
// Path pattern check
if (!string.IsNullOrEmpty(scriptPath))
{
info.AppendLine("\n[Path Pattern Check]");
string[] pathPatterns = new string[]
{
scriptPath,
scriptPath.StartsWith("Assets/") ? scriptPath : $"Assets/{scriptPath}",
$"Assets/Scripts/{scriptPath}",
$"Assets/{scriptPath}.cs",
$"Assets/Scripts/{scriptPath}.cs"
};
foreach (string pattern in pathPatterns)
{
bool exists = System.IO.File.Exists(pattern);
info.AppendLine($" - {pattern}: {(exists ? "EXISTS" : "not found")}");
}
}
if (!string.IsNullOrEmpty(fileName))
{
string searchName = fileName.EndsWith(".cs") ? fileName.Replace(".cs", "") : fileName;
info.AppendLine("\n[AssetDatabase Search]");
// Try multiple search patterns
string[] searchPatterns = new string[]
{
$"{searchName} t:Script",
$"t:Script {searchName}",
searchName
};
foreach (string pattern in searchPatterns)
{
string[] foundFiles = AssetDatabase.FindAssets(pattern);
info.AppendLine($" Pattern '{pattern}': {foundFiles.Length} results");
for (int i = 0; i < foundFiles.Length && i < 3; i++)
{
string path = AssetDatabase.GUIDToAssetPath(foundFiles[i]);
info.AppendLine($" [{i}] {path}");
}
}
// File system search
info.AppendLine("\n[File System Search]");
try
{
string[] allCsFiles = System.IO.Directory.GetFiles("Assets", "*.cs", System.IO.SearchOption.AllDirectories);
// Exact match
var exactMatches = allCsFiles.Where(f =>
string.Equals(System.IO.Path.GetFileNameWithoutExtension(f), searchName, StringComparison.OrdinalIgnoreCase)
).Take(5).ToArray();
// Partial match
var partialMatches = allCsFiles.Where(f =>
System.IO.Path.GetFileNameWithoutExtension(f).IndexOf(searchName, StringComparison.OrdinalIgnoreCase) >= 0
).Take(5).ToArray();
info.AppendLine($" - Total .cs files in Assets: {allCsFiles.Length}");
info.AppendLine($" - Exact matches for '{searchName}': {exactMatches.Length}");
foreach (string file in exactMatches)
{
info.AppendLine($" {file}");
}
if (partialMatches.Length > exactMatches.Length)
{
info.AppendLine($" - Partial matches: {partialMatches.Length - exactMatches.Length}");
foreach (string file in partialMatches.Except(exactMatches).Take(3))
{
info.AppendLine($" {file}");
}
}
}
catch (Exception e)
{
info.AppendLine($" - File system search error: {e.Message}");
}
}
// Recommended solutions
info.AppendLine("\n[Recommended Solutions]");
info.AppendLine("1. Use full path from Assets folder (e.g., 'Assets/Scripts/MyScript.cs')");
info.AppendLine("2. Use exact file name without extension (e.g., 'MyScript')");
info.AppendLine("3. Ensure the script file exists and is imported in Unity");
return info.ToString();
}
/// <summary>
/// Simple syntax validation for C# scripts
/// </summary>
private bool ValidateCSharpSyntax(string code, out string error)
{
error = null;
try
{
// Basic syntax check
int openBraces = 0;
int closeBraces = 0;
int openParentheses = 0;
int closeParentheses = 0;
int openBrackets = 0;
int closeBrackets = 0;
bool inString = false;
bool inChar = false;
bool inComment = false;
bool inMultiLineComment = false;
// For class structure check
var classStack = new Stack<string>();
bool foundNamespace = false;
for (int i = 0; i < code.Length; i++)
{
char c = code[i];
char prev = i > 0 ? code[i - 1] : '\0';
char next = i < code.Length - 1 ? code[i + 1] : '\0';
// Comment processing
if (!inString && !inChar)
{
if (c == '/' && next == '/')
{
inComment = true;
}
else if (c == '/' && next == '*')
{
inMultiLineComment = true;
i++; // Skip next char
continue;
}
else if (inMultiLineComment && c == '*' && next == '/')
{
inMultiLineComment = false;
i++; // Skip next char
continue;
}
}
if (inComment && c == '\n')
{
inComment = false;
}
if (inComment || inMultiLineComment)
{
continue;
}
// String and character literal processing
if (c == '"' && prev != '\\')
{
inString = !inString;
}
else if (c == '\'' && prev != '\\' && !inString)
{
inChar = !inChar;
}
// Count brackets (ignore inside strings)
if (!inString && !inChar)
{
switch (c)
{
case '{': openBraces++; break;
case '}': closeBraces++; break;
case '(': openParentheses++; break;
case ')': closeParentheses++; break;
case '[': openBrackets++; break;
case ']': closeBrackets++; break;
}
}
}
// Balance check
if (openBraces != closeBraces)
{
error = $"Unbalanced braces: {{ = {openBraces}, }} = {closeBraces}";
return false;
}
if (openParentheses != closeParentheses)
{
error = $"Unbalanced parentheses: ( = {openParentheses}, ) = {closeParentheses}";
return false;
}
if (openBrackets != closeBrackets)
{
error = $"Unbalanced brackets: [ = {openBrackets}, ] = {closeBrackets}";
return false;
}
// Basic class structure check
var lines = code.Split('\n');
int braceLevel = 0;
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i].Trim();
// Detect namespace
if (line.StartsWith("namespace "))
{
foundNamespace = true;
}
// Detect class
if (line.Contains("class ") && !line.StartsWith("//"))
{
// Check brace level before class
if (foundNamespace && braceLevel < 1)
{
error = $"Class declaration outside namespace braces at line {i + 1}";
return false;
}
}
// Track brace level
foreach (char c in line)
{
if (c == '{') braceLevel++;
else if (c == '}') braceLevel--;
if (braceLevel < 0)
{
error = $"Extra closing brace detected at line {i + 1}";
return false;
}
}
}
// Semicolon check (basic statement ending)
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i].Trim();
// Exclude empty lines, comments, bracket-ending lines, labels, etc.
if (string.IsNullOrWhiteSpace(line) ||
line.StartsWith("//") ||
line.EndsWith("{") ||
line.EndsWith("}") ||
line.EndsWith(":") ||
line.StartsWith("#") || // Preprocessor directive
line.StartsWith("[") || // Attribute
line.Contains("=>") || // Lambda expression
line.EndsWith(",")) // Enum, etc.
{
continue;
}
// Check for tokens that indicate statement ending
if (!line.EndsWith(";") &&
!line.EndsWith("}") &&
!line.Contains("class ") &&
!line.Contains("interface ") &&
!line.Contains("enum ") &&
!line.Contains("struct ") &&
!line.Contains("namespace "))
{
// Check if next line continues
if (i + 1 < lines.Length)
{
string nextLine = lines[i + 1].Trim();
if (!nextLine.StartsWith(".") && !nextLine.StartsWith("&&") && !nextLine.StartsWith("||"))
{
// Warning level (not treated as error)
SynLog.Warn($"[Syntax Check] Line {i + 1}: Possible missing semicolon: {line}");
}
}
}
}
return true;
}
catch (Exception e)
{
error = $"Error during syntax validation: {e.Message}";
return false;
}
}
/// <summary>
/// Check if class structure is preserved before and after editing
/// </summary>
private bool ValidateClassStructurePreservation(string originalCode, string newCode, out string error)
{
error = null;
try
{
// Extract class definitions
var originalClasses = ExtractClassDefinitions(originalCode);
var newClasses = ExtractClassDefinitions(newCode);
// Check if classes were deleted
foreach (var originalClass in originalClasses)
{
if (!newClasses.Any(c => c.name == originalClass.name))
{
error = $"Class '{originalClass.name}' was accidentally removed";
return false;
}
}
// Check if namespace structure is preserved
var originalNamespaces = ExtractNamespaces(originalCode);
var newNamespaces = ExtractNamespaces(newCode);
if (originalNamespaces.Count != newNamespaces.Count)
{
error = "Namespace structure was altered";
return false;
}
return true;
}
catch (Exception e)
{
error = $"Error validating class structure: {e.Message}";
return false;
}
}
private List<(string name, int startLine)> ExtractClassDefinitions(string code)
{
var classes = new List<(string name, int startLine)>();
var lines = code.Split('\n');
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i].Trim();
if (line.Contains("class ") && !line.StartsWith("//"))
{
var match = System.Text.RegularExpressions.Regex.Match(line, @"class\s+(\w+)");
if (match.Success)
{
classes.Add((match.Groups[1].Value, i + 1));
}
}
}
return classes;
}
private List<string> ExtractNamespaces(string code)
{
var namespaces = new List<string>();
var lines = code.Split('\n');
foreach (var line in lines)
{
var trimmed = line.Trim();
if (trimmed.StartsWith("namespace ") && !trimmed.StartsWith("//"))
{
var match = System.Text.RegularExpressions.Regex.Match(trimmed, @"namespace\s+([\w.]+)");
if (match.Success)
{
namespaces.Add(match.Groups[1].Value);
}
}
}
return namespaces;
}
/// <summary>
/// Create success response
/// </summary>
private string CreateSuccessResponse(string message)
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = message
}
}
});
}
/// <summary>
/// Create error response
/// </summary>
private string CreateErrorResponse(string message)
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = message
}
}
});
}
private string CreatePrefab(Dictionary<string, string> parameters)
{
// Check path parameter with priority
var path = parameters.GetValueOrDefault("path");
// Support multiple candidates for name parameter
var name = parameters.GetValueOrDefault("name") ??
parameters.GetValueOrDefault("prefabName") ??
parameters.GetValueOrDefault("fileName");
// Support multiple parameter names
var sourceName = parameters.GetValueOrDefault("source") ??
parameters.GetValueOrDefault("target") ??
parameters.GetValueOrDefault("gameObject") ??
parameters.GetValueOrDefault("object") ??
parameters.GetValueOrDefault("sourceName") ??
parameters.GetValueOrDefault("objectName");
GameObject source = null;
SynLog.Info($"[CreatePrefab] === PARAMETER ANALYSIS ===");
SynLog.Info($"[CreatePrefab] Total parameters: {parameters.Count}");
foreach (var param in parameters)
{
SynLog.Info($"[CreatePrefab] Parameter: '{param.Key}' = '{param.Value}'");
}
SynLog.Info($"[CreatePrefab] Extracted path: '{path}'");
SynLog.Info($"[CreatePrefab] Extracted name: '{name}'");
SynLog.Info($"[CreatePrefab] Extracted sourceName: '{sourceName}'");
SynLog.Info($"[CreatePrefab] === END ANALYSIS ===");
// Path analysis
string folderPath = "Assets/Synaptic_Generated";
string fileName = name;
// Prioritize path parameter if specified
if (!string.IsNullOrEmpty(path))
{
if (path.Contains("/"))
{
var lastSlash = path.LastIndexOf('/');
folderPath = path.Substring(0, lastSlash);
fileName = path.Substring(lastSlash + 1);
// Remove .prefab extension
if (fileName.EndsWith(".prefab"))
{
fileName = fileName.Substring(0, fileName.Length - 7);
}
SynLog.Info($"[CreatePrefab] Parsed path - folder: '{folderPath}', file: '{fileName}'");
}
else
{
fileName = path;
}
}
// Use sourceName or default name if name not specified
else if (string.IsNullOrEmpty(name))
{
if (!string.IsNullOrEmpty(sourceName))
{
fileName = sourceName + "Prefab";
SynLog.Info($"[CreatePrefab] Auto-generated name from source: '{fileName}'");
}
else
{
fileName = "NewPrefab";
SynLog.Info($"[CreatePrefab] Using default name: '{fileName}'");
}
}
if (!string.IsNullOrEmpty(sourceName))
{
if (sourceName == "last")
{
source = lastCreatedObject;
SynLog.Info($"[CreatePrefab] Using last created object: {source?.name ?? "null"}");
}
else
{
SynLog.Info($"[CreatePrefab] Searching for source object: {sourceName}");
// Search using GetTargetGameObject
var tempParams = new Dictionary<string, string> { {"target", sourceName} };
source = GetTargetGameObject(tempParams);
SynLog.Info($"[CreatePrefab] Found source object: {source?.name ?? "null"}");
// Additionally, search with different parameter name
if (source == null)
{
tempParams = new Dictionary<string, string> { {"name", sourceName} };
source = GetTargetGameObject(tempParams);
SynLog.Info($"[CreatePrefab] Fallback search by name: {source?.name ?? "null"}");
}
// If not in scene, attempt to instantiate from existing prefab
if (source == null)
{
var existingPrefabPath = $"Assets/Synaptic_Generated/{sourceName}.prefab";
if (System.IO.File.Exists(existingPrefabPath))
{
var prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(existingPrefabPath);
if (prefabAsset != null)
{
// Case: Instantiate
if (parameters.TryGetValue("instantiate", out var instantiateValue) &&
instantiateValue.ToLower() == "true")
{
source = PrefabUtility.InstantiatePrefab(prefabAsset) as GameObject;
if (source != null)
{
source.name = name;
// Set position
if (parameters.TryGetValue("position", out var posStr))
{
Vector3 position = Vector3.zero;
if (posStr.StartsWith("{") && posStr.EndsWith("}"))
{
// JSON format
try
{
var posJson = JsonConvert.DeserializeObject<Dictionary<string, float>>(posStr);
position = new Vector3(
posJson.GetValueOrDefault("x", 0),
posJson.GetValueOrDefault("y", 0),
posJson.GetValueOrDefault("z", 0)
);
}
catch { }
}
else
{
// Comma-separated string
var parts = posStr.Split(',');
if (parts.Length >= 3 &&
float.TryParse(parts[0], out var x) &&
float.TryParse(parts[1], out var y) &&
float.TryParse(parts[2], out var z))
{
position = new Vector3(x, y, z);
}
}
source.transform.position = position;
}
lastCreatedObject = source;
return $"Instantiated prefab: {name} from {sourceName}";
}
}
else
{
// Case: Create new prefab based on existing prefab
source = PrefabUtility.InstantiatePrefab(prefabAsset) as GameObject;
}
}
}
}
}
}
else
{
// If source parameter not specified, use lastCreatedObject
source = lastCreatedObject;
SynLog.Info($"[CreatePrefab] No source specified, using last created object: {source?.name ?? "null"}");
}
if (source == null)
{
var allObjects = GameObject.FindObjectsOfType<GameObject>();
SynLog.Info($"[CreatePrefab] Available objects in scene: {string.Join(", ", allObjects.Take(10).Select(o => o.name))}");
// Last resort: Search directly from available objects
if (!string.IsNullOrEmpty(sourceName))
{
source = allObjects.FirstOrDefault(o => o.name == sourceName);
if (source == null)
{
source = allObjects.FirstOrDefault(o => o.name.Contains(sourceName));
}
SynLog.Info($"[CreatePrefab] Direct search result: {source?.name ?? "null"}");
}
if (source == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"No source GameObject found: {sourceName ?? "null"}",
availableObjects = allObjects.Take(10).Select(o => o.name).ToArray(),
searchedName = sourceName,
lastCreatedObject = lastCreatedObject?.name
}, Formatting.Indented);
}
}
// Get folder path from parameters (prioritize if already set by path analysis)
if (parameters.ContainsKey("folder") || parameters.ContainsKey("savePath") || parameters.ContainsKey("path"))
{
var paramFolderPath = parameters.GetValueOrDefault("folder") ??
parameters.GetValueOrDefault("savePath") ??
parameters.GetValueOrDefault("path");
if (!string.IsNullOrEmpty(paramFolderPath))
{
folderPath = paramFolderPath;
if (!folderPath.StartsWith("Assets/"))
{
folderPath = "Assets/" + folderPath.TrimStart('/');
}
SynLog.Info($"[CreatePrefab] Using parameter folder path: '{folderPath}'");
}
}
// Check and add .prefab extension
if (!fileName.EndsWith(".prefab"))
{
fileName += ".prefab";
}
var prefabPath = $"{folderPath}/{fileName}";
SynLog.Info($"[CreatePrefab] Final prefab path: '{prefabPath}'");
SynLog.Info($"[CreatePrefab] Folder: '{folderPath}', File: '{fileName}'");
// Auto-create folder if it doesn't exist
if (!AssetDatabase.IsValidFolder(folderPath))
{
SynLog.Info($"[Synaptic] Folder '{folderPath}' does not exist. Creating folder...");
// Create folder using CreateFolder method
var createFolderParams = new Dictionary<string, string> { ["path"] = folderPath };
var createResult = CreateFolder(createFolderParams);
try
{
var result = JsonConvert.DeserializeObject<Dictionary<string, object>>(createResult);
if (!(bool)result["success"])
{
return $"Error: Failed to create folder '{folderPath}': {result["error"]}";
}
SynLog.Info($"[Synaptic] Successfully created folder: {folderPath}");
}
catch (Exception e)
{
return $"Error: Failed to parse folder creation result: {e.Message}";
}
}
var prefab = PrefabUtility.SaveAsPrefabAsset(source, prefabPath);
// Delete temporary object if it was an instantiated prefab
if (parameters.TryGetValue("instantiate", out var instantiate) &&
instantiate.ToLower() == "true" &&
!parameters.ContainsKey("keep_instance"))
{
UnityEditor.Undo.DestroyObjectImmediate(source);
}
if (prefab != null)
{
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created prefab '{name}' successfully",
prefabName = name,
prefabPath = prefabPath,
sourceName = source.name,
sourceType = source.GetType().Name
});
}
else
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Failed to create prefab '{name}' at {prefabPath}"
});
}
}
private string SetupPhysics(Dictionary<string, string> parameters)
{
try
{
var target = parameters.GetValueOrDefault("target");
GameObject go = null;
if (string.IsNullOrEmpty(target) || target == "global")
{
// Global physics settings
if (parameters.TryGetValue("gravity", out var gravityStr))
{
try
{
// Parse JSON format gravity
var gravityDict = JsonConvert.DeserializeObject<Dictionary<string, float>>(gravityStr);
if (gravityDict != null && gravityDict.ContainsKey("x") && gravityDict.ContainsKey("y") && gravityDict.ContainsKey("z"))
{
Physics.gravity = new Vector3(gravityDict["x"], gravityDict["y"], gravityDict["z"]);
}
}
catch
{
// Try comma-separated format
var parts = gravityStr.Split(',');
if (parts.Length == 3)
{
Physics.gravity = new Vector3(float.Parse(parts[0]), float.Parse(parts[1]), float.Parse(parts[2]));
}
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = "Global physics settings updated",
gravity = new { x = Physics.gravity.x, y = Physics.gravity.y, z = Physics.gravity.z }
});
}
if (target == "last")
{
go = lastCreatedObject;
if (go == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "No object has been created yet (lastCreatedObject is null)",
hint = "Create an object first or specify a specific target name"
});
}
}
else
{
go = GetTargetGameObject(parameters);
}
if (go == null)
{
return CreateGameObjectNotFoundResponse("SetupPhysics", target, parameters);
}
// Add physics components
var rigidbodyParam = parameters.GetValueOrDefault("rigidbody", "false").ToLower();
if (rigidbodyParam == "true" || rigidbodyParam == "1")
{
var rb = go.GetComponent<Rigidbody>();
if (rb == null)
{
rb = go.AddComponent<Rigidbody>();
}
if (parameters.TryGetValue("mass", out var mass))
{
rb.mass = float.Parse(mass);
}
if (parameters.TryGetValue("gravity", out var gravity))
{
var gravityLower = gravity.ToLower();
rb.useGravity = gravityLower == "true" || gravityLower == "1";
}
}
if (parameters.TryGetValue("collider", out var colliderType))
{
switch (colliderType.ToLower())
{
case "box":
go.AddComponent<BoxCollider>();
break;
case "sphere":
go.AddComponent<SphereCollider>();
break;
case "capsule":
go.AddComponent<CapsuleCollider>();
break;
case "mesh":
go.AddComponent<MeshCollider>();
break;
}
}
// Register to UNDO
UnityEditor.Undo.RecordObject(go, "Setup Physics");
var responseData = new Dictionary<string, object>
{
["success"] = true,
["message"] = $"Successfully setup physics for '{go.name}'",
["target"] = go.name,
["components"] = go.GetComponents<Component>().Select(c => c.GetType().Name).ToArray()
};
// Detailed information about added physics components
var rigidbody = go.GetComponent<Rigidbody>();
if (rigidbody != null)
{
responseData["rigidbody"] = new
{
mass = rigidbody.mass,
useGravity = rigidbody.useGravity,
isKinematic = rigidbody.isKinematic,
drag = rigidbody.linearDamping,
angularDrag = rigidbody.angularDamping
};
}
var collider = go.GetComponent<Collider>();
if (collider != null)
{
responseData["collider"] = new
{
type = collider.GetType().Name,
isTrigger = collider.isTrigger,
enabled = collider.enabled
};
}
return JsonConvert.SerializeObject(responseData, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse("SetupPhysics", e, parameters);
}
}
private string CreateMaterial(Dictionary<string, string> parameters)
{
var name = parameters.GetValueOrDefault("name", "NewMaterial");
// Get default shader based on render pipeline
var pipeline = DetectRenderingPipeline();
string defaultShader;
switch (pipeline)
{
case "URP":
defaultShader = "Universal Render Pipeline/Lit";
break;
case "HDRP":
defaultShader = "HDRP/Lit";
break;
default:
defaultShader = "Standard";
break;
}
var shaderName = parameters.GetValueOrDefault("shader", defaultShader);
SynLog.Info($"[CreateMaterial] Pipeline: {pipeline}, Default shader: {defaultShader}, Using: {shaderName}");
// Create folder if it doesn't exist
var folderPath = "Assets/Synaptic_Generated";
if (!AssetDatabase.IsValidFolder(folderPath))
{
AssetDatabase.CreateFolder("Assets", "Synaptic_Generated");
AssetDatabase.Refresh();
}
// Find shader with fallback
Shader shader = null;
string usedShaderName = shaderName;
// Try registered Synaptic shader first if keyword matches
var shaderKey = shaderName.ToLower();
var currentPipeline = DetectRenderingPipeline();
if (RegisteredShaders.TryGetValue(shaderKey, out var shaderInfo))
{
if (shaderInfo.IsAvailable(currentPipeline))
{
var registeredShaderName = shaderInfo.GetShaderName(currentPipeline);
shader = Shader.Find(registeredShaderName);
if (shader != null)
{
usedShaderName = registeredShaderName;
SynLog.Info($"[CreateMaterial] Using registered shader: {registeredShaderName}");
}
}
}
// Fallback to direct shader name
if (shader == null)
{
shader = Shader.Find(shaderName);
}
// Final fallback to pipeline-compatible standard shader
if (shader == null)
{
shader = GetRenderPipelineCompatibleShader();
usedShaderName = shader.name;
SynLog.Warn($"[CreateMaterial] Shader '{shaderName}' not found, using fallback: {usedShaderName}");
}
var mat = new Material(shader);
mat.name = name;
if (parameters.TryGetValue("color", out var colorStr))
{
var color = ParseColor(colorStr);
SynLog.Info($"[CreateMaterial] Setting color: {colorStr} -> {color}");
// URP/HDRP use _BaseColor, Legacy uses _Color
if (mat.HasProperty("_BaseColor"))
{
mat.SetColor("_BaseColor", color);
}
if (mat.HasProperty("_Color"))
{
mat.SetColor("_Color", color);
}
}
// Apply metallic/smoothness if provided
if (parameters.TryGetValue("metallic", out var metallicStr) && float.TryParse(metallicStr, out var metallic))
{
mat.SetFloat("_Metallic", metallic);
}
if (parameters.TryGetValue("smoothness", out var smoothnessStr) && float.TryParse(smoothnessStr, out var smoothness))
{
mat.SetFloat("_Glossiness", smoothness);
mat.SetFloat("_Smoothness", smoothness);
}
var matPath = $"{folderPath}/{name}.mat";
AssetDatabase.CreateAsset(mat, matPath);
AssetDatabase.Refresh();
// Register to UNDO
UnityEditor.Undo.RegisterCreatedObjectUndo(mat, $"Create Material {name}");
// Return JSON with path for other tools to use
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created material: {name}",
path = matPath,
shader = usedShaderName
});
}
private string CreateParticleSystem(Dictionary<string, string> parameters)
{
var name = parameters.GetValueOrDefault("name", "ParticleSystem");
var type = parameters.GetValueOrDefault("preset", "default");
var go = new GameObject(name);
var ps = go.AddComponent<ParticleSystem>();
// Position
if (parameters.TryGetValue("position", out var pos))
{
go.transform.position = ParseVector3(pos);
}
var main = ps.main;
var emission = ps.emission;
var shape = ps.shape;
// Extended parameters
if (parameters.TryGetValue("lifetime", out var lifetimeStr) && float.TryParse(lifetimeStr, out var lifetime))
main.startLifetime = lifetime;
if (parameters.TryGetValue("startSpeed", out var speedStr) && float.TryParse(speedStr, out var speed))
main.startSpeed = speed;
if (parameters.TryGetValue("startSize", out var sizeStr) && float.TryParse(sizeStr, out var size))
main.startSize = size;
if (parameters.TryGetValue("emission", out var emissionStr) && float.TryParse(emissionStr, out var emissionRate))
emission.rateOverTime = emissionRate;
if (parameters.TryGetValue("gravity", out var gravityStr) && float.TryParse(gravityStr, out var gravity))
main.gravityModifier = gravity;
// Shape configuration
if (parameters.TryGetValue("shape", out var shapeStr))
{
switch (shapeStr.ToLower())
{
case "sphere": shape.shapeType = ParticleSystemShapeType.Sphere; break;
case "box": shape.shapeType = ParticleSystemShapeType.Box; break;
case "cone": shape.shapeType = ParticleSystemShapeType.Cone; break;
case "circle": shape.shapeType = ParticleSystemShapeType.Circle; break;
case "edge": shape.shapeType = ParticleSystemShapeType.SingleSidedEdge; break;
case "mesh": shape.shapeType = ParticleSystemShapeType.Mesh; break;
}
}
// Physics
if (parameters.TryGetValue("usePhysics", out var physicsStr) && bool.TryParse(physicsStr, out var usePhysics) && usePhysics)
{
var collision = ps.collision;
collision.enabled = true;
collision.type = ParticleSystemCollisionType.World;
}
// Settings by preset
switch (type.ToLower())
{
case "fire":
main.startLifetime = 1f;
main.startSpeed = 5f;
main.startSize = 0.5f;
main.startColor = new Color(1f, 0.5f, 0f);
emission.rateOverTime = 50;
shape.shapeType = ParticleSystemShapeType.Cone;
shape.angle = 25;
break;
case "smoke":
main.startLifetime = 3f;
main.startSpeed = 1f;
main.startSize = 1f;
main.startColor = new Color(0.5f, 0.5f, 0.5f, 0.5f);
emission.rateOverTime = 20;
shape.shapeType = ParticleSystemShapeType.Sphere;
break;
case "sparkle":
main.startLifetime = 2f;
main.startSpeed = 2f;
main.startSize = 0.1f;
main.startColor = Color.white;
emission.rateOverTime = 100;
shape.shapeType = ParticleSystemShapeType.Box;
break;
case "rain":
main.startLifetime = 2f;
main.startSpeed = 10f;
main.startSize = 0.1f;
main.startColor = new Color(0.5f, 0.5f, 1f, 0.5f);
emission.rateOverTime = 500;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(10, 0.1f, 10);
go.transform.position += Vector3.up * 10;
break;
case "explosion":
main.duration = 0.5f;
main.startLifetime = 1f;
main.startSpeed = 10f;
main.startSize = 1f;
main.startColor = new Color(1f, 0.5f, 0f);
emission.SetBursts(new ParticleSystem.Burst[] {
new ParticleSystem.Burst(0.0f, 100)
});
shape.shapeType = ParticleSystemShapeType.Sphere;
ps.Play();
break;
case "snow":
main.startLifetime = 5f;
main.startSpeed = 1f;
main.startSize = 0.1f;
main.startColor = Color.white;
main.gravityModifier = 0.3f;
emission.rateOverTime = 30;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(10, 1, 10);
break;
case "magic":
main.startLifetime = 2f;
main.startSpeed = 0.5f;
main.startSize = 0.3f;
main.startColor = new Color(0.5f, 0f, 1f);
emission.rateOverTime = 20;
shape.shapeType = ParticleSystemShapeType.Circle;
var trails = ps.trails;
trails.enabled = true;
trails.lifetime = 0.5f;
break;
case "lightning":
main.startLifetime = 0.1f;
main.startSpeed = 50f;
main.startSize = 0.5f;
main.startColor = new Color(0.7f, 0.7f, 1f);
emission.SetBursts(new ParticleSystem.Burst[] {
new ParticleSystem.Burst(0.0f, 1, 3, 0.1f)
});
shape.shapeType = ParticleSystemShapeType.SingleSidedEdge;
break;
case "tornado":
main.startLifetime = 3f;
main.startSpeed = 5f;
main.startSize = 0.5f;
main.startColor = new Color(0.6f, 0.6f, 0.6f, 0.5f);
emission.rateOverTime = 100;
shape.shapeType = ParticleSystemShapeType.Cone;
shape.angle = 0;
shape.radius = 0.1f;
shape.radiusThickness = 1f;
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.orbitalY = 2f;
break;
case "galaxy":
main.startLifetime = 10f;
main.startSpeed = 0.1f;
main.startSize = 0.05f;
main.startColor = new Color(0.8f, 0.5f, 1f);
emission.rateOverTime = 1000;
shape.shapeType = ParticleSystemShapeType.Circle;
shape.radius = 5f;
var rotation = ps.rotationOverLifetime;
rotation.enabled = true;
rotation.z = new ParticleSystem.MinMaxCurve(0f, 360f);
break;
}
// Custom parameters (removed: already implemented above)
lastCreatedObject = go;
createdObjects.Add(go);
Selection.activeGameObject = go;
return $"Created ParticleSystem: {name} (preset: {type})";
}
private string SetupMaterial(Dictionary<string, string> parameters)
{
try
{
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var materialName = parameters.GetValueOrDefault("materialName", "New Material");
// Get default shader based on render pipeline
var pipeline = DetectRenderingPipeline();
string defaultShader;
switch (pipeline)
{
case "URP":
defaultShader = "Universal Render Pipeline/Lit";
break;
case "HDRP":
defaultShader = "HDRP/Lit";
break;
default:
defaultShader = "Standard";
break;
}
var shaderName = parameters.GetValueOrDefault("shader", defaultShader);
SynLog.Info($"[SetupMaterial] Pipeline: {pipeline}, Shader: {shaderName}");
GameObject target = null;
if (!string.IsNullOrEmpty(targetObject))
{
target = GameObject.Find(targetObject);
if (target == null)
{
return $"Target object '{targetObject}' not found";
}
}
// Resolve shader FIRST - prioritize registered Synaptic shaders by logical key
var shaderKey = shaderName.ToLower();
bool usedRegistered = false;
string registeredSource = "";
Shader resolvedShader = null;
string resolvedShaderName = shaderName;
var currentPipeline = DetectRenderingPipeline();
// Check if we have a registered Synaptic shader for this logical key
if (RegisteredShaders.TryGetValue(shaderKey, out var shaderInfo))
{
if (shaderInfo.IsAvailable(currentPipeline))
{
var registeredShaderName = shaderInfo.GetShaderName(currentPipeline);
resolvedShader = Shader.Find(registeredShaderName);
if (resolvedShader != null)
{
usedRegistered = true;
registeredSource = shaderInfo.Source;
resolvedShaderName = registeredShaderName;
SynLog.Info($"[SetupMaterial] Using registered shader: {registeredShaderName} from {shaderInfo.Source}");
}
}
}
// Fallback: try direct shader name
if (resolvedShader == null)
{
resolvedShader = Shader.Find(shaderName);
if (resolvedShader != null)
{
resolvedShaderName = shaderName;
}
}
// Final fallback: pipeline-compatible standard shader
if (resolvedShader == null)
{
resolvedShader = GetRenderPipelineCompatibleShader();
resolvedShaderName = resolvedShader.name;
SynLog.Warn($"[SetupMaterial] Shader '{shaderName}' not found, using fallback: {resolvedShaderName}");
}
// Create or get material
Material material = null;
// First, try to load existing material by name
var existingMatPath = $"Assets/Synaptic_Generated/{materialName}.mat";
var existingMat = AssetDatabase.LoadAssetAtPath<Material>(existingMatPath);
if (existingMat != null)
{
material = existingMat;
SynLog.Info($"[SetupMaterial] Using existing material: {existingMatPath}");
}
if (target != null)
{
var renderer = target.GetComponent<Renderer>();
if (renderer == null)
{
return $"Object '{targetObject}' has no Renderer component";
}
// Use existing material if found, otherwise create new one
if (material != null)
{
renderer.material = material;
}
else if (parameters.ContainsKey("color") || parameters.ContainsKey("shader"))
{
// Create new material only if color or shader is specified
material = new Material(resolvedShader);
material.name = materialName;
renderer.material = material;
}
else if (renderer.material != null && !renderer.material.name.Contains("Default"))
{
material = renderer.material;
}
else
{
material = new Material(resolvedShader);
material.name = materialName;
renderer.material = material;
}
}
else if (material == null)
{
material = new Material(resolvedShader);
material.name = materialName;
}
// Apply resolved shader to material (in case we reused existing material)
if (parameters.ContainsKey("shader") || usedRegistered)
{
material.shader = resolvedShader;
}
// Apply default properties for specific shader types (when using fallback standard shader)
if (!usedRegistered && (resolvedShaderName.Contains("Standard") || resolvedShaderName.Contains("Lit")))
{
switch (shaderKey)
{
case "water":
// Fallback water settings for standard shader
material.SetFloat("_Metallic", 0);
material.SetFloat("_Glossiness", 0.9f);
material.SetColor("_Color", new Color(0.1f, 0.4f, 0.8f, 0.7f));
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.renderQueue = 3000;
break;
case "glass":
material.SetFloat("_Metallic", 0);
material.SetFloat("_Glossiness", 1f);
material.SetColor("_Color", new Color(1f, 1f, 1f, 0.2f));
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.renderQueue = 3000;
break;
case "hologram":
material.SetFloat("_Metallic", 0);
material.SetFloat("_Glossiness", 0.5f);
material.SetColor("_Color", new Color(0f, 1f, 0.8f, 0.5f));
material.SetColor("_EmissionColor", new Color(0f, 1f, 0.8f) * 2f);
material.EnableKeyword("_EMISSION");
material.renderQueue = 3000;
break;
}
}
// Apply color
if (parameters.TryGetValue("color", out var colorStr))
{
var color = ParseColor(colorStr);
SynLog.Info($"[SetupMaterial] Setting color: {colorStr} -> {color}");
material.SetColor("_Color", color);
material.SetColor("_BaseColor", color); // URP/HDRP support
// For Standard shader, also set Albedo
if (material.HasProperty("_Color"))
{
material.color = color;
}
}
// Apply metallic
if (parameters.TryGetValue("metallic", out var metallicStr) &&
float.TryParse(metallicStr, out var metallic))
{
material.SetFloat("_Metallic", metallic);
}
// Apply smoothness
if (parameters.TryGetValue("smoothness", out var smoothnessStr) &&
float.TryParse(smoothnessStr, out var smoothness))
{
material.SetFloat("_Glossiness", smoothness);
}
// Apply emission
if (parameters.TryGetValue("emission", out var emissionStr) &&
bool.TryParse(emissionStr, out var emission) && emission)
{
material.EnableKeyword("_EMISSION");
if (parameters.TryGetValue("emissionColor", out var emissionColorStr))
{
material.SetColor("_EmissionColor", ParseColor(emissionColorStr));
}
else
{
material.SetColor("_EmissionColor", material.color);
}
}
// Apply transparency
if (parameters.TryGetValue("transparency", out var transparencyStr) &&
float.TryParse(transparencyStr, out var transparency))
{
var color = material.color;
color.a = 1f - transparency;
material.SetColor("_Color", color);
if (transparency > 0)
{
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
}
}
// Ensure material changes are applied
if (target != null)
{
var renderer = target.GetComponent<Renderer>();
renderer.material = material;
EditorUtility.SetDirty(renderer);
EditorUtility.SetDirty(target);
}
return target != null
? $"Material '{material.name}' applied to {targetObject} (Color: {material.color})"
: $"Material '{material.name}' created";
}
catch (Exception e)
{
return $"Error setting up material: {e.Message}";
}
}
private string SetupNavMesh(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target");
if (string.IsNullOrEmpty(targetName))
{
// Create NavMeshSurface component
var navSurface = new GameObject("NavMeshSurface");
// Use NavMeshSurface from Unity.AI.Navigation package
var navMeshSurfaceType = Type.GetType("Unity.AI.Navigation.NavMeshSurface, Unity.AI.Navigation");
if (navMeshSurfaceType != null)
{
var surface = navSurface.AddComponent(navMeshSurfaceType);
UnityEditor.Undo.RegisterCreatedObjectUndo(navSurface, "Create NavMeshSurface");
// Attempt to bake automatically
try
{
var buildNavMeshMethod = navMeshSurfaceType.GetMethod("BuildNavMesh");
if (buildNavMeshMethod != null)
{
buildNavMeshMethod.Invoke(surface, null);
return JsonConvert.SerializeObject(new
{
success = true,
message = "Created and baked NavMeshSurface",
objectName = navSurface.name,
note = "NavMesh has been automatically baked"
});
}
}
catch (Exception ex)
{
SynLog.Warn($"Failed to auto-bake NavMesh: {ex.Message}");
}
return JsonConvert.SerializeObject(new
{
success = true,
message = "Created NavMeshSurface (requires manual baking)",
objectName = navSurface.name,
note = "Use Window > AI > Navigation to bake the NavMesh"
});
}
else
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "NavMesh setup requires Unity.AI.Navigation package",
solution = "Install Unity.AI.Navigation package via Package Manager",
packageName = "com.unity.ai.navigation"
});
}
}
else
{
var target = GetTargetGameObject(parameters);
if (target == null)
{
return CreateGameObjectNotFoundResponse("SetupNavMesh", targetName, parameters);
}
// Add NavMeshAgent
var agent = target.AddComponent<UnityEngine.AI.NavMeshAgent>();
UnityEditor.Undo.RecordObject(target, "Add NavMeshAgent");
if (parameters.TryGetValue("speed", out var speed))
agent.speed = float.Parse(speed);
if (parameters.TryGetValue("radius", out var radius))
agent.radius = float.Parse(radius);
if (parameters.TryGetValue("height", out var height))
agent.height = float.Parse(height);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Added NavMeshAgent to '{targetName}'",
target = targetName,
agent = new
{
speed = agent.speed,
radius = agent.radius,
height = agent.height
}
});
}
}
catch (Exception e)
{
return CreateErrorResponse("SetupNavMesh", e, parameters);
}
}
private string CreateAudioMixer(Dictionary<string, string> parameters)
{
var name = parameters.GetValueOrDefault("name", "AudioMixer");
// AudioMixer asset creation requires Unity's built-in menu
// Use simple implementation as editor extension creation is complex
// Alternative: Create GameObject with AudioSource
var audioGO = new GameObject($"{name}_AudioController");
var audioSource = audioGO.AddComponent<AudioSource>();
// Basic settings
audioSource.volume = 1.0f;
audioSource.playOnAwake = false;
lastCreatedObject = audioGO;
createdObjects.Add(audioGO);
Selection.activeGameObject = audioGO;
return $"Created AudioController GameObject: {name} (Note: For full AudioMixer, use Unity's Create menu)";
}
// ===== New Audio tool implementation =====
private string CreateAudioSource(Dictionary<string, string> parameters)
{
try
{
var gameObjectName = parameters.GetValueOrDefault("gameObject", "AudioObject");
var audioClipPath = parameters.GetValueOrDefault("audioClip", "");
var playOnAwake = bool.Parse(parameters.GetValueOrDefault("playOnAwake", "true"));
var loop = bool.Parse(parameters.GetValueOrDefault("loop", "false"));
var volume = float.Parse(parameters.GetValueOrDefault("volume", "1"));
var pitch = float.Parse(parameters.GetValueOrDefault("pitch", "1"));
var spatialMode = parameters.GetValueOrDefault("spatialMode", "3D");
var minDistance = float.Parse(parameters.GetValueOrDefault("minDistance", "1"));
var maxDistance = float.Parse(parameters.GetValueOrDefault("maxDistance", "500"));
GameObject targetGO = GameObject.Find(gameObjectName);
if (targetGO == null)
{
targetGO = new GameObject(gameObjectName);
}
var audioSource = targetGO.AddComponent<AudioSource>();
// Load AudioClip
if (!string.IsNullOrEmpty(audioClipPath))
{
var audioClip = AssetDatabase.LoadAssetAtPath<AudioClip>(audioClipPath);
if (audioClip != null)
{
audioSource.clip = audioClip;
}
}
// Basic settings
audioSource.playOnAwake = playOnAwake;
audioSource.loop = loop;
audioSource.volume = volume;
audioSource.pitch = pitch;
audioSource.minDistance = minDistance;
audioSource.maxDistance = maxDistance;
if (spatialMode == "3D")
{
audioSource.spatialBlend = 1.0f; // Full 3D
audioSource.dopplerLevel = 1.0f;
audioSource.spread = 0;
audioSource.rolloffMode = AudioRolloffMode.Logarithmic;
}
else
{
audioSource.spatialBlend = 0.0f; // Full 2D
}
lastCreatedObject = targetGO;
Selection.activeGameObject = targetGO;
return $"[NexusAudio] Created AudioSource on '{gameObjectName}'\n" +
$" Settings: PlayOnAwake={playOnAwake}, Loop={loop}\n" +
$" Volume: {volume}, Pitch: {pitch}\n" +
$" Spatial Mode: {spatialMode}\n" +
$" Tip: Drag your audio clip to complete setup";
}
catch (Exception e)
{
return CreateErrorResponse("CreateAudioSource", e, parameters);
}
}
private string Setup3DAudio(Dictionary<string, string> parameters)
{
try
{
var audioSourceName = parameters.GetValueOrDefault("audioSource", "");
var dopplerLevel = float.Parse(parameters.GetValueOrDefault("dopplerLevel", "1"));
var spread = float.Parse(parameters.GetValueOrDefault("spread", "0"));
var rolloffMode = parameters.GetValueOrDefault("volumeRolloff", "Logarithmic");
var minDistance = float.Parse(parameters.GetValueOrDefault("minDistance", "1"));
var maxDistance = float.Parse(parameters.GetValueOrDefault("maxDistance", "500"));
GameObject go = GameObject.Find(audioSourceName);
if (go == null)
{
return $"GameObject {audioSourceName} not found";
}
// Smart search for AudioSource
string foundLocation;
var audioSource = FindAudioSourceSmart(go, out foundLocation);
if (audioSource == null)
{
return $"[NexusAudio] AudioSource not found: {audioSourceName}.\n" +
$"Tip: Please specify an object with AudioSource or its parent object.\n" +
$"Example: 'BGM Manager' or 'Player'";
}
// 3D settings
audioSource.spatialBlend = 1.0f; // Full 3D
audioSource.dopplerLevel = dopplerLevel;
audioSource.spread = spread;
audioSource.minDistance = minDistance;
audioSource.maxDistance = maxDistance;
// Rolloff mode settings
switch (rolloffMode)
{
case "Linear":
audioSource.rolloffMode = AudioRolloffMode.Linear;
break;
case "Custom":
audioSource.rolloffMode = AudioRolloffMode.Custom;
break;
default:
audioSource.rolloffMode = AudioRolloffMode.Logarithmic;
break;
}
// Add detailed information to success message
if (foundLocation != audioSourceName)
{
return $"[NexusAudio] 3D audio configured (AudioSource at {foundLocation}):\n" +
$" Doppler Effect: {dopplerLevel}\n" +
$" Spread: {spread}°\n" +
$" Rolloff Mode: {rolloffMode}\n" +
$" Min Distance: {minDistance}m, Max Distance: {maxDistance}m";
}
else
{
return $"[NexusAudio] 3D audio configured ({audioSourceName}):\n" +
$" Doppler Effect: {dopplerLevel}\n" +
$" Spread: {spread}°\n" +
$" Rolloff Mode: {rolloffMode}\n" +
$" Min Distance: {minDistance}m, Max Distance: {maxDistance}m";
}
}
catch (Exception e)
{
return CreateErrorResponse("Setup3DAudio", e, parameters);
}
}
private string CreateAudioClip(Dictionary<string, string> parameters)
{
try
{
var name = parameters.GetValueOrDefault("name", "AudioClip");
var path = parameters.GetValueOrDefault("path", "");
var loadType = parameters.GetValueOrDefault("loadType", "DecompressOnLoad");
var compressionFormat = parameters.GetValueOrDefault("compressionFormat", "Vorbis");
var forceToMono = bool.Parse(parameters.GetValueOrDefault("forceToMono", "false"));
if (!string.IsNullOrEmpty(path))
{
// Change import settings for existing audio file
var audioImporter = AssetImporter.GetAtPath(path) as AudioImporter;
if (audioImporter != null)
{
var audioImporterSettings = new AudioImporterSampleSettings();
audioImporterSettings.loadType = (AudioClipLoadType)Enum.Parse(typeof(AudioClipLoadType), loadType);
audioImporterSettings.compressionFormat = (AudioCompressionFormat)Enum.Parse(typeof(AudioCompressionFormat), compressionFormat);
audioImporter.defaultSampleSettings = audioImporterSettings;
audioImporter.forceToMono = forceToMono;
audioImporter.SaveAndReimport();
return $"Updated audio import settings for {path}";
}
else
{
return $"Audio file not found at path: {path}";
}
}
else
{
// Create procedural audio (simple version)
return "Procedural audio generation requires custom implementation";
}
}
catch (Exception e)
{
return CreateErrorResponse("CreateAudioClip", e, parameters);
}
}
/// <summary>
/// Helper method to convert object to Dictionary<string, object>
/// </summary>
private Dictionary<string, object> ConvertToDictionary(object obj)
{
if (obj is Dictionary<string, object> dict)
return dict;
if (obj is Newtonsoft.Json.Linq.JObject jobj)
return jobj.ToObject<Dictionary<string, object>>();
// Return null for other formats
return null;
}
/// <summary>
/// Helper method for smart AudioSource search
/// Search from child objects if specified GameObject doesn't have AudioSource
/// </summary>
private AudioSource FindAudioSourceSmart(GameObject targetObject, out string foundLocation)
{
foundLocation = targetObject.name;
// First check the target object itself
var audioSource = targetObject.GetComponent<AudioSource>();
if (audioSource != null)
{
return audioSource;
}
// Search from child objects
audioSource = targetObject.GetComponentInChildren<AudioSource>();
if (audioSource != null)
{
foundLocation = $"{targetObject.name}/{audioSource.gameObject.name}";
SynLog.Info($"[NexusAudio] Found AudioSource in child object '{audioSource.gameObject.name}'");
return audioSource;
}
// Suggest auto-creation if no AudioSource exists
SynLog.Warn($"[NexusAudio] AudioSource not found in '{targetObject.name}' or its children. Consider auto-creating");
return null;
}
/// <summary>
/// Helper method to create or get AudioSource as needed
/// </summary>
private AudioSource GetOrCreateAudioSource(GameObject targetObject, bool autoCreate = true)
{
string foundLocation;
var audioSource = FindAudioSourceSmart(targetObject, out foundLocation);
if (audioSource == null && autoCreate)
{
audioSource = targetObject.AddComponent<AudioSource>();
SynLog.Info($"[NexusAudio] Automatically created AudioSource on '{targetObject.name}'");
}
return audioSource;
}
private string SetupAudioEffects(Dictionary<string, string> parameters)
{
try
{
var audioSourceName = parameters.GetValueOrDefault("audioSource", "");
var effectsJson = parameters.GetValueOrDefault("effects", "[]");
var effects = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(effectsJson);
GameObject go = GameObject.Find(audioSourceName);
if (go == null)
{
return $"GameObject {audioSourceName} not found";
}
// Smart search for AudioSource
string foundLocation;
var audioSource = FindAudioSourceSmart(go, out foundLocation);
if (audioSource == null)
{
return $"[NexusAudio] AudioSource not found: {audioSourceName}.\n" +
$"Tip: Please specify an object with AudioSource or its parent object.\n" +
$"Example: 'BGM Manager' or 'Player'";
}
// Apply effects to GameObject with AudioSource
var audioSourceObject = audioSource.gameObject;
if (audioSourceObject != go)
{
SynLog.Info($"[NexusAudio] Applying effects to AudioSource at {foundLocation}");
}
var results = new List<string>();
foreach (var effect in effects)
{
var effectType = effect.GetValueOrDefault("type", "").ToString();
var enabled = bool.Parse(effect.GetValueOrDefault("enabled", "true").ToString());
switch (effectType)
{
case "LowPass":
var lowPass = audioSourceObject.GetComponent<AudioLowPassFilter>() ?? audioSourceObject.AddComponent<AudioLowPassFilter>();
lowPass.enabled = enabled;
if (effect.ContainsKey("parameters"))
{
try
{
var paramDict = ConvertToDictionary(effect["parameters"]);
if (paramDict != null && paramDict.ContainsKey("cutoffFrequency"))
{
lowPass.cutoffFrequency = float.Parse(paramDict["cutoffFrequency"].ToString());
}
if (paramDict != null && paramDict.ContainsKey("lowpassResonanceQ"))
{
lowPass.lowpassResonanceQ = float.Parse(paramDict["lowpassResonanceQ"].ToString());
}
}
catch { }
}
results.Add($"Added LowPass filter");
break;
case "HighPass":
var highPass = audioSourceObject.GetComponent<AudioHighPassFilter>() ?? audioSourceObject.AddComponent<AudioHighPassFilter>();
highPass.enabled = enabled;
if (effect.ContainsKey("parameters"))
{
try
{
var paramDict = ConvertToDictionary(effect["parameters"]);
if (paramDict != null && paramDict.ContainsKey("cutoffFrequency"))
{
highPass.cutoffFrequency = float.Parse(paramDict["cutoffFrequency"].ToString());
}
}
catch { }
}
results.Add($"Added HighPass filter");
break;
case "Echo":
var echo = audioSourceObject.GetComponent<AudioEchoFilter>() ?? audioSourceObject.AddComponent<AudioEchoFilter>();
echo.enabled = enabled;
if (effect.ContainsKey("parameters"))
{
try
{
var paramDict = ConvertToDictionary(effect["parameters"]);
if (paramDict != null)
{
if (paramDict.ContainsKey("delay"))
echo.delay = float.Parse(paramDict["delay"].ToString());
if (paramDict.ContainsKey("decayRatio"))
echo.decayRatio = float.Parse(paramDict["decayRatio"].ToString());
}
}
catch { }
}
results.Add($"Added Echo filter");
break;
case "Distortion":
var distortion = audioSourceObject.GetComponent<AudioDistortionFilter>() ?? audioSourceObject.AddComponent<AudioDistortionFilter>();
distortion.enabled = enabled;
if (effect.ContainsKey("parameters"))
{
try
{
var paramDict = ConvertToDictionary(effect["parameters"]);
if (paramDict != null && paramDict.ContainsKey("distortionLevel"))
{
distortion.distortionLevel = float.Parse(paramDict["distortionLevel"].ToString());
}
}
catch { }
}
results.Add($"Added Distortion filter");
break;
case "Reverb":
var reverbFilter = audioSourceObject.GetComponent<AudioReverbFilter>() ?? audioSourceObject.AddComponent<AudioReverbFilter>();
reverbFilter.enabled = enabled;
if (effect.ContainsKey("parameters"))
{
try
{
var paramDict = ConvertToDictionary(effect["parameters"]);
if (paramDict != null && paramDict.ContainsKey("reverbPreset"))
{
var presetName = paramDict["reverbPreset"].ToString();
reverbFilter.reverbPreset = (AudioReverbPreset)Enum.Parse(typeof(AudioReverbPreset), presetName);
}
}
catch { }
}
results.Add($"Added Reverb filter");
break;
case "Chorus":
var chorus = audioSourceObject.GetComponent<AudioChorusFilter>() ?? audioSourceObject.AddComponent<AudioChorusFilter>();
chorus.enabled = enabled;
if (effect.ContainsKey("parameters"))
{
try
{
var paramDict = ConvertToDictionary(effect["parameters"]);
if (paramDict != null)
{
if (paramDict.ContainsKey("dryMix"))
chorus.dryMix = float.Parse(paramDict["dryMix"].ToString());
if (paramDict.ContainsKey("wetMix1"))
chorus.wetMix1 = float.Parse(paramDict["wetMix1"].ToString());
if (paramDict.ContainsKey("delay"))
chorus.delay = float.Parse(paramDict["delay"].ToString());
if (paramDict.ContainsKey("rate"))
chorus.rate = float.Parse(paramDict["rate"].ToString());
}
}
catch { }
}
results.Add($"Added Chorus filter");
break;
}
}
// Add detailed information to success message
if (audioSourceObject != go)
{
return $"[NexusAudio] Added audio effects to child object '{audioSource.gameObject.name}' of {audioSourceName}:\n{string.Join("\n", results)}";
}
else
{
return $"[NexusAudio] Added audio effects to {audioSourceName}:\n{string.Join("\n", results)}";
}
}
catch (Exception e)
{
return CreateErrorResponse("SetupAudioEffects", e, parameters);
}
}
private string CreateReverbZones(Dictionary<string, string> parameters)
{
try
{
var name = parameters.GetValueOrDefault("name", "ReverbZone");
var minDistance = float.Parse(parameters.GetValueOrDefault("minDistance", "10"));
var maxDistance = float.Parse(parameters.GetValueOrDefault("maxDistance", "30"));
var reverbPreset = parameters.GetValueOrDefault("reverbPreset", "Room");
// Improve position parameter processing
Vector3 position = Vector3.zero;
if (parameters.ContainsKey("position"))
{
var positionStr = parameters["position"];
try
{
// If JSON string
if (positionStr.StartsWith("{"))
{
var posDict = JsonConvert.DeserializeObject<Dictionary<string, float>>(positionStr);
position = new Vector3(
posDict.GetValueOrDefault("x", 0),
posDict.GetValueOrDefault("y", 0),
posDict.GetValueOrDefault("z", 0)
);
}
else
{
// If comma-separated values (e.g., "0,1,0")
var coords = positionStr.Split(',');
if (coords.Length >= 3)
{
position = new Vector3(
float.Parse(coords[0].Trim()),
float.Parse(coords[1].Trim()),
float.Parse(coords[2].Trim())
);
}
}
}
catch
{
// Use default value when parsing fails
position = Vector3.zero;
}
}
var reverbGO = new GameObject(name);
reverbGO.transform.position = position;
var audioReverbZone = reverbGO.AddComponent<AudioReverbZone>();
audioReverbZone.minDistance = minDistance;
audioReverbZone.maxDistance = maxDistance;
audioReverbZone.reverbPreset = (AudioReverbPreset)Enum.Parse(typeof(AudioReverbPreset), reverbPreset);
// Process custom parameters
if (parameters.ContainsKey("customParameters"))
{
var customParamsStr = parameters["customParameters"];
try
{
var customParams = JsonConvert.DeserializeObject<Dictionary<string, float>>(customParamsStr);
// Unity's standard AudioReverbZone cannot set custom parameters,
// so skip here (for future extensions)
}
catch
{
// Continue even if custom parameter parsing fails
}
}
lastCreatedObject = reverbGO;
createdObjects.Add(reverbGO);
Selection.activeGameObject = reverbGO;
return $"[NexusAudio] Reverb Zone '{name}' created successfully!\n" +
$" Position: ({position.x:F1}, {position.y:F1}, {position.z:F1})\n" +
$" Preset: {reverbPreset}\n" +
$" Effective Range: {minDistance}m -{maxDistance}m\n" +
$" Tip: Reverb effects will be applied when player enters this zone";
}
catch (Exception e)
{
return CreateErrorResponse("CreateReverbZones", e, parameters);
}
}
private string SetupAudioOcclusion(Dictionary<string, string> parameters)
{
try
{
var audioSourceName = parameters.GetValueOrDefault("audioSource", "");
var enableOcclusion = bool.Parse(parameters.GetValueOrDefault("enableOcclusion", "true"));
GameObject go = GameObject.Find(audioSourceName);
if (go == null)
{
return $"GameObject {audioSourceName} not found";
}
var audioSource = go.GetComponent<AudioSource>();
if (audioSource == null)
{
return $"AudioSource component not found on {audioSourceName}";
}
// Unity doesn't have built-in audio occlusion, so we'll add a custom component
// This is a placeholder for a custom audio occlusion system
return $"[NexusAudio] Audio occlusion basic settings applied successfully!\n" +
$" Target: {audioSourceName}\n" +
$" Note: Full occlusion effects require custom scripting\n" +
$" Tip: Simulates sound blocking by walls and obstacles";
}
catch (Exception e)
{
return CreateErrorResponse("SetupAudioOcclusion", e, parameters);
}
}
private string CreateAdaptiveMusic(Dictionary<string, string> parameters)
{
try
{
var name = parameters.GetValueOrDefault("name", "AdaptiveMusic");
var segmentsJson = parameters.GetValueOrDefault("segments", "[]");
var bpm = float.Parse(parameters.GetValueOrDefault("bpm", "120"));
var beatsPerBar = int.Parse(parameters.GetValueOrDefault("beatsPerBar", "4"));
var segments = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(segmentsJson);
// Create GameObject for Adaptive Music System
var musicGO = new GameObject(name);
var audioSources = new List<AudioSource>();
// Create AudioSource for each segment
foreach (var segment in segments)
{
var segmentName = segment.GetValueOrDefault("name", "Segment").ToString();
var audioClipPath = segment.GetValueOrDefault("audioClip", "").ToString();
var isLoop = bool.Parse(segment.GetValueOrDefault("isLoop", "false").ToString());
var segmentGO = new GameObject($"{name}_{segmentName}");
segmentGO.transform.SetParent(musicGO.transform);
var audioSource = segmentGO.AddComponent<AudioSource>();
audioSource.playOnAwake = false;
audioSource.loop = isLoop;
if (!string.IsNullOrEmpty(audioClipPath))
{
var audioClip = AssetDatabase.LoadAssetAtPath<AudioClip>(audioClipPath);
if (audioClip != null)
{
audioSource.clip = audioClip;
}
}
audioSources.Add(audioSource);
}
// Add AdaptiveMusicController
var controller = musicGO.AddComponent<AdaptiveMusicController>();
controller.bpm = bpm;
controller.beatsPerBar = beatsPerBar;
controller.playOnStart = true;
// Set segment information to controller
controller.segments = new List<AdaptiveMusicController.MusicSegment>();
int index = 0;
foreach (var segment in segments)
{
var segmentData = new AdaptiveMusicController.MusicSegment
{
name = segment.GetValueOrDefault("name", "Segment").ToString(),
audioSource = audioSources[index],
startTime = float.Parse(segment.GetValueOrDefault("startTime", "0").ToString()),
endTime = float.Parse(segment.GetValueOrDefault("endTime", "0").ToString()),
loopPoint = float.Parse(segment.GetValueOrDefault("loopPoint", "0").ToString()),
isLoop = bool.Parse(segment.GetValueOrDefault("isLoop", "false").ToString()),
fadeInDuration = float.Parse(segment.GetValueOrDefault("fadeIn", "0").ToString()),
fadeOutDuration = float.Parse(segment.GetValueOrDefault("fadeOut", "0").ToString())
};
// Add transition information
if (segment.ContainsKey("transitions"))
{
var transitions = segment["transitions"] as List<object>;
if (transitions != null)
{
segmentData.transitions = new List<AdaptiveMusicController.Transition>();
foreach (Dictionary<string, object> trans in transitions)
{
var transition = new AdaptiveMusicController.Transition
{
toSegment = trans.GetValueOrDefault("toSegment", "").ToString(),
type = (AdaptiveMusicController.TransitionType)Enum.Parse(
typeof(AdaptiveMusicController.TransitionType),
trans.GetValueOrDefault("type", "Crossfade").ToString(),
true),
duration = float.Parse(trans.GetValueOrDefault("duration", "1").ToString())
};
segmentData.transitions.Add(transition);
}
}
}
controller.segments.Add(segmentData);
index++;
}
lastCreatedObject = musicGO;
createdObjects.Add(musicGO);
Selection.activeGameObject = musicGO;
return $"[NexusAudio] Adaptive Music System '{name}' created successfully!\n" +
$" Segment Count: {segments.Count}\n" +
$" BPM: {bpm}, Time Signature: {beatsPerBar}/4\n" +
$" Status: AdaptiveMusicController configured automatically\n" +
$" Tip: Press Play to automatically start intro to loop sequence";
}
catch (Exception e)
{
return CreateErrorResponse("CreateAdaptiveMusic", e, parameters);
}
}
private string SetupAudioTriggers(Dictionary<string, string> parameters)
{
try
{
var triggerName = parameters.GetValueOrDefault("triggerName", "AudioTrigger");
var triggerType = parameters.GetValueOrDefault("triggerType", "OnEnter");
var targetGameObject = parameters.GetValueOrDefault("targetGameObject", "");
var audioActionsJson = parameters.GetValueOrDefault("audioActions", "[]");
GameObject targetGO = GameObject.Find(targetGameObject);
if (targetGO == null)
{
targetGO = new GameObject(targetGameObject);
}
// Add collider for trigger
if (triggerType.StartsWith("On") && targetGO.GetComponent<Collider>() == null)
{
var collider = targetGO.AddComponent<BoxCollider>();
collider.isTrigger = true;
}
// Setup trigger events (requires custom script)
return $"[NexusAudio] Audio Trigger '{triggerName}' configured successfully!\n" +
$" Target: {targetGameObject}\n" +
$" Trigger Type: {triggerType}\n" +
$" Note: Full functionality requires custom scripting\n" +
$" Tip: Plays audio on specific events";
}
catch (Exception e)
{
return CreateErrorResponse("SetupAudioTriggers", e, parameters);
}
}
private string CreateSoundPools(Dictionary<string, string> parameters)
{
try
{
var poolName = parameters.GetValueOrDefault("poolName", "SoundPool");
var audioClipsJson = parameters.GetValueOrDefault("audioClips", "[]");
var playbackMode = parameters.GetValueOrDefault("playbackMode", "RandomNoRepeat");
var pitchVariation = float.Parse(parameters.GetValueOrDefault("pitchVariation", "0"));
var volumeVariation = float.Parse(parameters.GetValueOrDefault("volumeVariation", "0"));
var audioClips = JsonConvert.DeserializeObject<List<string>>(audioClipsJson);
// Create GameObject for Sound Pool
var poolGO = new GameObject(poolName);
var audioSources = new List<AudioSource>();
// Create AudioSource for each audio clip
foreach (var clipPath in audioClips)
{
var sourceGO = new GameObject($"{poolName}_Source");
sourceGO.transform.SetParent(poolGO.transform);
var audioSource = sourceGO.AddComponent<AudioSource>();
audioSource.playOnAwake = false;
var audioClip = AssetDatabase.LoadAssetAtPath<AudioClip>(clipPath);
if (audioClip != null)
{
audioSource.clip = audioClip;
}
audioSources.Add(audioSource);
}
lastCreatedObject = poolGO;
createdObjects.Add(poolGO);
Selection.activeGameObject = poolGO;
return $"[NexusAudio] Sound Pool '{poolName}' created successfully!\n" +
$" Audio Clip Count: {audioClips.Count}\n" +
$" Playback Mode: {playbackMode}\n" +
$" Pitch Variation: ±{pitchVariation}\n" +
$" Volume Variation: ±{volumeVariation}\n" +
$" Tip: Achieves natural sound effects by avoiding repetition of the same sounds";
}
catch (Exception e)
{
return CreateErrorResponse("CreateSoundPools", e, parameters);
}
}
private string CreateAudioMixing(Dictionary<string, string> parameters)
{
try
{
var mixerName = parameters.GetValueOrDefault("mixerName", "DynamicMixer");
var mixerGroupsJson = parameters.GetValueOrDefault("mixerGroups", "[]");
// Simplified version: GameObject-based mixing system instead of AudioMixer
var mixerGO = new GameObject(mixerName);
var mixerGroups = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(mixerGroupsJson);
foreach (var group in mixerGroups)
{
var groupName = group.GetValueOrDefault("name", "Group").ToString();
var volume = float.Parse(group.GetValueOrDefault("volume", "0").ToString());
var priority = int.Parse(group.GetValueOrDefault("priority", "128").ToString());
var groupGO = new GameObject($"{mixerName}_{groupName}");
groupGO.transform.SetParent(mixerGO.transform);
var audioSource = groupGO.AddComponent<AudioSource>();
audioSource.volume = Mathf.Pow(10f, volume / 20f); // Convert dB to linear
audioSource.priority = priority;
audioSource.playOnAwake = false;
}
lastCreatedObject = mixerGO;
createdObjects.Add(mixerGO);
Selection.activeGameObject = mixerGO;
return $"[NexusAudio] Audio Mixing System '{mixerName}' created successfully!\n" +
$" Group Count: {mixerGroups.Count}\n" +
$" Note: Use Unity's AudioMixer for advanced features\n" +
$" Tip: You can manage volume and effects by groups";
}
catch (Exception e)
{
return CreateErrorResponse("CreateAudioMixing", e, parameters);
}
}
private string SetupSpatialAudio(Dictionary<string, string> parameters)
{
try
{
var audioSourceName = parameters.GetValueOrDefault("audioSource", "");
var spatializerPlugin = parameters.GetValueOrDefault("spatializerPlugin", "Unity");
var enableBinaural = bool.Parse(parameters.GetValueOrDefault("enableBinaural", "true"));
GameObject go = GameObject.Find(audioSourceName);
if (go == null)
{
return $"GameObject {audioSourceName} not found";
}
var audioSource = go.GetComponent<AudioSource>();
if (audioSource == null)
{
return $"AudioSource component not found on {audioSourceName}";
}
// Basic spatial audio settings
audioSource.spatialBlend = 1.0f; // Full 3D
audioSource.spatialize = true; // Enable spatialization
audioSource.spatializePostEffects = true; // Apply spatialization to post-effects
return $"[NexusAudio] Spatial Audio configured successfully!\n" +
$" Target: {audioSourceName}\n" +
$" Plugin: {spatializerPlugin}\n" +
$" Note: Plugin-specific features may require additional setup\n" +
$" Tip: Enables realistic spatial audio for VR/AR applications";
}
catch (Exception e)
{
return CreateErrorResponse("SetupSpatialAudio", e, parameters);
}
}
private string CreateAudioVisualization(Dictionary<string, string> parameters)
{
try
{
var visualizerName = parameters.GetValueOrDefault("visualizerName", "AudioVisualizer");
var audioSourceName = parameters.GetValueOrDefault("audioSource", "");
var visualizationType = parameters.GetValueOrDefault("visualizationType", "Spectrum");
var frequencyBands = int.Parse(parameters.GetValueOrDefault("frequencyBands", "64"));
var sensitivity = float.Parse(parameters.GetValueOrDefault("sensitivity", "100"));
GameObject audioGO = GameObject.Find(audioSourceName);
if (audioGO == null)
{
return $"GameObject {audioSourceName} not found";
}
// Create GameObject for visualizer
var visualizerGO = new GameObject(visualizerName);
// Create objects for visualization (e.g., bar display)
if (visualizationType == "Spectrum" || visualizationType == "Waveform")
{
for (int i = 0; i < frequencyBands; i++)
{
var barGO = CreatePrimitiveWithMaterial(PrimitiveType.Cube);
barGO.name = $"Bar_{i}";
barGO.transform.SetParent(visualizerGO.transform);
barGO.transform.position = new Vector3(i * 1.1f - (frequencyBands * 0.55f), 0, 0);
barGO.transform.localScale = new Vector3(1, 0.1f, 1);
}
}
else if (visualizationType == "VUMeter")
{
var meterGO = CreatePrimitiveWithMaterial(PrimitiveType.Cube);
meterGO.name = "VUMeter";
meterGO.transform.SetParent(visualizerGO.transform);
meterGO.transform.localScale = new Vector3(10, 1, 1);
}
lastCreatedObject = visualizerGO;
createdObjects.Add(visualizerGO);
Selection.activeGameObject = visualizerGO;
return $"[NexusAudio] Audio Visualizer '{visualizerName}' created successfully!\n" +
$" Audio Source: {audioSourceName}\n" +
$" Display Type: {visualizationType}\n" +
$" Frequency Bands: {frequencyBands}\n" +
$" Sensitivity: {sensitivity}\n" +
$" Note: Real-time updates require custom scripting\n" +
$" Tip: Creates music-synchronized visual effects";
}
catch (Exception e)
{
return CreateErrorResponse("CreateAudioVisualization", e, parameters);
}
}
// ===== Advanced Input Tools Implementation =====
private string SetupCustomInput(Dictionary<string, string> parameters)
{
try
{
var actionName = parameters.GetValueOrDefault("actionName", "CustomAction");
var bindingType = parameters.GetValueOrDefault("bindingType", "Button");
var bindingsJson = parameters.GetValueOrDefault("bindings", "[]");
var actionMap = parameters.GetValueOrDefault("actionMap", "Player");
// Requires Input System Package
// Check for Input System availability
bool isInputSystemAvailable = false;
#if ENABLE_INPUT_SYSTEM && INPUT_SYSTEM_PACKAGE
isInputSystemAvailable = true;
var playerInput = GameObject.FindObjectOfType<PlayerInput>();
if (playerInput == null)
{
var inputGO = new GameObject("PlayerInput");
playerInput = inputGO.AddComponent<PlayerInput>();
}
#endif
if (isInputSystemAvailable)
{
return $"[NexusInput] Custom Input '{actionName}' configured successfully!\n" +
$" Type: {bindingType}\n" +
$" Action Map: {actionMap}";
}
else
{
// Provide hints for alternative implementation with Legacy Input Manager
var customInputGO = new GameObject($"CustomInput_{actionName}");
var inputInfo = customInputGO.AddComponent<CustomInputInfo>();
inputInfo.actionName = actionName;
inputInfo.bindingType = bindingType;
return $"[NexusInput] Custom Input '{actionName}' configuration created successfully.\n" +
$" Note: Input System package is not installed.\n" +
$" Alternative: Use Input Manager or install Input System from Package Manager.\n" +
$" Configuration saved at: {customInputGO.name}";
}
}
catch (Exception e)
{
return CreateErrorResponse("SetupCustomInput", e, parameters);
}
}
private string CreateGestureRecognition(Dictionary<string, string> parameters)
{
try
{
var gestureName = parameters.GetValueOrDefault("gestureName", "CustomGesture");
var gestureType = parameters.GetValueOrDefault("gestureType", "Swipe");
var minDistance = float.Parse(parameters.GetValueOrDefault("minDistance", "50"));
var maxTime = float.Parse(parameters.GetValueOrDefault("maxTime", "1"));
// Create GestureRecognizer script
var gestureGO = new GameObject($"{gestureName}_GestureRecognizer");
// Save basic Gesture Recognition settings
var gestureData = gestureGO.AddComponent<GestureData>();
gestureData.gestureName = gestureName;
gestureData.gestureType = gestureType;
gestureData.minDistance = minDistance;
gestureData.maxTime = maxTime;
lastCreatedObject = gestureGO;
Selection.activeGameObject = gestureGO;
return $"[NexusInput] Gesture Recognition '{gestureName}' created successfully!\n" +
$" Type: {gestureType}\n" +
$" Min Distance: {minDistance}px\n" +
$" Max Time: {maxTime} seconds\n" +
$" Tip: Implement gesture detection logic with custom scripts";
}
catch (Exception e)
{
return CreateErrorResponse("CreateGestureRecognition", e, parameters);
}
}
private string SetupHapticFeedback(Dictionary<string, string> parameters)
{
try
{
var feedbackName = parameters.GetValueOrDefault("feedbackName", "HapticFeedback");
var platform = parameters.GetValueOrDefault("platform", "Both");
var feedbackType = parameters.GetValueOrDefault("feedbackType", "Light");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "0.1"));
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "1"));
var hapticGO = new GameObject($"{feedbackName}_Controller");
// Platform-specific vibration settings
switch (platform)
{
case "Mobile":
#if UNITY_IOS || UNITY_ANDROID
// Mobile vibration settings
Handheld.Vibrate();
#endif
break;
case "Controller":
#if ENABLE_INPUT_SYSTEM && INPUT_SYSTEM_PACKAGE
// Controller vibration settings (using Input System)
// Add Gamepad vibration implementation here
#endif
break;
}
return $"[NexusInput] Haptic Feedback '{feedbackName}' configured successfully!\n" +
$" Platform: {platform}\n" +
$" Type: {feedbackType}\n" +
$" Duration: {duration} seconds\n" +
$" Intensity: {intensity}\n" +
$" Tip: Provides vibration effects on mobile devices and controllers";
}
catch (Exception e)
{
return CreateErrorResponse("SetupHapticFeedback", e, parameters);
}
}
private string CreateInputValidation(Dictionary<string, string> parameters)
{
try
{
var validatorName = parameters.GetValueOrDefault("validatorName", "InputValidator");
var validationType = parameters.GetValueOrDefault("validationType", "Email");
var customPattern = parameters.GetValueOrDefault("customPattern", "");
var minLength = int.Parse(parameters.GetValueOrDefault("minLength", "0"));
var maxLength = int.Parse(parameters.GetValueOrDefault("maxLength", "100"));
var errorMessage = parameters.GetValueOrDefault("errorMessage", "Invalid input");
var realTimeValidation = bool.Parse(parameters.GetValueOrDefault("realTimeValidation", "true"));
var validatorGO = new GameObject(validatorName);
var validator = validatorGO.AddComponent<InputValidator>();
// Configure validation patterns
switch (validationType)
{
case "Email":
validator.pattern = @"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
break;
case "Phone":
validator.pattern = @"^[\d\s\-\+\(\)]+$";
break;
case "Number":
validator.pattern = @"^\d+$";
break;
case "AlphaNumeric":
validator.pattern = @"^[a-zA-Z0-9]+$";
break;
case "URL":
validator.pattern = @"^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$";
break;
case "Custom":
validator.pattern = customPattern;
break;
}
validator.minLength = minLength;
validator.maxLength = maxLength;
validator.errorMessage = errorMessage;
validator.realTimeValidation = realTimeValidation;
return $"[NexusInput] Input Validation '{validatorName}' created successfully!\n" +
$" Type: {validationType}\n" +
$" Length: {minLength} - {maxLength}\n" +
$" Real-time Validation: {realTimeValidation}\n" +
$" Error Message: {errorMessage}";
}
catch (Exception e)
{
return CreateErrorResponse("CreateInputValidation", e, parameters);
}
}
private string SetupAccessibilityInput(Dictionary<string, string> parameters)
{
try
{
var accessibilityName = parameters.GetValueOrDefault("accessibilityName", "AccessibilitySettings");
var featuresJson = parameters.GetValueOrDefault("features", "[]");
var features = JsonConvert.DeserializeObject<List<string>>(featuresJson);
var buttonScaling = float.Parse(parameters.GetValueOrDefault("buttonScaling", "1.5"));
var contrastLevel = parameters.GetValueOrDefault("contrastLevel", "High");
var accessibilityGO = new GameObject(accessibilityName);
var settings = accessibilityGO.AddComponent<AccessibilitySettings>();
// Configure accessibility features
settings.features = features;
settings.buttonScaling = buttonScaling;
settings.contrastLevel = contrastLevel;
// Apply button scaling
if (features.Contains("LargeButtons"))
{
var buttons = GameObject.FindObjectsOfType<UnityEngine.UI.Button>();
foreach (var button in buttons)
{
var rectTransform = button.GetComponent<RectTransform>();
if (rectTransform != null)
{
rectTransform.localScale *= buttonScaling;
}
}
}
return $"[NexusInput] Accessibility Settings '{accessibilityName}' created successfully!\n" +
$" Features: {string.Join(", ", features)}\n" +
$" Button Scale: {buttonScaling}x\n" +
$" Contrast: {contrastLevel}\n" +
$" Tip: Optimizes UI for comfortable use by all users";
}
catch (Exception e)
{
return CreateErrorResponse("SetupAccessibilityInput", e, parameters);
}
}
private string CreateInputRecording(Dictionary<string, string> parameters)
{
try
{
var recorderName = parameters.GetValueOrDefault("recorderName", "InputRecorder");
var recordingMode = parameters.GetValueOrDefault("recordingMode", "All");
var includeTimestamps = bool.Parse(parameters.GetValueOrDefault("includeTimestamps", "true"));
var maxRecordingTime = float.Parse(parameters.GetValueOrDefault("maxRecordingTime", "300"));
var recorderGO = new GameObject(recorderName);
var recorder = recorderGO.AddComponent<InputRecorder>();
recorder.recordingMode = recordingMode;
recorder.includeTimestamps = includeTimestamps;
recorder.maxRecordingTime = maxRecordingTime;
return $"[NexusInput] Input Recording System '{recorderName}' created successfully!\n" +
$" Recording Mode: {recordingMode}\n" +
$" Timestamps: {includeTimestamps}\n" +
$" Max Recording Time: {maxRecordingTime} seconds\n" +
$" Tip: Record and replay inputs for testing and replay purposes";
}
catch (Exception e)
{
return CreateErrorResponse("CreateInputRecording", e, parameters);
}
}
// ===== Touch & Gesture Tools Implementation =====
private string SetupMultitouch(Dictionary<string, string> parameters)
{
try
{
var maxTouches = int.Parse(parameters.GetValueOrDefault("maxTouches", "10"));
var touchSensitivity = float.Parse(parameters.GetValueOrDefault("touchSensitivity", "1"));
var enableGestures = bool.Parse(parameters.GetValueOrDefault("enableGestures", "true"));
var touchVisualization = bool.Parse(parameters.GetValueOrDefault("touchVisualization", "false"));
var multitouchGO = new GameObject("MultitouchController");
var controller = multitouchGO.AddComponent<MultitouchController>();
controller.maxTouches = maxTouches;
controller.touchSensitivity = touchSensitivity;
controller.enableGestures = enableGestures;
// Touch visualization settings
if (touchVisualization)
{
for (int i = 0; i < maxTouches; i++)
{
var touchIndicator = CreatePrimitiveWithMaterial(PrimitiveType.Sphere);
touchIndicator.name = $"TouchIndicator_{i}";
touchIndicator.transform.SetParent(multitouchGO.transform);
touchIndicator.transform.localScale = Vector3.one * 0.5f;
touchIndicator.SetActive(false);
}
}
return $"[NexusInput] Multitouch Controller configured successfully!\n" +
$" Max Touch Count: {maxTouches}\n" +
$" Sensitivity: {touchSensitivity}\n" +
$" Gestures: {enableGestures}\n" +
$" Visualization: {touchVisualization}\n" +
$" Tip: Handles multiple simultaneous touches on mobile devices";
}
catch (Exception e)
{
return CreateErrorResponse("SetupMultitouch", e, parameters);
}
}
private string CreatePinchZoom(Dictionary<string, string> parameters)
{
try
{
var targetObject = parameters.GetValueOrDefault("targetObject", "Main Camera");
var zoomType = parameters.GetValueOrDefault("zoomType", "Camera");
var minZoom = float.Parse(parameters.GetValueOrDefault("minZoom", "0.5"));
var maxZoom = float.Parse(parameters.GetValueOrDefault("maxZoom", "3"));
var zoomSpeed = float.Parse(parameters.GetValueOrDefault("zoomSpeed", "1"));
var smoothing = bool.Parse(parameters.GetValueOrDefault("smoothing", "true"));
GameObject target = GameObject.Find(targetObject);
if (target == null)
{
target = Camera.main?.gameObject;
if (target == null)
{
return $"Target object '{targetObject}' not found";
}
}
var pinchZoom = target.AddComponent<PinchZoomController>();
pinchZoom.zoomType = zoomType;
pinchZoom.minZoom = minZoom;
pinchZoom.maxZoom = maxZoom;
pinchZoom.zoomSpeed = zoomSpeed;
pinchZoom.smoothing = smoothing;
return $"[NexusInput] Pinch Zoom added to '{target.name}' successfully!\n" +
$" Zoom Type: {zoomType}\n" +
$" Zoom Range: {minZoom} - {maxZoom}\n" +
$" Speed: {zoomSpeed}\n" +
$" Smoothing: {smoothing}\n" +
$" Tip: Use two fingers to pinch in/out for zooming";
}
catch (Exception e)
{
return CreateErrorResponse("CreatePinchZoom", e, parameters);
}
}
private string SetupSwipeDetection(Dictionary<string, string> parameters)
{
try
{
var swipeDirectionsJson = parameters.GetValueOrDefault("swipeDirections", "[\"All\"]");
var swipeDirections = JsonConvert.DeserializeObject<List<string>>(swipeDirectionsJson);
var minSwipeDistance = float.Parse(parameters.GetValueOrDefault("minSwipeDistance", "50"));
var maxSwipeTime = float.Parse(parameters.GetValueOrDefault("maxSwipeTime", "0.5"));
var detectDiagonals = bool.Parse(parameters.GetValueOrDefault("detectDiagonals", "false"));
var swipeGO = new GameObject("SwipeDetector");
var detector = swipeGO.AddComponent<SwipeDetector>();
detector.swipeDirections = swipeDirections;
detector.minSwipeDistance = minSwipeDistance;
detector.maxSwipeTime = maxSwipeTime;
detector.detectDiagonals = detectDiagonals;
return $"[NexusInput] Swipe Detection configured successfully!\n" +
$" Detection Directions: {string.Join(", ", swipeDirections)}\n" +
$" Min Distance: {minSwipeDistance}px\n" +
$" Max Time: {maxSwipeTime} seconds\n" +
$" Diagonal Detection: {detectDiagonals}\n" +
$" Tip: Swipe the screen to perform actions";
}
catch (Exception e)
{
return CreateErrorResponse("SetupSwipeDetection", e, parameters);
}
}
private string CreateDragDrop(Dictionary<string, string> parameters)
{
try
{
var draggableObject = parameters.GetValueOrDefault("draggableObject", "");
var dropZonesJson = parameters.GetValueOrDefault("dropZones", "[]");
var dropZones = JsonConvert.DeserializeObject<List<string>>(dropZonesJson);
var snapToGrid = bool.Parse(parameters.GetValueOrDefault("snapToGrid", "false"));
var gridSize = float.Parse(parameters.GetValueOrDefault("gridSize", "1"));
var showGhost = bool.Parse(parameters.GetValueOrDefault("showGhost", "true"));
GameObject dragObj = GameObject.Find(draggableObject);
if (dragObj == null)
{
dragObj = new GameObject(draggableObject);
}
var dragHandler = dragObj.AddComponent<DragDropHandler>();
dragHandler.dropZones = dropZones;
dragHandler.snapToGrid = snapToGrid;
dragHandler.gridSize = gridSize;
dragHandler.showGhost = showGhost;
// Enable Raycast Target for objects within Canvas
var graphic = dragObj.GetComponent<UnityEngine.UI.Graphic>();
if (graphic != null)
{
graphic.raycastTarget = true;
}
return $"[NexusInput] Drag & Drop functionality added to '{dragObj.name}'!\n" +
$" Drop Zones: {dropZones.Count}\n" +
$" Grid Snap: {snapToGrid} (Size: {gridSize})\n" +
$" Ghost Display: {showGhost}\n" +
$" Tip: Drag objects and drop them at designated locations";
}
catch (Exception e)
{
return CreateErrorResponse("CreateDragDrop", e, parameters);
}
}
private string SetupTouchEffects(Dictionary<string, string> parameters)
{
try
{
var effectType = parameters.GetValueOrDefault("effectType", "Ripple");
var color = parameters.GetValueOrDefault("color", "#FFFFFF");
var size = float.Parse(parameters.GetValueOrDefault("size", "1"));
var duration = float.Parse(parameters.GetValueOrDefault("duration", "0.5"));
var fadeOut = bool.Parse(parameters.GetValueOrDefault("fadeOut", "true"));
var touchEffectGO = new GameObject("TouchEffectController");
var controller = touchEffectGO.AddComponent<TouchEffectController>();
controller.effectType = effectType;
controller.effectColor = ColorUtility.TryParseHtmlString(color, out Color parsedColor) ? parsedColor : Color.white;
controller.effectSize = size;
controller.effectDuration = duration;
controller.fadeOut = fadeOut;
// Create effect prefab
GameObject effectPrefab = null;
switch (effectType)
{
case "Ripple":
effectPrefab = CreateRippleEffect(parsedColor, size);
break;
case "Glow":
effectPrefab = CreateGlowEffect(parsedColor, size);
break;
case "Particle":
effectPrefab = CreateParticleEffect(parsedColor, size);
break;
}
if (effectPrefab != null)
{
controller.effectPrefab = effectPrefab;
}
return $"[NexusInput] Touch Effects configured successfully!\n" +
$" Effect Type: {effectType}\n" +
$" Color: {color}\n" +
$" Size: {size}\n" +
$" Duration: {duration} seconds\n" +
$" Fade Out: {fadeOut}\n" +
$" Tip: Displays visual feedback at touch positions";
}
catch (Exception e)
{
return CreateErrorResponse("SetupTouchEffects", e, parameters);
}
}
// Helper methods
private GameObject CreateRippleEffect(Color color, float size)
{
var ripple = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder);
ripple.name = "RippleEffect";
ripple.transform.localScale = new Vector3(size, 0.01f, size);
var renderer = ripple.GetComponent<Renderer>();
var material = CreateRenderPipelineCompatibleMaterial();
material.color = new Color(color.r, color.g, color.b, 0.5f);
material.SetFloat("_Mode", 3); // Transparent
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
renderer.material = material;
ripple.SetActive(false);
return ripple;
}
private GameObject CreateGlowEffect(Color color, float size)
{
var glow = CreatePrimitiveWithMaterial(PrimitiveType.Sphere);
glow.name = "GlowEffect";
glow.transform.localScale = Vector3.one * size;
var renderer = glow.GetComponent<Renderer>();
var material = CreateRenderPipelineCompatibleMaterial();
material.EnableKeyword("_EMISSION");
material.SetColor("_EmissionColor", color * 2f);
renderer.material = material;
glow.SetActive(false);
return glow;
}
private GameObject CreateParticleEffect(Color color, float size)
{
var particleGO = new GameObject("ParticleEffect");
var particleSystem = particleGO.AddComponent<ParticleSystem>();
var main = particleSystem.main;
main.startLifetime = 1f;
main.startSpeed = 2f;
main.startSize = size * 0.1f;
main.startColor = color;
var emission = particleSystem.emission;
emission.SetBursts(new ParticleSystem.Burst[] {
new ParticleSystem.Burst(0.0f, 20)
});
var shape = particleSystem.shape;
shape.shapeType = ParticleSystemShapeType.Circle;
shape.radius = 0.1f;
particleGO.SetActive(false);
return particleGO;
}
// Custom component class definition (temporary implementation)
[System.Serializable]
public class CustomInputInfo : MonoBehaviour
{
public string actionName;
public string bindingType;
public string actionMap;
}
[System.Serializable]
public class GestureData : MonoBehaviour
{
public string gestureName;
public string gestureType;
public float minDistance;
public float maxTime;
}
[System.Serializable]
public class InputValidator : MonoBehaviour
{
public string pattern;
public int minLength;
public int maxLength;
public string errorMessage;
public bool realTimeValidation;
}
[System.Serializable]
public class AccessibilitySettings : MonoBehaviour
{
public List<string> features;
public float buttonScaling;
public string contrastLevel;
}
[System.Serializable]
public class InputRecorder : MonoBehaviour
{
public string recordingMode;
public bool includeTimestamps;
public float maxRecordingTime;
}
[System.Serializable]
public class MultitouchController : MonoBehaviour
{
public int maxTouches;
public float touchSensitivity;
public bool enableGestures;
}
[System.Serializable]
public class PinchZoomController : MonoBehaviour
{
public string zoomType;
public float minZoom;
public float maxZoom;
public float zoomSpeed;
public bool smoothing;
}
[System.Serializable]
public class SwipeDetector : MonoBehaviour
{
public List<string> swipeDirections;
public float minSwipeDistance;
public float maxSwipeTime;
public bool detectDiagonals;
}
[System.Serializable]
public class DragDropHandler : MonoBehaviour
{
public List<string> dropZones;
public bool snapToGrid;
public float gridSize;
public bool showGhost;
}
[System.Serializable]
public class TouchEffectController : MonoBehaviour
{
public string effectType;
public Color effectColor;
public float effectSize;
public float effectDuration;
public bool fadeOut;
public GameObject effectPrefab;
}
// ===== Visual Enhancement Tools Implementation =====
private string CreateParticlePreset(Dictionary<string, string> parameters)
{
try
{
var effectName = parameters.GetValueOrDefault("effectName", "ParticleEffect");
var preset = parameters.GetValueOrDefault("preset", "Fire");
var lifetime = float.Parse(parameters.GetValueOrDefault("lifetime", "5"));
var startSpeed = float.Parse(parameters.GetValueOrDefault("startSpeed", "5"));
var startSize = float.Parse(parameters.GetValueOrDefault("startSize", "1"));
var emission = float.Parse(parameters.GetValueOrDefault("emission", "10"));
var gravity = float.Parse(parameters.GetValueOrDefault("gravity", "0"));
var shape = parameters.GetValueOrDefault("shape", "Cone");
var usePhysics = bool.Parse(parameters.GetValueOrDefault("usePhysics", "false"));
var particleGO = new GameObject(effectName);
var particleSystem = particleGO.AddComponent<ParticleSystem>();
// Main module settings
var main = particleSystem.main;
main.startLifetime = lifetime;
main.startSpeed = startSpeed;
main.startSize = startSize;
main.gravityModifier = gravity;
// Configure by preset
switch (preset)
{
case "Fire":
SetupFireParticles(particleSystem);
break;
case "Smoke":
SetupSmokeParticles(particleSystem);
break;
case "Explosion":
SetupExplosionParticles(particleSystem);
break;
case "Rain":
SetupRainParticles(particleSystem);
break;
case "Snow":
SetupSnowParticles(particleSystem);
break;
case "Sparkles":
SetupSparklesParticles(particleSystem);
break;
case "Magic":
SetupMagicParticles(particleSystem);
break;
case "Lightning":
SetupLightningParticles(particleSystem);
break;
case "Tornado":
SetupTornadoParticles(particleSystem);
break;
case "Galaxy":
SetupGalaxyParticles(particleSystem);
break;
}
// Shape module settings
var shapeModule = particleSystem.shape;
switch (shape)
{
case "Sphere":
shapeModule.shapeType = ParticleSystemShapeType.Sphere;
break;
case "Cone":
shapeModule.shapeType = ParticleSystemShapeType.Cone;
shapeModule.angle = 25f;
break;
case "Box":
shapeModule.shapeType = ParticleSystemShapeType.Box;
break;
case "Circle":
shapeModule.shapeType = ParticleSystemShapeType.Circle;
break;
case "Edge":
shapeModule.shapeType = ParticleSystemShapeType.SingleSidedEdge;
break;
}
// Emission module
var emissionModule = particleSystem.emission;
emissionModule.rateOverTime = emission;
// Physics settings
if (usePhysics)
{
var collision = particleSystem.collision;
collision.enabled = true;
collision.type = ParticleSystemCollisionType.World;
collision.bounce = 0.5f;
}
lastCreatedObject = particleGO;
Selection.activeGameObject = particleGO;
return $"[NexusVisual] Particle Effect '{effectName}' created successfully!\n" +
$" Preset: {preset}\n" +
$" Lifetime: {lifetime} seconds\n" +
$" Emission: {emission}/second\n" +
$" Physics: {usePhysics}\n" +
$" Tip: Beautiful effects configured according to preset";
}
catch (Exception e)
{
return CreateErrorResponse("CreateParticlePreset", e, parameters);
}
}
// ===== Professional Particle Preset System =====
// Based on AAA game industry standards and Unity best practices
private void SetupFireParticles(ParticleSystem ps)
{
// Professional campfire/torch fire effect
var main = ps.main;
main.duration = 5f;
main.loop = true;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.8f, 1.5f);
main.startSpeed = new ParticleSystem.MinMaxCurve(1f, 2f);
main.startSize = new ParticleSystem.MinMaxCurve(0.3f, 0.8f);
main.startRotation = new ParticleSystem.MinMaxCurve(0f, Mathf.PI * 2f);
main.gravityModifier = -0.3f; // Fire rises upward
main.maxParticles = 300;
main.simulationSpace = ParticleSystemSimulationSpace.World;
// Emission - continuous with bursts for flickers
var emission = ps.emission;
emission.rateOverTime = 50f;
emission.SetBursts(new ParticleSystem.Burst[] {
new ParticleSystem.Burst(0f, 5, 15, 5, 0.1f)
});
// Shape - cone pointing up for natural fire shape
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Cone;
shape.angle = 15f;
shape.radius = 0.2f;
shape.radiusThickness = 1f;
shape.rotation = new Vector3(-90f, 0f, 0f);
// Velocity - turbulent upward motion
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.space = ParticleSystemSimulationSpace.Local;
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(1f, 3f);
velocityOverLifetime.x = new ParticleSystem.MinMaxCurve(-0.3f, 0.3f);
velocityOverLifetime.z = new ParticleSystem.MinMaxCurve(-0.3f, 0.3f);
// Color gradient: white core -> yellow -> orange -> red -> transparent
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(1f, 0.95f, 0.8f), 0f), // White-yellow core
new GradientColorKey(new Color(1f, 0.7f, 0.2f), 0.2f), // Bright orange
new GradientColorKey(new Color(1f, 0.4f, 0.1f), 0.5f), // Deep orange
new GradientColorKey(new Color(0.8f, 0.2f, 0.05f), 0.8f),// Dark red
new GradientColorKey(new Color(0.3f, 0.1f, 0.05f), 1f) // Almost black
},
new GradientAlphaKey[] {
new GradientAlphaKey(0f, 0f),
new GradientAlphaKey(1f, 0.1f),
new GradientAlphaKey(0.8f, 0.5f),
new GradientAlphaKey(0.3f, 0.8f),
new GradientAlphaKey(0f, 1f)
}
);
colorOverLifetime.color = gradient;
// Size over lifetime - expand then shrink
var sizeOverLifetime = ps.sizeOverLifetime;
sizeOverLifetime.enabled = true;
var sizeCurve = new AnimationCurve(
new Keyframe(0f, 0.2f),
new Keyframe(0.2f, 1f),
new Keyframe(0.7f, 0.8f),
new Keyframe(1f, 0.1f)
);
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1f, sizeCurve);
// Noise for flickering
var noise = ps.noise;
noise.enabled = true;
noise.strength = 0.5f;
noise.frequency = 2f;
noise.scrollSpeed = 1f;
noise.damping = true;
// Renderer settings for additive blending
var renderer = ps.GetComponent<ParticleSystemRenderer>();
if (renderer != null)
{
renderer.renderMode = ParticleSystemRenderMode.Billboard;
var mat = CreateParticleMaterial("fire");
if (mat != null) renderer.material = mat;
}
// Add child embers/sparks
CreateFireEmbers(ps.gameObject);
}
private void CreateFireEmbers(GameObject parent)
{
var embersGO = new GameObject("Embers");
embersGO.transform.SetParent(parent.transform);
embersGO.transform.localPosition = Vector3.zero;
var embers = embersGO.AddComponent<ParticleSystem>();
var main = embers.main;
main.startLifetime = new ParticleSystem.MinMaxCurve(1f, 2f);
main.startSpeed = new ParticleSystem.MinMaxCurve(2f, 4f);
main.startSize = new ParticleSystem.MinMaxCurve(0.02f, 0.08f);
main.gravityModifier = -0.5f;
main.maxParticles = 100;
var emission = embers.emission;
emission.rateOverTime = 10f;
var shape = embers.shape;
shape.shapeType = ParticleSystemShapeType.Cone;
shape.angle = 25f;
shape.radius = 0.3f;
shape.rotation = new Vector3(-90f, 0f, 0f);
var colorOverLifetime = embers.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(1f, 0.6f, 0.2f), 0f),
new GradientColorKey(new Color(1f, 0.3f, 0.1f), 0.5f),
new GradientColorKey(new Color(0.5f, 0.1f, 0f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(0.8f, 0.7f),
new GradientAlphaKey(0f, 1f)
}
);
colorOverLifetime.color = gradient;
var noise = embers.noise;
noise.enabled = true;
noise.strength = 1f;
noise.frequency = 0.5f;
}
private void SetupSmokeParticles(ParticleSystem ps)
{
// Professional volumetric smoke effect
var main = ps.main;
main.duration = 5f;
main.loop = true;
main.startLifetime = new ParticleSystem.MinMaxCurve(4f, 7f);
main.startSpeed = new ParticleSystem.MinMaxCurve(0.5f, 1.5f);
main.startSize = new ParticleSystem.MinMaxCurve(0.5f, 1.5f);
main.startRotation = new ParticleSystem.MinMaxCurve(0f, Mathf.PI * 2f);
main.startColor = new Color(0.4f, 0.4f, 0.4f, 0.3f);
main.gravityModifier = -0.05f; // Slow rise
main.maxParticles = 200;
main.simulationSpace = ParticleSystemSimulationSpace.World;
var emission = ps.emission;
emission.rateOverTime = 15f;
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Cone;
shape.angle = 20f;
shape.radius = 0.3f;
shape.rotation = new Vector3(-90f, 0f, 0f);
// Gradual rise with acceleration then deceleration
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.space = ParticleSystemSimulationSpace.World;
var yCurve = new AnimationCurve(
new Keyframe(0f, 0.5f),
new Keyframe(0.3f, 1.5f),
new Keyframe(1f, 0.3f)
);
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(1f, yCurve);
// Size expands as smoke disperses
var sizeOverLifetime = ps.sizeOverLifetime;
sizeOverLifetime.enabled = true;
var sizeCurve = new AnimationCurve(
new Keyframe(0f, 0.3f),
new Keyframe(0.3f, 0.7f),
new Keyframe(1f, 1.5f)
);
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(2f, sizeCurve);
// Fade out naturally
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0.5f, 0.5f, 0.5f), 0f),
new GradientColorKey(new Color(0.4f, 0.4f, 0.45f), 0.5f),
new GradientColorKey(new Color(0.35f, 0.35f, 0.4f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(0f, 0f),
new GradientAlphaKey(0.4f, 0.1f),
new GradientAlphaKey(0.3f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
colorOverLifetime.color = gradient;
// Rotation for organic movement
var rotationOverLifetime = ps.rotationOverLifetime;
rotationOverLifetime.enabled = true;
rotationOverLifetime.z = new ParticleSystem.MinMaxCurve(-0.5f, 0.5f);
// Noise for turbulent organic motion
var noise = ps.noise;
noise.enabled = true;
noise.strength = 0.8f;
noise.frequency = 0.3f;
noise.scrollSpeed = 0.5f;
noise.damping = true;
noise.octaveCount = 2;
var renderer = ps.GetComponent<ParticleSystemRenderer>();
if (renderer != null)
{
renderer.renderMode = ParticleSystemRenderMode.Billboard;
var mat = CreateParticleMaterial("smoke");
if (mat != null) renderer.material = mat;
}
}
private void SetupExplosionParticles(ParticleSystem ps)
{
// Professional AAA explosion with multiple layers
var main = ps.main;
main.duration = 2f;
main.loop = false;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.3f, 0.8f);
main.startSpeed = new ParticleSystem.MinMaxCurve(8f, 15f);
main.startSize = new ParticleSystem.MinMaxCurve(0.5f, 1.5f);
main.startRotation = new ParticleSystem.MinMaxCurve(0f, Mathf.PI * 2f);
main.gravityModifier = 0.5f;
main.maxParticles = 200;
main.simulationSpace = ParticleSystemSimulationSpace.World;
// Single burst
var emission = ps.emission;
emission.rateOverTime = 0f;
emission.SetBursts(new ParticleSystem.Burst[] {
new ParticleSystem.Burst(0f, 50, 80)
});
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Sphere;
shape.radius = 0.2f;
// Rapid deceleration (air resistance)
var limitVelocityOverLifetime = ps.limitVelocityOverLifetime;
limitVelocityOverLifetime.enabled = true;
limitVelocityOverLifetime.dampen = 0.2f;
var limitCurve = new AnimationCurve(
new Keyframe(0f, 20f),
new Keyframe(0.3f, 5f),
new Keyframe(1f, 1f)
);
limitVelocityOverLifetime.limit = new ParticleSystem.MinMaxCurve(1f, limitCurve);
// Explosion color: bright flash -> orange -> dark smoke
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(1f, 1f, 0.9f), 0f), // White flash
new GradientColorKey(new Color(1f, 0.8f, 0.3f), 0.1f), // Yellow
new GradientColorKey(new Color(1f, 0.5f, 0.1f), 0.3f), // Orange
new GradientColorKey(new Color(0.6f, 0.3f, 0.1f), 0.6f),// Dark orange
new GradientColorKey(new Color(0.2f, 0.15f, 0.1f), 1f) // Smoke/ash
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(1f, 0.2f),
new GradientAlphaKey(0.6f, 0.5f),
new GradientAlphaKey(0.3f, 0.8f),
new GradientAlphaKey(0f, 1f)
}
);
colorOverLifetime.color = gradient;
// Size: expand then disperse
var sizeOverLifetime = ps.sizeOverLifetime;
sizeOverLifetime.enabled = true;
var sizeCurve = new AnimationCurve(
new Keyframe(0f, 0.5f),
new Keyframe(0.2f, 1f),
new Keyframe(1f, 0.2f)
);
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1f, sizeCurve);
// Add sub-emitters for debris, smoke, and sparks
CreateExplosionDebris(ps.gameObject);
CreateExplosionSmoke(ps.gameObject);
CreateExplosionSparks(ps.gameObject);
var renderer = ps.GetComponent<ParticleSystemRenderer>();
if (renderer != null)
{
renderer.renderMode = ParticleSystemRenderMode.Billboard;
var mat = CreateParticleMaterial("explosion");
if (mat != null) renderer.material = mat;
}
}
private void CreateExplosionDebris(GameObject parent)
{
var debrisGO = new GameObject("Debris");
debrisGO.transform.SetParent(parent.transform);
debrisGO.transform.localPosition = Vector3.zero;
var debris = debrisGO.AddComponent<ParticleSystem>();
var main = debris.main;
main.duration = 2f;
main.loop = false;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.5f, 1.5f);
main.startSpeed = new ParticleSystem.MinMaxCurve(5f, 12f);
main.startSize = new ParticleSystem.MinMaxCurve(0.05f, 0.2f);
main.gravityModifier = 2f;
main.startColor = new Color(0.3f, 0.25f, 0.2f);
var emission = debris.emission;
emission.rateOverTime = 0f;
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 30, 50) });
var shape = debris.shape;
shape.shapeType = ParticleSystemShapeType.Sphere;
shape.radius = 0.3f;
var collision = debris.collision;
collision.enabled = true;
collision.type = ParticleSystemCollisionType.World;
collision.bounce = 0.3f;
collision.lifetimeLoss = 0.2f;
}
private void CreateExplosionSmoke(GameObject parent)
{
var smokeGO = new GameObject("ExplosionSmoke");
smokeGO.transform.SetParent(parent.transform);
smokeGO.transform.localPosition = Vector3.zero;
var smoke = smokeGO.AddComponent<ParticleSystem>();
var main = smoke.main;
main.duration = 3f;
main.loop = false;
main.startDelay = 0.1f;
main.startLifetime = new ParticleSystem.MinMaxCurve(2f, 4f);
main.startSpeed = new ParticleSystem.MinMaxCurve(2f, 5f);
main.startSize = new ParticleSystem.MinMaxCurve(1f, 2.5f);
main.gravityModifier = -0.1f;
main.startColor = new Color(0.3f, 0.3f, 0.3f, 0.5f);
var emission = smoke.emission;
emission.rateOverTime = 0f;
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0.1f, 20, 30) });
var colorOverLifetime = smoke.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(Color.gray, 0f), new GradientColorKey(new Color(0.3f, 0.3f, 0.3f), 1f) },
new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f), new GradientAlphaKey(0.3f, 0.5f), new GradientAlphaKey(0f, 1f) }
);
colorOverLifetime.color = gradient;
var sizeOverLifetime = smoke.sizeOverLifetime;
sizeOverLifetime.enabled = true;
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1f, AnimationCurve.Linear(0f, 0.5f, 1f, 1.5f));
}
private void CreateExplosionSparks(GameObject parent)
{
var sparksGO = new GameObject("Sparks");
sparksGO.transform.SetParent(parent.transform);
sparksGO.transform.localPosition = Vector3.zero;
var sparks = sparksGO.AddComponent<ParticleSystem>();
var main = sparks.main;
main.duration = 1f;
main.loop = false;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.3f, 0.8f);
main.startSpeed = new ParticleSystem.MinMaxCurve(10f, 20f);
main.startSize = new ParticleSystem.MinMaxCurve(0.02f, 0.05f);
main.gravityModifier = 1f;
main.startColor = new Color(1f, 0.8f, 0.3f);
var emission = sparks.emission;
emission.rateOverTime = 0f;
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 50, 100) });
var trails = sparks.trails;
trails.enabled = true;
trails.lifetime = 0.1f;
trails.widthOverTrail = new ParticleSystem.MinMaxCurve(1f, AnimationCurve.Linear(0f, 1f, 1f, 0f));
var colorOverLifetime = sparks.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(new Color(1f, 0.9f, 0.5f), 0f), new GradientColorKey(new Color(1f, 0.4f, 0.1f), 1f) },
new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0f, 1f) }
);
colorOverLifetime.color = gradient;
}
private void SetupRainParticles(ParticleSystem ps)
{
// Professional rain system - follow camera
var main = ps.main;
main.duration = 5f;
main.loop = true;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.5f, 1f);
main.startSpeed = new ParticleSystem.MinMaxCurve(15f, 25f);
main.startSize = new ParticleSystem.MinMaxCurve(0.01f, 0.03f);
main.startColor = new Color(0.7f, 0.75f, 0.85f, 0.4f);
main.gravityModifier = 0f; // Speed handles fall
main.maxParticles = 5000;
main.simulationSpace = ParticleSystemSimulationSpace.World;
var emission = ps.emission;
emission.rateOverTime = 2000f;
// Large box above camera
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(30f, 0f, 30f);
shape.position = new Vector3(0f, 15f, 0f);
shape.rotation = new Vector3(0f, 0f, 0f);
// Stretch particles for rain streak effect
var renderer = ps.GetComponent<ParticleSystemRenderer>();
if (renderer != null)
{
renderer.renderMode = ParticleSystemRenderMode.Stretch;
renderer.lengthScale = 2f;
renderer.velocityScale = 0.02f;
var mat = CreateParticleMaterial("rain");
if (mat != null) renderer.material = mat;
}
// Slight wind variation
var noise = ps.noise;
noise.enabled = true;
noise.strength = 0.1f;
noise.frequency = 0.5f;
// Collision for splash effects
var collision = ps.collision;
collision.enabled = true;
collision.type = ParticleSystemCollisionType.World;
collision.bounce = 0f;
collision.lifetimeLoss = 1f;
collision.sendCollisionMessages = true;
// Create splash sub-emitter
CreateRainSplash(ps.gameObject);
}
private void CreateRainSplash(GameObject parent)
{
var splashGO = new GameObject("RainSplash");
splashGO.transform.SetParent(parent.transform);
splashGO.transform.localPosition = Vector3.zero;
var splash = splashGO.AddComponent<ParticleSystem>();
var main = splash.main;
main.duration = 1f;
main.loop = true;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.1f, 0.3f);
main.startSpeed = new ParticleSystem.MinMaxCurve(0.5f, 2f);
main.startSize = new ParticleSystem.MinMaxCurve(0.02f, 0.05f);
main.startColor = new Color(0.8f, 0.85f, 0.9f, 0.5f);
main.gravityModifier = 2f;
main.maxParticles = 500;
var emission = splash.emission;
emission.rateOverTime = 0f; // Triggered by collision
var shape = splash.shape;
shape.shapeType = ParticleSystemShapeType.Hemisphere;
shape.radius = 0.05f;
shape.rotation = new Vector3(-90f, 0f, 0f);
var colorOverLifetime = splash.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f), new GradientAlphaKey(0f, 1f) }
);
colorOverLifetime.color = gradient;
}
private void SetupSnowParticles(ParticleSystem ps)
{
// Professional snow with natural floating motion
var main = ps.main;
main.duration = 10f;
main.loop = true;
main.startLifetime = new ParticleSystem.MinMaxCurve(5f, 10f);
main.startSpeed = new ParticleSystem.MinMaxCurve(0.5f, 1.5f);
main.startSize = new ParticleSystem.MinMaxCurve(0.03f, 0.15f);
main.startRotation = new ParticleSystem.MinMaxCurve(0f, Mathf.PI * 2f);
main.startColor = new ParticleSystem.MinMaxGradient(
new Color(1f, 1f, 1f, 0.8f),
new Color(0.95f, 0.98f, 1f, 0.9f)
);
main.gravityModifier = 0.05f;
main.maxParticles = 3000;
main.simulationSpace = ParticleSystemSimulationSpace.World;
var emission = ps.emission;
emission.rateOverTime = 300f;
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(40f, 0f, 40f);
shape.position = new Vector3(0f, 20f, 0f);
// Natural floating/drifting motion
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.space = ParticleSystemSimulationSpace.World;
velocityOverLifetime.x = new ParticleSystem.MinMaxCurve(-0.3f, 0.3f);
velocityOverLifetime.z = new ParticleSystem.MinMaxCurve(-0.3f, 0.3f);
// Turbulent noise for realistic snowflake motion
var noise = ps.noise;
noise.enabled = true;
noise.strength = 0.5f;
noise.frequency = 0.3f;
noise.scrollSpeed = 0.2f;
noise.damping = false;
noise.octaveCount = 2;
// Slow rotation
var rotationOverLifetime = ps.rotationOverLifetime;
rotationOverLifetime.enabled = true;
rotationOverLifetime.z = new ParticleSystem.MinMaxCurve(-0.5f, 0.5f);
// Collision with ground
var collision = ps.collision;
collision.enabled = true;
collision.type = ParticleSystemCollisionType.World;
collision.bounce = 0f;
collision.lifetimeLoss = 1f;
var renderer = ps.GetComponent<ParticleSystemRenderer>();
if (renderer != null)
{
renderer.renderMode = ParticleSystemRenderMode.Billboard;
var mat = CreateParticleMaterial("snow");
if (mat != null) renderer.material = mat;
}
}
private void SetupSparklesParticles(ParticleSystem ps)
{
// Magical sparkle/glitter effect
var main = ps.main;
main.duration = 5f;
main.loop = true;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.5f, 1.5f);
main.startSpeed = new ParticleSystem.MinMaxCurve(0.5f, 2f);
main.startSize = new ParticleSystem.MinMaxCurve(0.05f, 0.15f);
main.startColor = new ParticleSystem.MinMaxGradient(
new Color(1f, 1f, 0.8f),
new Color(1f, 0.9f, 0.6f)
);
main.maxParticles = 200;
var emission = ps.emission;
emission.rateOverTime = 30f;
emission.SetBursts(new ParticleSystem.Burst[] {
new ParticleSystem.Burst(0f, 5, 10, 3, 0.5f)
});
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Sphere;
shape.radius = 1f;
// Twinkle effect with size pulsing
var sizeOverLifetime = ps.sizeOverLifetime;
sizeOverLifetime.enabled = true;
var sizeCurve = new AnimationCurve(
new Keyframe(0f, 0f),
new Keyframe(0.1f, 1f),
new Keyframe(0.3f, 0.3f),
new Keyframe(0.5f, 1f),
new Keyframe(0.7f, 0.3f),
new Keyframe(0.9f, 1f),
new Keyframe(1f, 0f)
);
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1f, sizeCurve);
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(Color.white, 0f),
new GradientColorKey(new Color(1f, 0.95f, 0.7f), 0.5f),
new GradientColorKey(Color.white, 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(0f, 0f),
new GradientAlphaKey(1f, 0.2f),
new GradientAlphaKey(0.8f, 0.8f),
new GradientAlphaKey(0f, 1f)
}
);
colorOverLifetime.color = gradient;
// Slow orbital motion
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.orbitalY = new ParticleSystem.MinMaxCurve(0.5f);
var renderer = ps.GetComponent<ParticleSystemRenderer>();
if (renderer != null)
{
renderer.renderMode = ParticleSystemRenderMode.Billboard;
var mat = CreateParticleMaterial("sparkle");
if (mat != null) renderer.material = mat;
}
}
private void SetupMagicParticles(ParticleSystem ps)
{
// Professional magical aura effect
var main = ps.main;
main.duration = 5f;
main.loop = true;
main.startLifetime = new ParticleSystem.MinMaxCurve(1f, 2.5f);
main.startSpeed = new ParticleSystem.MinMaxCurve(0.5f, 1.5f);
main.startSize = new ParticleSystem.MinMaxCurve(0.1f, 0.3f);
main.startColor = new ParticleSystem.MinMaxGradient(
new Color(0.3f, 0.1f, 1f), // Deep purple
new Color(0.1f, 0.5f, 1f) // Cyan blue
);
main.maxParticles = 300;
var emission = ps.emission;
emission.rateOverTime = 40f;
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Circle;
shape.radius = 0.8f;
shape.radiusThickness = 0f; // Emit from edge
shape.arc = 360f;
shape.rotation = new Vector3(90f, 0f, 0f);
// Spiral upward motion
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.space = ParticleSystemSimulationSpace.Local;
velocityOverLifetime.orbitalY = new ParticleSystem.MinMaxCurve(1.5f, 2.5f);
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(0.5f, 1f);
velocityOverLifetime.radial = new ParticleSystem.MinMaxCurve(-0.2f, 0.2f);
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0.5f, 0.2f, 1f), 0f),
new GradientColorKey(new Color(0.2f, 0.6f, 1f), 0.5f),
new GradientColorKey(new Color(0.8f, 0.4f, 1f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(0f, 0f),
new GradientAlphaKey(1f, 0.2f),
new GradientAlphaKey(0.7f, 0.7f),
new GradientAlphaKey(0f, 1f)
}
);
colorOverLifetime.color = gradient;
var sizeOverLifetime = ps.sizeOverLifetime;
sizeOverLifetime.enabled = true;
var sizeCurve = new AnimationCurve(
new Keyframe(0f, 0.5f),
new Keyframe(0.3f, 1f),
new Keyframe(1f, 0.2f)
);
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1f, sizeCurve);
// Magic trails
var trails = ps.trails;
trails.enabled = true;
trails.lifetime = 0.4f;
trails.minVertexDistance = 0.05f;
trails.worldSpace = false;
trails.widthOverTrail = new ParticleSystem.MinMaxCurve(1f, AnimationCurve.Linear(0f, 1f, 1f, 0f));
trails.colorOverLifetime = gradient;
trails.inheritParticleColor = true;
// Particle lights for glow
var lights = ps.lights;
lights.enabled = true;
lights.ratio = 0.3f;
lights.intensity = new ParticleSystem.MinMaxCurve(0.5f, 1f);
lights.range = new ParticleSystem.MinMaxCurve(1f, 2f);
lights.useParticleColor = true;
// Add inner core particles
CreateMagicCore(ps.gameObject);
}
private void CreateMagicCore(GameObject parent)
{
var coreGO = new GameObject("MagicCore");
coreGO.transform.SetParent(parent.transform);
coreGO.transform.localPosition = Vector3.zero;
var core = coreGO.AddComponent<ParticleSystem>();
var main = core.main;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.3f, 0.6f);
main.startSpeed = 0f;
main.startSize = new ParticleSystem.MinMaxCurve(0.2f, 0.5f);
main.startColor = new Color(0.9f, 0.8f, 1f);
var emission = core.emission;
emission.rateOverTime = 15f;
var shape = core.shape;
shape.shapeType = ParticleSystemShapeType.Sphere;
shape.radius = 0.1f;
var colorOverLifetime = core.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(new Color(0.8f, 0.7f, 1f), 1f) },
new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0f, 1f) }
);
colorOverLifetime.color = gradient;
}
private void SetupLightningParticles(ParticleSystem ps)
{
// Realistic lightning bolt effect
var main = ps.main;
main.duration = 0.5f;
main.loop = false;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.05f, 0.15f);
main.startSpeed = new ParticleSystem.MinMaxCurve(50f, 100f);
main.startSize = new ParticleSystem.MinMaxCurve(0.1f, 0.3f);
main.startColor = new Color(0.8f, 0.85f, 1f);
main.maxParticles = 50;
var emission = ps.emission;
emission.rateOverTime = 0f;
emission.SetBursts(new ParticleSystem.Burst[] {
new ParticleSystem.Burst(0f, 10, 20),
new ParticleSystem.Burst(0.05f, 5, 10),
new ParticleSystem.Burst(0.1f, 3, 5)
});
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Cone;
shape.angle = 5f;
shape.radius = 0.1f;
shape.rotation = new Vector3(-90f, 0f, 0f);
// Branch-like trails
var trails = ps.trails;
trails.enabled = true;
trails.lifetime = 0.1f;
trails.minVertexDistance = 0.1f;
trails.worldSpace = true;
trails.dieWithParticles = true;
trails.widthOverTrail = new ParticleSystem.MinMaxCurve(1f, AnimationCurve.Linear(0f, 1f, 1f, 0f));
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(Color.white, 0f),
new GradientColorKey(new Color(0.7f, 0.8f, 1f), 0.5f),
new GradientColorKey(new Color(0.5f, 0.6f, 1f), 1f)
},
new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0.5f, 0.5f), new GradientAlphaKey(0f, 1f) }
);
colorOverLifetime.color = gradient;
// Random branch directions
var noise = ps.noise;
noise.enabled = true;
noise.strength = 5f;
noise.frequency = 3f;
// Add flash light
CreateLightningFlash(ps.gameObject);
}
private void CreateLightningFlash(GameObject parent)
{
var flashGO = new GameObject("LightningFlash");
flashGO.transform.SetParent(parent.transform);
flashGO.transform.localPosition = Vector3.zero;
var light = flashGO.AddComponent<Light>();
light.type = LightType.Point;
light.color = new Color(0.8f, 0.85f, 1f);
light.intensity = 5f;
light.range = 30f;
}
private void SetupTornadoParticles(ParticleSystem ps)
{
// Professional tornado/vortex effect
var main = ps.main;
main.duration = 10f;
main.loop = true;
main.startLifetime = new ParticleSystem.MinMaxCurve(2f, 4f);
main.startSpeed = new ParticleSystem.MinMaxCurve(2f, 5f);
main.startSize = new ParticleSystem.MinMaxCurve(0.3f, 1f);
main.startColor = new Color(0.5f, 0.45f, 0.4f, 0.6f);
main.gravityModifier = -0.5f;
main.maxParticles = 500;
var emission = ps.emission;
emission.rateOverTime = 100f;
// Cone at base, expanding upward
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Cone;
shape.angle = 5f;
shape.radius = 2f;
shape.radiusThickness = 0f;
shape.rotation = new Vector3(-90f, 0f, 0f);
// Strong orbital motion with upward pull
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.space = ParticleSystemSimulationSpace.Local;
velocityOverLifetime.orbitalY = new ParticleSystem.MinMaxCurve(3f, 6f);
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(3f, 6f);
velocityOverLifetime.radial = new ParticleSystem.MinMaxCurve(-0.5f, 0.5f);
// Expand as particles rise
var sizeOverLifetime = ps.sizeOverLifetime;
sizeOverLifetime.enabled = true;
var sizeCurve = new AnimationCurve(
new Keyframe(0f, 0.5f),
new Keyframe(0.5f, 1f),
new Keyframe(1f, 1.5f)
);
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1f, sizeCurve);
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0.6f, 0.55f, 0.5f), 0f),
new GradientColorKey(new Color(0.4f, 0.38f, 0.35f), 0.5f),
new GradientColorKey(new Color(0.3f, 0.28f, 0.25f), 1f)
},
new GradientAlphaKey[] { new GradientAlphaKey(0.8f, 0f), new GradientAlphaKey(0.5f, 0.7f), new GradientAlphaKey(0f, 1f) }
);
colorOverLifetime.color = gradient;
// Turbulence
var noise = ps.noise;
noise.enabled = true;
noise.strength = 1f;
noise.frequency = 1f;
noise.scrollSpeed = 2f;
// Add debris layer
CreateTornadoDebris(ps.gameObject);
}
private void CreateTornadoDebris(GameObject parent)
{
var debrisGO = new GameObject("TornadoDebris");
debrisGO.transform.SetParent(parent.transform);
debrisGO.transform.localPosition = Vector3.zero;
var debris = debrisGO.AddComponent<ParticleSystem>();
var main = debris.main;
main.startLifetime = new ParticleSystem.MinMaxCurve(1f, 3f);
main.startSpeed = new ParticleSystem.MinMaxCurve(3f, 8f);
main.startSize = new ParticleSystem.MinMaxCurve(0.05f, 0.2f);
main.startColor = new Color(0.4f, 0.35f, 0.3f);
main.gravityModifier = 0.3f;
var emission = debris.emission;
emission.rateOverTime = 30f;
var shape = debris.shape;
shape.shapeType = ParticleSystemShapeType.Circle;
shape.radius = 2f;
shape.rotation = new Vector3(90f, 0f, 0f);
var velocityOverLifetime = debris.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.orbitalY = new ParticleSystem.MinMaxCurve(4f, 8f);
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(2f, 5f);
}
private void SetupGalaxyParticles(ParticleSystem ps)
{
// Professional spiral galaxy effect
var main = ps.main;
main.duration = 20f;
main.loop = true;
main.startLifetime = new ParticleSystem.MinMaxCurve(10f, 20f);
main.startSpeed = 0f;
main.startSize = new ParticleSystem.MinMaxCurve(0.03f, 0.15f);
main.startColor = new ParticleSystem.MinMaxGradient(
Color.white,
new Color(0.7f, 0.8f, 1f)
);
main.maxParticles = 2000;
main.simulationSpace = ParticleSystemSimulationSpace.Local;
var emission = ps.emission;
emission.rateOverTime = 100f;
// Disc shape for galaxy plane
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Circle;
shape.radius = 5f;
shape.radiusThickness = 0.9f;
shape.arc = 360f;
shape.rotation = new Vector3(90f, 0f, 0f);
// Spiral rotation
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.space = ParticleSystemSimulationSpace.Local;
// Inner stars rotate faster (Kepler's laws)
var orbitalCurve = new AnimationCurve(
new Keyframe(0f, 2f),
new Keyframe(1f, 0.5f)
);
velocityOverLifetime.orbitalY = new ParticleSystem.MinMaxCurve(1f, orbitalCurve);
// Star color variation
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(1f, 0.95f, 0.9f), 0f), // White/yellow
new GradientColorKey(new Color(0.9f, 0.95f, 1f), 0.5f), // Blue-white
new GradientColorKey(new Color(1f, 0.8f, 0.7f), 1f) // Orange
},
new GradientAlphaKey[] {
new GradientAlphaKey(0f, 0f),
new GradientAlphaKey(1f, 0.1f),
new GradientAlphaKey(0.8f, 0.9f),
new GradientAlphaKey(0f, 1f)
}
);
colorOverLifetime.color = gradient;
// Twinkle effect
var sizeOverLifetime = ps.sizeOverLifetime;
sizeOverLifetime.enabled = true;
var sizeCurve = new AnimationCurve(
new Keyframe(0f, 0.5f),
new Keyframe(0.25f, 1f),
new Keyframe(0.5f, 0.7f),
new Keyframe(0.75f, 1f),
new Keyframe(1f, 0.5f)
);
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1f, sizeCurve);
// Add galactic core
CreateGalacticCore(ps.gameObject);
// Add nebula clouds
CreateNebulaCloud(ps.gameObject);
}
private void CreateGalacticCore(GameObject parent)
{
var coreGO = new GameObject("GalacticCore");
coreGO.transform.SetParent(parent.transform);
coreGO.transform.localPosition = Vector3.zero;
var core = coreGO.AddComponent<ParticleSystem>();
var main = core.main;
main.startLifetime = new ParticleSystem.MinMaxCurve(5f, 10f);
main.startSpeed = 0f;
main.startSize = new ParticleSystem.MinMaxCurve(0.05f, 0.2f);
main.startColor = new Color(1f, 0.95f, 0.8f);
main.maxParticles = 500;
var emission = core.emission;
emission.rateOverTime = 50f;
var shape = core.shape;
shape.shapeType = ParticleSystemShapeType.Sphere;
shape.radius = 0.5f;
var velocityOverLifetime = core.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.orbitalY = new ParticleSystem.MinMaxCurve(2f, 4f);
// Add glow light
var light = coreGO.AddComponent<Light>();
light.type = LightType.Point;
light.color = new Color(1f, 0.95f, 0.85f);
light.intensity = 2f;
light.range = 3f;
}
private void CreateNebulaCloud(GameObject parent)
{
var nebulaGO = new GameObject("Nebula");
nebulaGO.transform.SetParent(parent.transform);
nebulaGO.transform.localPosition = Vector3.zero;
var nebula = nebulaGO.AddComponent<ParticleSystem>();
var main = nebula.main;
main.startLifetime = new ParticleSystem.MinMaxCurve(15f, 25f);
main.startSpeed = 0f;
main.startSize = new ParticleSystem.MinMaxCurve(1f, 3f);
main.startColor = new ParticleSystem.MinMaxGradient(
new Color(0.3f, 0.1f, 0.5f, 0.1f),
new Color(0.1f, 0.2f, 0.5f, 0.15f)
);
main.maxParticles = 100;
var emission = nebula.emission;
emission.rateOverTime = 5f;
var shape = nebula.shape;
shape.shapeType = ParticleSystemShapeType.Circle;
shape.radius = 4f;
shape.rotation = new Vector3(90f, 0f, 0f);
var velocityOverLifetime = nebula.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.orbitalY = new ParticleSystem.MinMaxCurve(0.2f, 0.5f);
}
private Material CreateParticleMaterial(string type)
{
var pipeline = DetectRenderingPipeline();
Shader shader = null;
// Find appropriate particle shader
if (pipeline == "URP")
{
shader = Shader.Find("Universal Render Pipeline/Particles/Unlit") ??
Shader.Find("Particles/Standard Unlit");
}
else if (pipeline == "HDRP")
{
shader = Shader.Find("HDRP/Particles/Unlit") ??
Shader.Find("Particles/Standard Unlit");
}
else
{
shader = Shader.Find("Particles/Standard Unlit") ??
Shader.Find("Particles/Alpha Blended");
}
if (shader == null) return null;
var mat = new Material(shader);
mat.name = $"Particle_{type}";
// Configure blending based on type
switch (type)
{
case "fire":
case "explosion":
case "sparkle":
case "magic":
// Additive blending for bright effects
mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);
break;
case "smoke":
case "rain":
case "snow":
default:
// Alpha blending for realistic effects
mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
break;
}
mat.SetInt("_ZWrite", 0);
mat.renderQueue = 3000;
return mat;
}
private string CreateAdvancedMaterial(Dictionary<string, string> parameters)
{
try
{
var materialName = parameters.GetValueOrDefault("materialName", "NewMaterial");
var shaderName = parameters.GetValueOrDefault("shader", "Standard");
// Parse properties from nested JSON object or direct parameters
Dictionary<string, string> props = new Dictionary<string, string>();
if (parameters.ContainsKey("properties"))
{
// Properties passed as nested JSON object
try
{
var propsJson = parameters["properties"];
SynLog.Info($"[Synaptic] Parsing properties JSON: {propsJson}");
var propsDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(propsJson);
foreach (var kvp in propsDict)
{
props[kvp.Key] = kvp.Value?.ToString() ?? "";
}
SynLog.Info($"[Synaptic] Parsed {props.Count} properties: {string.Join(", ", props.Keys)}");
}
catch (Exception e)
{
SynLog.Warn($"[Synaptic] Failed to parse properties JSON: {e.Message}. Using direct parameters.");
props = parameters;
}
}
else
{
// Properties passed directly (backward compatibility)
props = parameters;
}
var color = props.GetValueOrDefault("color", "#FFFFFF");
var metallic = float.Parse(props.GetValueOrDefault("metallic", "0"));
var smoothness = float.Parse(props.GetValueOrDefault("smoothness", "0.5"));
var emission = bool.Parse(props.GetValueOrDefault("emission", "false"));
var emissionColor = props.GetValueOrDefault("emissionColor", "#000000");
var emissionIntensity = float.Parse(props.GetValueOrDefault("emissionIntensity", "1"));
SynLog.Info($"[Synaptic] Material properties - Color: {color}, Metallic: {metallic}, Smoothness: {smoothness}");
// Resolve shader with render pipeline compatibility
Shader shader = ResolveShaderName(shaderName);
if (shader == null)
{
return $"Error: Shader '{shaderName}' not found\n\n" +
$"Available shader names:\n" +
$" Standard (Legacy)\n" +
$" Lit (auto-detects pipeline)\n" +
$" Unlit (auto-detects pipeline)\n" +
$" Universal Render Pipeline/Lit (URP)\n" +
$" Universal Render Pipeline/Unlit (URP)\n" +
$" HDRenderPipeline/Lit (HDRP)\n" +
$" HDRenderPipeline/Unlit (HDRP)\n\n" +
$"Current Pipeline: {DetectRenderingPipeline()}\n" +
$"Tip: Use 'Lit' for automatic pipeline detection";
}
// Create Material
Material material = new Material(shader);
material.name = materialName;
SynLog.Info($"[Synaptic] Created material '{materialName}' with shader '{shader.name}' (Pipeline: {DetectRenderingPipeline()})");
// Configure basic properties with pipeline compatibility
if (ColorUtility.TryParseHtmlString(color, out Color parsedColor))
{
SetMaterialColor(material, parsedColor);
}
// Configure PBR properties (pipeline-aware)
if (material.HasProperty("_Metallic"))
{
material.SetFloat("_Metallic", metallic);
SynLog.Info($"[Synaptic] Set _Metallic to {metallic}");
}
else
{
SynLog.Warn($"[Synaptic] Material has no _Metallic property");
}
// URP/HDRP use different smoothness property names
bool smoothnessSet = false;
if (material.HasProperty("_Smoothness"))
{
material.SetFloat("_Smoothness", smoothness);
SynLog.Info($"[Synaptic] Set _Smoothness to {smoothness}");
smoothnessSet = true;
}
else if (material.HasProperty("_Glossiness"))
{
material.SetFloat("_Glossiness", smoothness);
SynLog.Info($"[Synaptic] Set _Glossiness to {smoothness}");
smoothnessSet = true;
}
if (!smoothnessSet)
{
SynLog.Warn($"[Synaptic] Material has no smoothness property");
}
if (material.HasProperty("_GlossyReflections"))
{
material.SetFloat("_GlossyReflections", 1f);
}
// Configure emission with pipeline compatibility
if (emission)
{
if (ColorUtility.TryParseHtmlString(emissionColor, out Color emissiveColor))
{
SetMaterialEmission(material, emissiveColor, emissionIntensity);
}
}
// Special settings by preset
switch (shaderName)
{
case "Water":
SetupWaterMaterial(material);
break;
case "Glass":
SetupGlassMaterial(material);
break;
case "Hologram":
SetupHologramMaterial(material);
break;
case "Toon":
SetupToonMaterial(material);
break;
}
// Save Material
string folderPath = "Assets/Materials";
if (!AssetDatabase.IsValidFolder(folderPath))
{
AssetDatabase.CreateFolder("Assets", "Materials");
}
string assetPath = $"{folderPath}/{materialName}.mat";
assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
AssetDatabase.CreateAsset(material, assetPath);
AssetDatabase.SaveAssets();
return $"Material '{materialName}' created successfully\n" +
$" Shader: {shader.name}\n" +
$" Color: {color}\n" +
$" Metallic: {metallic}, Smoothness: {smoothness}\n" +
$" Emission: {emission}\n" +
$" Pipeline: {DetectRenderingPipeline()}\n" +
$" Path: {assetPath}";
}
catch (Exception e)
{
return CreateErrorResponse("CreateAdvancedMaterial", e, parameters);
}
}
private void SetupWaterMaterial(Material material)
{
material.SetFloat("_Mode", 3); // Transparent
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
material.SetColor("_Color", new Color(0.2f, 0.5f, 0.8f, 0.7f));
material.SetFloat("_Metallic", 0f);
material.SetFloat("_Smoothness", 0.95f);
}
private void SetupGlassMaterial(Material material)
{
material.SetFloat("_Mode", 3); // Transparent
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
material.SetColor("_Color", new Color(1f, 1f, 1f, 0.2f));
material.SetFloat("_Metallic", 0f);
material.SetFloat("_Smoothness", 1f);
}
private void SetupHologramMaterial(Material material)
{
material.SetFloat("_Mode", 3); // Transparent
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_ZWrite", 0);
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
material.EnableKeyword("_EMISSION");
material.SetColor("_Color", new Color(0f, 1f, 1f, 0.5f));
material.SetColor("_EmissionColor", new Color(0f, 1f, 1f) * 2f);
}
private void SetupToonMaterial(Material material)
{
// If Toon shader is not available, use standard shader as alternative
material.SetFloat("_Metallic", 0f);
material.SetFloat("_Smoothness", 0f);
}
private string SetupLightingPreset(Dictionary<string, string> parameters)
{
try
{
var preset = parameters.GetValueOrDefault("preset", "Studio");
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "1"));
var ambientMode = parameters.GetValueOrDefault("ambientMode", "Skybox");
var fog = bool.Parse(parameters.GetValueOrDefault("fog", "false"));
var shadows = parameters.GetValueOrDefault("shadows", "Soft");
// Light settings
var directionalLight = GameObject.Find("Directional Light") ?? new GameObject("Directional Light");
var light = directionalLight.GetComponent<Light>() ?? directionalLight.AddComponent<Light>();
light.type = LightType.Directional;
// Settings by preset
switch (preset)
{
case "Studio":
SetupStudioLighting(light, intensity);
break;
case "Sunset":
SetupSunsetLighting(light, intensity);
break;
case "Night":
SetupNightLighting(light, intensity);
break;
case "Overcast":
SetupOvercastLighting(light, intensity);
break;
case "Desert":
SetupDesertLighting(light, intensity);
break;
case "Forest":
SetupForestLighting(light, intensity);
break;
case "Underwater":
SetupUnderwaterLighting(light, intensity);
break;
case "Space":
SetupSpaceLighting(light, intensity);
break;
case "Neon":
SetupNeonLighting(light, intensity);
break;
}
// Ambient light settings
switch (ambientMode)
{
case "Skybox":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Skybox;
break;
case "Gradient":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
break;
case "Color":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
break;
}
// Shadow settings
switch (shadows)
{
case "None":
light.shadows = LightShadows.None;
break;
case "Hard":
light.shadows = LightShadows.Hard;
break;
case "Soft":
light.shadows = LightShadows.Soft;
break;
}
// Fog settings
RenderSettings.fog = fog;
if (fog && parameters.ContainsKey("fogColor"))
{
if (ColorUtility.TryParseHtmlString(parameters["fogColor"], out Color fogColor))
{
RenderSettings.fogColor = fogColor;
}
RenderSettings.fogDensity = float.Parse(parameters.GetValueOrDefault("fogDensity", "0.01"));
}
return $"[NexusVisual] lighting preset '{preset}' applied successfully!\n" +
$" Intensity: {intensity}\n" +
$" Ambient Mode: {ambientMode}\n" +
$" Shadows: {shadows}\n" +
$" Fog: {fog}\n" +
$" Tip: Scene atmosphere has been dramatically changed";
}
catch (Exception e)
{
return CreateErrorResponse("SetupLightingPreset", e, parameters);
}
}
// ===== Professional Cinematic Lighting Presets =====
// Based on film industry 3-point lighting and cinematography techniques
private void SetupStudioLighting(Light light, float intensity)
{
// Professional 3-point studio lighting setup
// Key light - main illumination at 45 degrees
light.color = new Color(1f, 0.98f, 0.95f); // Slightly warm white (5600K daylight)
light.intensity = 1.2f * intensity;
light.transform.rotation = Quaternion.Euler(35f, -45f, 0f);
light.shadows = LightShadows.Soft;
light.shadowStrength = 0.6f;
light.shadowBias = 0.05f;
light.shadowNormalBias = 0.4f;
// Create fill light (softer, opposite side)
CreateOrUpdateLight("Studio_FillLight", LightType.Directional,
new Color(0.9f, 0.95f, 1f), 0.4f * intensity,
Quaternion.Euler(25f, 135f, 0f), false);
// Create rim/back light (edge definition)
CreateOrUpdateLight("Studio_RimLight", LightType.Directional,
new Color(1f, 1f, 1f), 0.6f * intensity,
Quaternion.Euler(10f, 180f, 0f), false);
// Ambient - neutral gray for controlled shadows
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.7f, 0.72f, 0.75f);
RenderSettings.ambientEquatorColor = new Color(0.5f, 0.5f, 0.52f);
RenderSettings.ambientGroundColor = new Color(0.3f, 0.3f, 0.32f);
RenderSettings.ambientIntensity = 0.8f * intensity;
RenderSettings.fog = false;
}
private void SetupSunsetLighting(Light light, float intensity)
{
// Golden hour/sunset lighting (warm, low angle, long shadows)
// Color temperature ~2500K
light.color = new Color(1f, 0.55f, 0.25f); // Deep orange-gold
light.intensity = 0.9f * intensity;
light.transform.rotation = Quaternion.Euler(8f, -160f, 0f); // Low sun angle
light.shadows = LightShadows.Soft;
light.shadowStrength = 0.7f;
// Sky fill light (opposite, cool blue from sky)
CreateOrUpdateLight("Sunset_SkyFill", LightType.Directional,
new Color(0.5f, 0.6f, 0.9f), 0.15f * intensity,
Quaternion.Euler(60f, 20f, 0f), false);
// Gradient ambient - warm sky to cool ground bounce
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(1f, 0.6f, 0.35f); // Warm sky
RenderSettings.ambientEquatorColor = new Color(0.9f, 0.5f, 0.4f); // Orange horizon
RenderSettings.ambientGroundColor = new Color(0.4f, 0.35f, 0.3f); // Warm shadow
RenderSettings.ambientIntensity = 0.7f * intensity;
// Atmospheric haze
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(1f, 0.6f, 0.4f);
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = 0.008f;
}
private void SetupNightLighting(Light light, float intensity)
{
// Moonlit night scene - cool blue tones, high contrast
// Moon light (cool blue-white)
light.color = new Color(0.6f, 0.7f, 0.9f); // Cool moonlight
light.intensity = 0.15f * intensity;
light.transform.rotation = Quaternion.Euler(45f, -60f, 0f);
light.shadows = LightShadows.Soft;
light.shadowStrength = 0.9f; // Deep shadows at night
// Subtle sky dome light
CreateOrUpdateLight("Night_SkyDome", LightType.Directional,
new Color(0.3f, 0.35f, 0.5f), 0.05f * intensity,
Quaternion.Euler(90f, 0f, 0f), false);
// Dark ambient with subtle blue
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.15f, 0.18f, 0.25f);
RenderSettings.ambientEquatorColor = new Color(0.08f, 0.1f, 0.15f);
RenderSettings.ambientGroundColor = new Color(0.02f, 0.02f, 0.03f);
RenderSettings.ambientIntensity = 0.3f * intensity;
// Night fog
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.05f, 0.06f, 0.1f);
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = 0.015f;
}
private void SetupOvercastLighting(Light light, float intensity)
{
// Cloudy day - soft, diffused, minimal shadows
light.color = new Color(0.85f, 0.88f, 0.92f); // Cool desaturated white
light.intensity = 0.7f * intensity;
light.transform.rotation = Quaternion.Euler(55f, -30f, 0f);
light.shadows = LightShadows.Soft;
light.shadowStrength = 0.2f; // Very soft shadows
// Even ambient (clouds diffuse light evenly)
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.65f, 0.68f, 0.72f);
RenderSettings.ambientEquatorColor = new Color(0.6f, 0.62f, 0.65f);
RenderSettings.ambientGroundColor = new Color(0.4f, 0.42f, 0.45f);
RenderSettings.ambientIntensity = 1.0f * intensity;
// Gray atmospheric fog
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.7f, 0.72f, 0.75f);
RenderSettings.fogMode = FogMode.Linear;
RenderSettings.fogStartDistance = 20f;
RenderSettings.fogEndDistance = 150f;
}
private void SetupDesertLighting(Light light, float intensity)
{
// Harsh desert sun - very bright, warm, strong shadows
light.color = new Color(1f, 0.95f, 0.85f); // Warm white
light.intensity = 1.5f * intensity;
light.transform.rotation = Quaternion.Euler(65f, -20f, 0f); // High sun
light.shadows = LightShadows.Hard; // Sharp shadows
light.shadowStrength = 0.95f;
// Hot ground bounce
CreateOrUpdateLight("Desert_GroundBounce", LightType.Directional,
new Color(1f, 0.9f, 0.7f), 0.3f * intensity,
Quaternion.Euler(-30f, 0f, 0f), false);
// Warm ambient from sand
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.9f, 0.88f, 0.8f);
RenderSettings.ambientEquatorColor = new Color(0.95f, 0.9f, 0.75f);
RenderSettings.ambientGroundColor = new Color(0.8f, 0.7f, 0.5f); // Sand color
RenderSettings.ambientIntensity = 1.1f * intensity;
// Heat haze effect
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.95f, 0.9f, 0.8f);
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = 0.003f;
}
private void SetupForestLighting(Light light, float intensity)
{
// Dappled forest light - filtered through canopy
light.color = new Color(0.95f, 1f, 0.85f); // Slightly green-tinted
light.intensity = 0.6f * intensity;
light.transform.rotation = Quaternion.Euler(70f, -45f, 0f); // High, filtered
light.shadows = LightShadows.Soft;
light.shadowStrength = 0.5f;
// Green ambient from foliage
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.5f, 0.65f, 0.45f); // Green canopy
RenderSettings.ambientEquatorColor = new Color(0.35f, 0.5f, 0.35f);
RenderSettings.ambientGroundColor = new Color(0.2f, 0.25f, 0.15f); // Forest floor
RenderSettings.ambientIntensity = 0.7f * intensity;
// Forest mist
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.5f, 0.6f, 0.5f);
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = 0.012f;
// Add god rays effect light
CreateOrUpdateLight("Forest_GodRays", LightType.Spot,
new Color(1f, 0.98f, 0.85f), 0.8f * intensity,
Quaternion.Euler(80f, 30f, 0f), false, 25f, 50f);
}
private void SetupUnderwaterLighting(Light light, float intensity)
{
// Underwater caustics - blue-green, filtered sunlight
light.color = new Color(0.3f, 0.7f, 0.8f); // Cyan-blue
light.intensity = 0.4f * intensity;
light.transform.rotation = Quaternion.Euler(75f, 0f, 0f); // Light from above
light.shadows = LightShadows.Soft;
light.shadowStrength = 0.3f;
// Deep blue ambient
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.2f, 0.5f, 0.6f);
RenderSettings.ambientEquatorColor = new Color(0.15f, 0.4f, 0.5f);
RenderSettings.ambientGroundColor = new Color(0.05f, 0.15f, 0.2f); // Deep ocean floor
RenderSettings.ambientIntensity = 0.9f * intensity;
// Heavy underwater fog
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.1f, 0.3f, 0.4f);
RenderSettings.fogMode = FogMode.Exponential;
RenderSettings.fogDensity = 0.08f;
}
private void SetupSpaceLighting(Light light, float intensity)
{
// Space - harsh sunlight, no atmosphere, pure black shadows
light.color = Color.white; // Pure sunlight in vacuum
light.intensity = 2.0f * intensity;
light.transform.rotation = Quaternion.Euler(30f, -60f, 0f);
light.shadows = LightShadows.Hard; // No atmosphere = hard shadows
light.shadowStrength = 1.0f; // Total shadow
// Minimal ambient (starlight only)
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.02f, 0.02f, 0.03f);
RenderSettings.ambientIntensity = 0.1f * intensity;
// No fog in space
RenderSettings.fog = false;
// Optional: Add rim light for "sci-fi" look
CreateOrUpdateLight("Space_RimLight", LightType.Directional,
new Color(0.5f, 0.6f, 1f), 0.2f * intensity,
Quaternion.Euler(0f, 180f, 0f), false);
}
private void SetupNeonLighting(Light light, float intensity)
{
// Cyberpunk neon city - colorful, high contrast
// Main light (purple/magenta)
light.color = new Color(0.9f, 0.2f, 0.8f); // Magenta
light.intensity = 0.5f * intensity;
light.transform.rotation = Quaternion.Euler(40f, -30f, 0f);
light.shadows = LightShadows.Soft;
light.shadowStrength = 0.8f;
// Cyan accent light (opposite)
CreateOrUpdateLight("Neon_CyanAccent", LightType.Directional,
new Color(0f, 0.9f, 1f), 0.3f * intensity,
Quaternion.Euler(30f, 150f, 0f), false);
// Pink rim light
CreateOrUpdateLight("Neon_PinkRim", LightType.Directional,
new Color(1f, 0.3f, 0.5f), 0.4f * intensity,
Quaternion.Euler(10f, 90f, 0f), false);
// Dark ambient with color
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.2f, 0.1f, 0.3f);
RenderSettings.ambientEquatorColor = new Color(0.15f, 0.05f, 0.2f);
RenderSettings.ambientGroundColor = new Color(0.05f, 0.02f, 0.1f);
RenderSettings.ambientIntensity = 0.4f * intensity;
// Atmospheric haze
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.1f, 0.02f, 0.15f);
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = 0.02f;
}
// Helper method to create or update additional lights
private void CreateOrUpdateLight(string name, LightType type, Color color, float intensity,
Quaternion rotation, bool shadows, float spotAngle = 30f, float range = 10f)
{
var lightObj = GameObject.Find(name);
if (lightObj == null)
{
lightObj = new GameObject(name);
}
var light = lightObj.GetComponent<Light>();
if (light == null)
{
light = lightObj.AddComponent<Light>();
}
light.type = type;
light.color = color;
light.intensity = intensity;
light.transform.rotation = rotation;
light.shadows = shadows ? LightShadows.Soft : LightShadows.None;
if (type == LightType.Spot)
{
light.spotAngle = spotAngle;
light.range = range;
}
}
// Additional cinematic presets
private void SetupCinematicLighting(Light light, float intensity, string style = "dramatic")
{
switch (style.ToLower())
{
case "dramatic":
// High contrast, single strong key light
light.color = new Color(1f, 0.95f, 0.9f);
light.intensity = 1.5f * intensity;
light.transform.rotation = Quaternion.Euler(25f, -70f, 0f);
light.shadows = LightShadows.Soft;
light.shadowStrength = 0.85f;
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.1f, 0.1f, 0.12f);
break;
case "romantic":
// Soft, warm, low contrast
light.color = new Color(1f, 0.85f, 0.7f);
light.intensity = 0.8f * intensity;
light.transform.rotation = Quaternion.Euler(30f, -45f, 0f);
light.shadows = LightShadows.Soft;
light.shadowStrength = 0.3f;
CreateOrUpdateLight("Romantic_Fill", LightType.Directional,
new Color(1f, 0.9f, 0.85f), 0.4f * intensity,
Quaternion.Euler(20f, 135f, 0f), false);
break;
case "horror":
// Harsh, underlit, strong shadows
light.color = new Color(0.7f, 0.8f, 0.9f);
light.intensity = 0.4f * intensity;
light.transform.rotation = Quaternion.Euler(-20f, 0f, 0f); // Light from below
light.shadows = LightShadows.Hard;
light.shadowStrength = 1.0f;
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.02f, 0.02f, 0.03f);
break;
case "noir":
// Black and white aesthetic, venetian blind shadows
light.color = Color.white;
light.intensity = 1.2f * intensity;
light.transform.rotation = Quaternion.Euler(15f, -80f, 0f);
light.shadows = LightShadows.Hard;
light.shadowStrength = 1.0f;
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.08f, 0.08f, 0.08f);
break;
}
}
private string CreateVisualEffect(Dictionary<string, string> parameters)
{
try
{
var effectName = parameters.GetValueOrDefault("effectName", "VisualEffect");
var effectType = parameters.GetValueOrDefault("effectType", "Aura");
var target = parameters.GetValueOrDefault("target", "");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "0"));
var loop = bool.Parse(parameters.GetValueOrDefault("loop", "true"));
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "1"));
GameObject targetObj = null;
if (!string.IsNullOrEmpty(target))
{
targetObj = GameObject.Find(target);
}
var effectGO = new GameObject(effectName);
if (targetObj != null)
{
effectGO.transform.SetParent(targetObj.transform);
effectGO.transform.localPosition = Vector3.zero;
}
// Settings by effect type
switch (effectType)
{
case "Aura":
CreateAuraEffect(effectGO, intensity);
break;
case "Trail":
CreateTrailEffect(effectGO, targetObj, intensity);
break;
case "Distortion":
CreateDistortionEffect(effectGO, intensity);
break;
case "Glow":
CreateGlowEffect(effectGO, intensity);
break;
case "Shield":
CreateShieldEffect(effectGO, intensity);
break;
case "Portal":
CreatePortalEffect(effectGO, intensity);
break;
case "Dissolve":
CreateDissolveEffect(effectGO, targetObj, intensity);
break;
case "Hologram":
CreateHologramEffect(effectGO, targetObj, intensity);
break;
}
// Duration settings
if (duration > 0 && !loop)
{
var destroyer = effectGO.AddComponent<TimedDestroyer>();
destroyer.lifetime = duration;
}
lastCreatedObject = effectGO;
Selection.activeGameObject = effectGO;
return $"[NexusVisual] Visual Effect '{effectName}' created successfully!\n" +
$" Type: {effectType}\n" +
$" Target: {(string.IsNullOrEmpty(target) ? "None" : target)}\n" +
$" Intensity: {intensity}\n" +
$" Loop: {loop}\n" +
$" Tip: Complex visual effects have been configured";
}
catch (Exception e)
{
return CreateErrorResponse("CreateVisualEffect", e, parameters);
}
}
private void CreateAuraEffect(GameObject effectGO, float intensity)
{
// Aura effect with particle system
var ps = effectGO.AddComponent<ParticleSystem>();
var main = ps.main;
main.startLifetime = 2f;
main.startSpeed = 0.5f;
main.startSize = 2f * intensity;
main.startColor = new Color(0.5f, 0.8f, 1f, 0.5f);
main.maxParticles = 50;
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Sphere;
shape.radius = 1.5f;
shape.radiusThickness = 0.5f;
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.space = ParticleSystemSimulationSpace.Local;
velocityOverLifetime.radial = new ParticleSystem.MinMaxCurve(0.5f);
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(new Color(0.5f, 0.8f, 1f), 1f) },
new GradientAlphaKey[] { new GradientAlphaKey(0f, 0f), new GradientAlphaKey(0.5f, 0.5f), new GradientAlphaKey(0f, 1f) }
);
colorOverLifetime.color = gradient;
// Add light
var lightGO = new GameObject("AuraLight");
lightGO.transform.SetParent(effectGO.transform);
var light = lightGO.AddComponent<Light>();
light.type = LightType.Point;
light.color = new Color(0.5f, 0.8f, 1f);
light.intensity = 2f * intensity;
light.range = 5f;
}
private void CreateTrailEffect(GameObject effectGO, GameObject targetObj, float intensity)
{
var trail = effectGO.AddComponent<TrailRenderer>();
trail.time = 1f;
trail.startWidth = 0.5f * intensity;
trail.endWidth = 0f;
// Gradient settings
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.cyan, 1f) },
new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0f, 1f) }
);
trail.colorGradient = gradient;
// Material settings
trail.material = new Material(Shader.Find("Sprites/Default"));
trail.material.SetColor("_Color", Color.cyan);
}
private void CreateDistortionEffect(GameObject effectGO, float intensity)
{
// Particles for distortion effect
var ps = effectGO.AddComponent<ParticleSystem>();
var main = ps.main;
main.startLifetime = 1f;
main.startSpeed = 0f;
main.startSize = 3f * intensity;
main.maxParticles = 1;
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Sphere;
shape.radius = 0.1f;
// Distortion Material required (normally uses special shader)
var renderer = ps.GetComponent<ParticleSystemRenderer>();
renderer.material = new Material(Shader.Find("Sprites/Default"));
renderer.material.color = new Color(1f, 1f, 1f, 0.2f);
}
private void CreateGlowEffect(GameObject effectGO, float intensity)
{
// Add emission to mesh renderer
var sphere = CreatePrimitiveWithMaterial(PrimitiveType.Sphere);
sphere.transform.SetParent(effectGO.transform);
sphere.transform.localScale = Vector3.one * 2f;
var renderer = sphere.GetComponent<Renderer>();
var material = CreateRenderPipelineCompatibleMaterial();
material.EnableKeyword("_EMISSION");
material.SetColor("_EmissionColor", Color.white * (2f * intensity));
material.SetFloat("_Smoothness", 1f);
renderer.material = material;
// Add point light
var light = effectGO.AddComponent<Light>();
light.type = LightType.Point;
light.color = Color.white;
light.intensity = 3f * intensity;
light.range = 10f;
}
private void CreateShieldEffect(GameObject effectGO, float intensity)
{
// Translucent sphere
var sphere = CreatePrimitiveWithMaterial(PrimitiveType.Sphere);
sphere.transform.SetParent(effectGO.transform);
sphere.transform.localScale = Vector3.one * 3f;
var renderer = sphere.GetComponent<Renderer>();
var material = CreateRenderPipelineCompatibleMaterial();
material.SetFloat("_Mode", 3); // Transparent
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
material.SetColor("_Color", new Color(0.5f, 0.8f, 1f, 0.3f));
material.EnableKeyword("_EMISSION");
material.SetColor("_EmissionColor", new Color(0.5f, 0.8f, 1f) * intensity);
renderer.material = material;
// Energy wave particles
var ps = effectGO.AddComponent<ParticleSystem>();
var main = ps.main;
main.startLifetime = 1f;
main.startSpeed = 0f;
main.startSize = 0.5f;
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Sphere;
shape.radius = 1.5f;
shape.radiusThickness = 1f; // Edge emission (replaces SphereShell)
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.radial = new ParticleSystem.MinMaxCurve(-1f);
}
private void CreatePortalEffect(GameObject effectGO, float intensity)
{
// Portal ring
var torus = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder);
torus.transform.SetParent(effectGO.transform);
torus.transform.localScale = new Vector3(3f, 0.1f, 3f);
var renderer = torus.GetComponent<Renderer>();
var material = CreateRenderPipelineCompatibleMaterial();
material.EnableKeyword("_EMISSION");
material.SetColor("_EmissionColor", new Color(1f, 0f, 1f) * (2f * intensity));
renderer.material = material;
// Swirl particles
var ps = effectGO.AddComponent<ParticleSystem>();
var main = ps.main;
main.startLifetime = 3f;
main.startSpeed = 1f;
main.startSize = 0.3f;
main.startColor = new Color(1f, 0f, 1f);
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Circle;
shape.radius = 1.5f;
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.orbitalY = new ParticleSystem.MinMaxCurve(5f);
velocityOverLifetime.radial = new ParticleSystem.MinMaxCurve(-2f);
}
private void CreateDissolveEffect(GameObject effectGO, GameObject targetObj, float intensity)
{
if (targetObj != null)
{
var renderer = targetObj.GetComponent<Renderer>();
if (renderer != null)
{
// Material settings for dissolve effect
var material = new Material(renderer.sharedMaterial);
material.SetFloat("_Mode", 2); // Fade
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 1);
material.EnableKeyword("_ALPHABLEND_ON");
material.renderQueue = 3000;
// Add dissolve controller
var dissolve = effectGO.AddComponent<DissolveController>();
dissolve.targetRenderer = renderer;
dissolve.dissolveMaterial = material;
dissolve.dissolveSpeed = intensity;
}
}
// Edge glow particles
var ps = effectGO.AddComponent<ParticleSystem>();
var main = ps.main;
main.startLifetime = 0.5f;
main.startSpeed = 2f;
main.startSize = 0.1f;
main.startColor = new Color(1f, 0.5f, 0f);
var emission = ps.emission;
emission.rateOverTime = 100f;
}
private void CreateHologramEffect(GameObject effectGO, GameObject targetObj, float intensity)
{
if (targetObj != null)
{
// Apply hologram Material
var renderer = targetObj.GetComponent<Renderer>();
if (renderer != null)
{
var material = CreateRenderPipelineCompatibleMaterial();
material.SetFloat("_Mode", 3); // Transparent
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_ZWrite", 0);
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
material.EnableKeyword("_EMISSION");
material.SetColor("_Color", new Color(0f, 1f, 1f, 0.5f));
material.SetColor("_EmissionColor", new Color(0f, 1f, 1f) * (2f * intensity));
renderer.material = material;
}
}
// Scan line effect
var scanLine = new GameObject("ScanLine");
scanLine.transform.SetParent(effectGO.transform);
var scanLineController = scanLine.AddComponent<ScanLineController>();
scanLineController.speed = 2f * intensity;
}
private string SetupReflectionProbe(Dictionary<string, string> parameters)
{
try
{
var probeName = parameters.GetValueOrDefault("probeName", "ReflectionProbe");
var position = ParseVector3(parameters.GetValueOrDefault("position", "0,0,0"));
var size = ParseVector3(parameters.GetValueOrDefault("size", "10,10,10"));
var resolution = int.Parse(parameters.GetValueOrDefault("resolution", "128"));
var updateMode = parameters.GetValueOrDefault("updateMode", "OnAwake");
var importance = int.Parse(parameters.GetValueOrDefault("importance", "1"));
var probeGO = new GameObject(probeName);
probeGO.transform.position = position;
var probe = probeGO.AddComponent<ReflectionProbe>();
probe.size = size;
probe.resolution = resolution;
probe.importance = importance;
switch (updateMode)
{
case "OnAwake":
probe.refreshMode = UnityEngine.Rendering.ReflectionProbeRefreshMode.OnAwake;
break;
case "EveryFrame":
probe.refreshMode = UnityEngine.Rendering.ReflectionProbeRefreshMode.EveryFrame;
break;
case "ViaScripting":
probe.refreshMode = UnityEngine.Rendering.ReflectionProbeRefreshMode.ViaScripting;
break;
}
// Bake the probe
probe.RenderProbe();
lastCreatedObject = probeGO;
Selection.activeGameObject = probeGO;
return $"[NexusVisual] Reflection Probe '{probeName}' created successfully!\n" +
$" Position: {position}\n" +
$" Size: {size}\n" +
$" Resolution: {resolution}\n" +
$" Update Mode: {updateMode}\n" +
$" Tip: Enables realistic reflections of the surrounding environment";
}
catch (Exception e)
{
return CreateErrorResponse("SetupReflectionProbe", e, parameters);
}
}
private string CreateLightProbeGroup(Dictionary<string, string> parameters)
{
try
{
var groupName = parameters.GetValueOrDefault("groupName", "LightProbeGroup");
var gridSize = ParseVector3(parameters.GetValueOrDefault("gridSize", "5,3,5"));
var spacing = float.Parse(parameters.GetValueOrDefault("spacing", "2"));
var center = ParseVector3(parameters.GetValueOrDefault("center", "0,0,0"));
var probeGroupGO = new GameObject(groupName);
probeGroupGO.transform.position = center;
var probeGroup = probeGroupGO.AddComponent<LightProbeGroup>();
// Place probes in grid pattern
List<Vector3> probePositions = new List<Vector3>();
for (int x = 0; x < gridSize.x; x++)
{
for (int y = 0; y < gridSize.y; y++)
{
for (int z = 0; z < gridSize.z; z++)
{
Vector3 position = new Vector3(
(x - gridSize.x / 2) * spacing,
y * spacing,
(z - gridSize.z / 2) * spacing
);
probePositions.Add(position);
}
}
}
probeGroup.probePositions = probePositions.ToArray();
lastCreatedObject = probeGroupGO;
Selection.activeGameObject = probeGroupGO;
return $"[NexusVisual] Light Probe Group '{groupName}' created successfully!\n" +
$" Grid Size: {gridSize}\n" +
$" Spacing: {spacing}m\n" +
$" Probe Count: {probePositions.Count}\n" +
$" Tip: Apply indirect lighting to dynamic objects";
}
catch (Exception e)
{
return CreateErrorResponse("CreateLightProbeGroup", e, parameters);
}
}
private string SetupVolumetricFog(Dictionary<string, string> parameters)
{
try
{
var fogName = parameters.GetValueOrDefault("fogName", "VolumetricFog");
var density = float.Parse(parameters.GetValueOrDefault("density", "0.05"));
var color = parameters.GetValueOrDefault("color", "#FFFFFF");
var height = float.Parse(parameters.GetValueOrDefault("height", "100"));
// Unity standard fog settings
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = density;
if (ColorUtility.TryParseHtmlString(color, out Color fogColor))
{
RenderSettings.fogColor = fogColor;
}
// Volumetric fog effect (simplified implementation with particles)
var fogGO = new GameObject(fogName);
var ps = fogGO.AddComponent<ParticleSystem>();
var main = ps.main;
main.startLifetime = 10f;
main.startSpeed = 0.5f;
main.startSize = 5f;
main.startColor = new Color(fogColor.r, fogColor.g, fogColor.b, 0.05f);
main.maxParticles = 500;
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(50f, height, 50f);
var emission = ps.emission;
emission.rateOverTime = 50f;
var velocityOverLifetime = ps.velocityOverLifetime;
velocityOverLifetime.enabled = true;
velocityOverLifetime.space = ParticleSystemSimulationSpace.World;
// Set all axes to the same mode (Random Between Two Constants) to avoid curve mode conflicts
velocityOverLifetime.x = new ParticleSystem.MinMaxCurve(-1f, 1f);
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(0f, 0f);
velocityOverLifetime.z = new ParticleSystem.MinMaxCurve(-1f, 1f);
// Assign compatible material to prevent pink materials
AssignWeatherMaterial(ps, "fog");
lastCreatedObject = fogGO;
Selection.activeGameObject = fogGO;
return $"[NexusVisual] Fog Effect '{fogName}' configured successfully!\n" +
$" Density: {density}\n" +
$" Color: {color}\n" +
$" Height: {height}m\n" +
$" Implementation: RenderSettings fog + Particle system\n" +
$" Note: This is a simplified fog effect, not true volumetric fog\n" +
$" Tip: For true volumetric fog, use URP/HDRP or custom shaders";
}
catch (Exception e)
{
return CreateErrorResponse("SetupVolumetricFog", e, parameters);
}
}
private string CreateDecal(Dictionary<string, string> parameters)
{
try
{
var decalName = parameters.GetValueOrDefault("decalName", "Decal");
var size = ParseVector3(parameters.GetValueOrDefault("size", "1,1,1"));
var opacity = float.Parse(parameters.GetValueOrDefault("opacity", "1"));
// Quad for Decal projection
var decalGO = CreatePrimitiveWithMaterial(PrimitiveType.Quad);
decalGO.name = decalName;
decalGO.transform.localScale = new Vector3(size.x, size.y, 1f);
decalGO.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
var renderer = decalGO.GetComponent<Renderer>();
var material = CreateRenderPipelineCompatibleMaterial();
material.SetFloat("_Mode", 3); // Transparent
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3001;
material.SetColor("_Color", new Color(1f, 1f, 1f, opacity));
renderer.material = material;
// Remove collider (Decal is not a physical object)
var collider = decalGO.GetComponent<Collider>();
if (collider != null)
{
GameObject.Destroy(collider);
}
lastCreatedObject = decalGO;
Selection.activeGameObject = decalGO;
return $"[NexusVisual] Decal '{decalName}' created successfully!\n" +
$" Size: {size}\n" +
$" Opacity: {opacity}\n" +
$" Tip: Projects textures onto surfaces (set texture manually)";
}
catch (Exception e)
{
return CreateErrorResponse("CreateDecal", e, parameters);
}
}
/// <summary>
/// Create caustics projector for underwater light effects
/// </summary>
private string CreateCausticsProjector(Dictionary<string, string> parameters)
{
try
{
var name = parameters.GetValueOrDefault("name", "CausticsProjector");
var size = ParseVector3(parameters.GetValueOrDefault("size", "10,1,10"));
var position = parameters.TryGetValue("position", out var posStr)
? ParseVector3(posStr)
: new Vector3(0, 5, 0);
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "1.0"));
var speed = float.Parse(parameters.GetValueOrDefault("speed", "1.0"));
var colorStr = parameters.GetValueOrDefault("color", "0.8,0.95,1,0.5");
var scale = float.Parse(parameters.GetValueOrDefault("scale", "0.3"));
// Create caustics plane
var causticsGO = CreatePrimitiveWithMaterial(PrimitiveType.Quad);
causticsGO.name = name;
causticsGO.transform.position = position;
causticsGO.transform.rotation = Quaternion.Euler(90f, 0f, 0f); // Face down
causticsGO.transform.localScale = new Vector3(size.x, size.z, 1f);
// Remove collider
var collider = causticsGO.GetComponent<Collider>();
if (collider != null)
{
GameObject.DestroyImmediate(collider);
}
// Find or create caustics shader (pipeline-aware)
var shader = ShaderPipelineManager.FindShaderForCurrentPipeline("Caustics");
if (shader == null)
{
// Fallback to additive shader
shader = Shader.Find("Particles/Standard Unlit");
if (shader == null)
{
shader = GetRenderPipelineCompatibleShader();
}
}
var material = new Material(shader);
material.name = "CausticsMaterial";
// Parse color
var colorParts = colorStr.Split(',');
var color = new Color(
colorParts.Length > 0 ? float.Parse(colorParts[0]) : 0.8f,
colorParts.Length > 1 ? float.Parse(colorParts[1]) : 0.95f,
colorParts.Length > 2 ? float.Parse(colorParts[2]) : 1f,
colorParts.Length > 3 ? float.Parse(colorParts[3]) : 0.5f
);
// Apply material settings
if (material.HasProperty("_CausticsColor"))
{
material.SetColor("_CausticsColor", color);
material.SetFloat("_CausticsIntensity", intensity);
material.SetVector("_Speed1", new Vector4(0.05f * speed, 0.03f * speed, 0, 0));
material.SetVector("_Speed2", new Vector4(-0.04f * speed, 0.06f * speed, 0, 0));
material.SetFloat("_Scale1", scale);
material.SetFloat("_Scale2", scale * 0.85f);
// Try to load caustics texture
var texturePath = "Assets/Synaptic AI Pro/Shaders/Caustics/CausticsTexture.png";
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(texturePath);
if (texture != null)
{
material.SetTexture("_CausticsTex", texture);
}
else
{
// Generate texture if not exists
SynLog.Info("[Synaptic] Caustics texture not found, please run Tools > Synaptic Pro > Shaders > Generate Caustics Texture");
}
}
else
{
// Fallback settings for standard shader
material.SetColor("_Color", color);
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_ZWrite", 0);
material.renderQueue = 3100;
}
var renderer = causticsGO.GetComponent<Renderer>();
renderer.material = material;
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
renderer.receiveShadows = false;
lastCreatedObject = causticsGO;
Selection.activeGameObject = causticsGO;
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Caustics projector '{name}' created",
gameObject = name,
position = new { x = position.x, y = position.y, z = position.z },
size = new { x = size.x, y = size.y, z = size.z },
intensity = intensity,
tip = "Run Tools > Synaptic Pro > Shaders > Generate Caustics Texture if texture is missing"
});
}
catch (Exception e)
{
return CreateErrorResponse("CreateCausticsProjector", e, parameters);
}
}
/// <summary>
/// Create complete water system with waves, physics, and visual effects
/// </summary>
private string CreateWaterSystem(Dictionary<string, string> parameters)
{
try
{
var name = parameters.GetValueOrDefault("name", "Water");
var waterType = parameters.GetValueOrDefault("type", "ocean").ToLower(); // ocean, pool, river
var size = ParseVector3(parameters.GetValueOrDefault("size", "50,1,50"));
var position = parameters.TryGetValue("position", out var posStr)
? ParseVector3(posStr)
: Vector3.zero;
var enablePhysics = bool.Parse(parameters.GetValueOrDefault("physics", "true"));
var enableCaustics = bool.Parse(parameters.GetValueOrDefault("caustics", "true"));
var subdivisions = int.Parse(parameters.GetValueOrDefault("subdivisions", "32"));
// Create water plane with subdivisions for better wave visuals
var waterGO = CreateSubdividedPlane(name, subdivisions, size.x, size.z);
waterGO.transform.position = position;
// Find or create water shader (pipeline-aware)
var shader = ShaderPipelineManager.FindShaderForCurrentPipeline("Water");
if (shader == null)
{
SynLog.Warn("[Synaptic] Water shader not found, using fallback");
shader = GetRenderPipelineCompatibleShader();
}
var material = new Material(shader);
material.name = $"{name}Material";
// Apply preset based on water type
ApplyWaterPreset(material, waterType);
// Try to load normal map
var normalMapPath = "Assets/Synaptic AI Pro/Shaders/Water/WaterNormalMap.png";
var normalMap = AssetDatabase.LoadAssetAtPath<Texture2D>(normalMapPath);
if (normalMap != null && material.HasProperty("_NormalMap"))
{
material.SetTexture("_NormalMap", normalMap);
}
else
{
SynLog.Info("[Synaptic] Water normal map not found. Run Tools > Synaptic Pro > Water > Generate Water Normal Map");
}
var renderer = waterGO.GetComponent<Renderer>();
renderer.material = material;
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
// Add WaterSurface component for physics
var waterSurface = waterGO.AddComponent<Synaptic.WaterSurface>();
ConfigureWaterSurface(waterSurface, waterType);
// Add trigger collider for water interaction
if (enablePhysics)
{
var collider = waterGO.AddComponent<BoxCollider>();
collider.isTrigger = true;
collider.size = new Vector3(1, 0.5f, 1);
collider.center = new Vector3(0, -0.25f, 0);
waterGO.AddComponent<Synaptic.WaterInteraction>();
}
// Create caustics projector under water
GameObject causticsGO = null;
if (enableCaustics)
{
var causticsParams = new Dictionary<string, string>
{
{ "name", $"{name}_Caustics" },
{ "size", $"{size.x * 0.8f},1,{size.z * 0.8f}" },
{ "position", $"{position.x},{position.y - 0.1f},{position.z}" },
{ "intensity", waterType == "pool" ? "1.5" : "0.8" },
{ "scale", waterType == "pool" ? "0.4" : "0.25" }
};
CreateCausticsProjector(causticsParams);
causticsGO = GameObject.Find($"{name}_Caustics");
if (causticsGO != null)
{
causticsGO.transform.parent = waterGO.transform;
}
}
lastCreatedObject = waterGO;
Selection.activeGameObject = waterGO;
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Water system '{name}' created",
gameObject = name,
type = waterType,
size = new { x = size.x, y = size.y, z = size.z },
features = new
{
gerstnerWaves = true,
normalMapping = normalMap != null,
depthBasedColor = true,
fresnel = true,
foam = true,
physics = enablePhysics,
caustics = enableCaustics && causticsGO != null
},
tips = new[]
{
"Run Tools > Synaptic Pro > Water > Generate Water Normal Map for better visuals",
"Add Buoyancy component to objects that should float",
"Adjust wave settings on WaterSurface component"
}
});
}
catch (Exception e)
{
return CreateErrorResponse("CreateWaterSystem", e, parameters);
}
}
private GameObject CreateSubdividedPlane(string name, int subdivisions, float width, float height)
{
var go = new GameObject(name);
var meshFilter = go.AddComponent<MeshFilter>();
var meshRenderer = go.AddComponent<MeshRenderer>();
var mesh = new Mesh();
mesh.name = $"{name}Mesh";
int vertexCount = (subdivisions + 1) * (subdivisions + 1);
var vertices = new Vector3[vertexCount];
var normals = new Vector3[vertexCount];
var uvs = new Vector2[vertexCount];
float stepX = width / subdivisions;
float stepZ = height / subdivisions;
float halfWidth = width / 2f;
float halfHeight = height / 2f;
for (int z = 0; z <= subdivisions; z++)
{
for (int x = 0; x <= subdivisions; x++)
{
int index = z * (subdivisions + 1) + x;
vertices[index] = new Vector3(x * stepX - halfWidth, 0, z * stepZ - halfHeight);
normals[index] = Vector3.up;
uvs[index] = new Vector2((float)x / subdivisions, (float)z / subdivisions);
}
}
int[] triangles = new int[subdivisions * subdivisions * 6];
int triIndex = 0;
for (int z = 0; z < subdivisions; z++)
{
for (int x = 0; x < subdivisions; x++)
{
int bottomLeft = z * (subdivisions + 1) + x;
int bottomRight = bottomLeft + 1;
int topLeft = bottomLeft + subdivisions + 1;
int topRight = topLeft + 1;
triangles[triIndex++] = bottomLeft;
triangles[triIndex++] = topLeft;
triangles[triIndex++] = topRight;
triangles[triIndex++] = bottomLeft;
triangles[triIndex++] = topRight;
triangles[triIndex++] = bottomRight;
}
}
mesh.vertices = vertices;
mesh.normals = normals;
mesh.uv = uvs;
mesh.triangles = triangles;
mesh.RecalculateBounds();
mesh.RecalculateTangents();
meshFilter.mesh = mesh;
return go;
}
private void ApplyWaterPreset(Material material, string waterType)
{
switch (waterType)
{
case "pool":
material.SetColor("_ShallowColor", new Color(0.4f, 0.85f, 0.95f, 0.4f));
material.SetColor("_DeepColor", new Color(0.1f, 0.5f, 0.7f, 0.8f));
material.SetFloat("_DepthMaxDistance", 3f);
material.SetFloat("_WaveSpeed", 0.3f);
material.SetFloat("_WaveStrength", 0.02f);
material.SetFloat("_WaveFrequency", 2f);
material.SetFloat("_NormalStrength", 0.3f);
material.SetFloat("_NormalSpeed", 0.2f);
material.SetFloat("_ReflectionStrength", 0.6f);
material.SetFloat("_Smoothness", 0.98f);
break;
case "river":
material.SetColor("_ShallowColor", new Color(0.3f, 0.6f, 0.5f, 0.5f));
material.SetColor("_DeepColor", new Color(0.1f, 0.3f, 0.25f, 0.9f));
material.SetFloat("_DepthMaxDistance", 4f);
material.SetFloat("_WaveSpeed", 1.5f);
material.SetFloat("_WaveStrength", 0.08f);
material.SetFloat("_WaveFrequency", 3f);
material.SetVector("_WaveDirectionA", new Vector4(1, 0, 0, 0));
material.SetFloat("_NormalStrength", 1.2f);
material.SetFloat("_NormalSpeed", 0.8f);
material.SetFloat("_FoamDistance", 0.8f);
material.SetFloat("_FoamCutoff", 0.4f);
break;
case "ocean":
default:
material.SetColor("_ShallowColor", new Color(0.2f, 0.7f, 0.8f, 0.6f));
material.SetColor("_DeepColor", new Color(0.05f, 0.2f, 0.4f, 0.95f));
material.SetFloat("_DepthMaxDistance", 8f);
material.SetFloat("_WaveSpeed", 0.8f);
material.SetFloat("_WaveStrength", 0.15f);
material.SetFloat("_WaveFrequency", 1.5f);
material.SetFloat("_NormalStrength", 0.8f);
material.SetFloat("_NormalSpeed", 0.3f);
material.SetFloat("_ReflectionStrength", 0.4f);
material.SetFloat("_Smoothness", 0.95f);
break;
}
}
private void ConfigureWaterSurface(Synaptic.WaterSurface waterSurface, string waterType)
{
switch (waterType)
{
case "pool":
waterSurface.waveSpeed = 0.3f;
waterSurface.waveStrength = 0.02f;
waterSurface.waveFrequency = 2f;
break;
case "river":
waterSurface.waveSpeed = 1.5f;
waterSurface.waveStrength = 0.08f;
waterSurface.waveFrequency = 3f;
waterSurface.waveDirectionA = new Vector2(1, 0);
break;
case "ocean":
default:
waterSurface.waveSpeed = 0.8f;
waterSurface.waveStrength = 0.15f;
waterSurface.waveFrequency = 1.5f;
break;
}
}
private string SetupColorGrading(Dictionary<string, string> parameters)
{
try
{
var preset = parameters.GetValueOrDefault("preset", "Cinematic");
var temperature = float.Parse(parameters.GetValueOrDefault("temperature", "0"));
var tint = float.Parse(parameters.GetValueOrDefault("tint", "0"));
var contrast = float.Parse(parameters.GetValueOrDefault("contrast", "0"));
var brightness = float.Parse(parameters.GetValueOrDefault("brightness", "0"));
var saturation = float.Parse(parameters.GetValueOrDefault("saturation", "0"));
// GameObject with color grading effect
var colorGradingGO = new GameObject("ColorGrading");
var controller = colorGradingGO.AddComponent<ColorGradingController>();
// Configure by preset
switch (preset)
{
case "Cinematic":
controller.temperature = 10f;
controller.tint = -5f;
controller.contrast = 10f;
controller.saturation = -10f;
break;
case "Vintage":
controller.temperature = 20f;
controller.tint = 10f;
controller.contrast = -10f;
controller.saturation = -30f;
controller.brightness = 10f;
break;
case "BlackWhite":
controller.saturation = -100f;
controller.contrast = 20f;
break;
case "Sepia":
controller.temperature = 40f;
controller.tint = 20f;
controller.saturation = -50f;
break;
case "Cold":
controller.temperature = -30f;
controller.tint = -10f;
break;
case "Warm":
controller.temperature = 30f;
controller.tint = 10f;
break;
case "Horror":
controller.temperature = -20f;
controller.tint = 20f;
controller.contrast = 30f;
controller.brightness = -20f;
break;
case "Cyberpunk":
controller.temperature = -10f;
controller.tint = 30f;
controller.contrast = 20f;
controller.saturation = 20f;
break;
}
// Add custom adjustments
controller.temperature += temperature;
controller.tint += tint;
controller.contrast += contrast;
controller.brightness += brightness;
controller.saturation += saturation;
lastCreatedObject = colorGradingGO;
Selection.activeGameObject = colorGradingGO;
return $"[NexusVisual] Color Grading '{preset}' applied successfully!\n" +
$" Temperature: {controller.temperature}\n" +
$" Tint: {controller.tint}\n" +
$" Contrast: {controller.contrast}\n" +
$" Brightness: {controller.brightness}\n" +
$" Saturation: {controller.saturation}\n" +
$" Tip: Cinematic color adjustments have been applied";
}
catch (Exception e)
{
return CreateErrorResponse("SetupColorGrading", e, parameters);
}
}
private string CreateLensFlare(Dictionary<string, string> parameters)
{
try
{
var flareName = parameters.GetValueOrDefault("flareName", "LensFlare");
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "1"));
var fadeSpeed = float.Parse(parameters.GetValueOrDefault("fadeSpeed", "3"));
var color = parameters.GetValueOrDefault("color", "#FFFFFF");
// Find or create light source
Light targetLight = null;
var lights = GameObject.FindObjectsOfType<Light>();
if (lights.Length > 0)
{
targetLight = lights[0];
}
else
{
var lightGO = new GameObject("Light for Flare");
targetLight = lightGO.AddComponent<Light>();
targetLight.type = LightType.Directional;
}
var lensFlare = targetLight.gameObject.AddComponent<LensFlare>();
lensFlare.brightness = intensity;
lensFlare.fadeSpeed = fadeSpeed;
if (ColorUtility.TryParseHtmlString(color, out Color flareColor))
{
lensFlare.color = flareColor;
}
// Set basic flare texture (requires dedicated asset)
// lensFlare.flare = Resources.Load<Flare>("DefaultFlare");
return $"[NexusVisual] Lens Flare '{flareName}' created successfully!\n" +
$" Intensity: {intensity}\n" +
$" Fade Speed: {fadeSpeed}\n" +
$" Color: {color}\n" +
$" Tip: Camera effects for sun and bright light sources have been added\n" +
$" Note: Please set flare assets manually";
}
catch (Exception e)
{
return CreateErrorResponse("CreateLensFlare", e, parameters);
}
}
// Helper class
[System.Serializable]
public class TimedDestroyer : MonoBehaviour
{
public float lifetime = 5f;
void Start()
{
Destroy(gameObject, lifetime);
}
}
[System.Serializable]
public class DissolveController : MonoBehaviour
{
public Renderer targetRenderer;
public Material dissolveMaterial;
public float dissolveSpeed = 1f;
private float dissolveAmount = 0f;
void Update()
{
if (dissolveMaterial != null)
{
dissolveAmount += Time.deltaTime * dissolveSpeed;
dissolveMaterial.SetFloat("_Cutoff", dissolveAmount);
if (dissolveAmount >= 1f)
{
Destroy(gameObject);
}
}
}
}
[System.Serializable]
public class ScanLineController : MonoBehaviour
{
public float speed = 2f;
private float offset = 0f;
void Update()
{
offset += Time.deltaTime * speed;
if (offset > 1f) offset -= 1f;
// Implementation of scan line effect (requires shader)
}
}
[System.Serializable]
public class ColorGradingController : MonoBehaviour
{
public float temperature = 0f;
public float tint = 0f;
public float contrast = 0f;
public float brightness = 0f;
public float saturation = 0f;
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
// Simplified implementation of post-process effects
// Actually requires custom shader
Graphics.Blit(source, destination);
}
}
// ===== Screen Effects Implementation =====
private string CreateScreenShake(Dictionary<string, string> parameters)
{
try
{
var duration = float.Parse(parameters.GetValueOrDefault("duration", "0.5"));
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "1"));
var frequency = float.Parse(parameters.GetValueOrDefault("frequency", "10"));
var damping = float.Parse(parameters.GetValueOrDefault("damping", "1"));
var targetCamera = parameters.GetValueOrDefault("camera", "Main Camera");
Camera camera = null;
if (targetCamera == "Main Camera")
{
camera = Camera.main;
}
else
{
var cameraGO = GameObject.Find(targetCamera);
if (cameraGO != null) camera = cameraGO.GetComponent<Camera>();
}
if (camera == null)
{
return $"Camera '{targetCamera}' not found";
}
var shakeController = camera.gameObject.GetComponent<ScreenShakeController>();
if (shakeController == null)
{
shakeController = camera.gameObject.AddComponent<ScreenShakeController>();
}
shakeController.StartShake(duration, intensity, frequency, damping);
return $"[NexusVisual] Screen Shake started on '{camera.name}'!\n" +
$" Duration: {duration}s\n" +
$" Intensity: {intensity}\n" +
$" Frequency: {frequency}Hz\n" +
$" Damping: {damping}\n" +
$" Tip: Creates dramatic impact for explosions or earthquakes";
}
catch (Exception e)
{
return CreateErrorResponse("CreateScreenShake", e, parameters);
}
}
private string CreateScreenFade(Dictionary<string, string> parameters)
{
try
{
var fadeType = parameters.GetValueOrDefault("fadeType", "FadeIn");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "1"));
var color = parameters.GetValueOrDefault("color", "#000000");
var delay = float.Parse(parameters.GetValueOrDefault("delay", "0"));
var curve = parameters.GetValueOrDefault("curve", "Linear");
// Canvas for fade overlay
var fadeCanvas = GameObject.Find("FadeCanvas");
if (fadeCanvas == null)
{
fadeCanvas = new GameObject("FadeCanvas");
var canvas = fadeCanvas.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 9999;
var canvasScaler = fadeCanvas.AddComponent<UnityEngine.UI.CanvasScaler>();
canvasScaler.uiScaleMode = UnityEngine.UI.CanvasScaler.ScaleMode.ScaleWithScreenSize;
fadeCanvas.AddComponent<UnityEngine.UI.GraphicRaycaster>();
}
// Fade image
var fadeImage = new GameObject("FadeImage");
fadeImage.transform.SetParent(fadeCanvas.transform, false);
var rectTransform = fadeImage.AddComponent<RectTransform>();
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.offsetMin = Vector2.zero;
rectTransform.offsetMax = Vector2.zero;
var image = fadeImage.AddComponent<UnityEngine.UI.Image>();
if (ColorUtility.TryParseHtmlString(color, out Color fadeColor))
{
image.color = fadeColor;
}
var fadeController = fadeImage.AddComponent<ScreenFadeController>();
fadeController.StartFade(fadeType, duration, delay, curve);
return $"[NexusVisual] Screen Fade '{fadeType}' started!\n" +
$" Duration: {duration}s\n" +
$" Delay: {delay}s\n" +
$" Color: {color}\n" +
$" Curve: {curve}\n" +
$" Tip: Perfect for scene transitions and dramatic reveals";
}
catch (Exception e)
{
return CreateErrorResponse("CreateScreenFade", e, parameters);
}
}
private string CreateVignetteEffect(Dictionary<string, string> parameters)
{
try
{
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "0.5"));
var smoothness = float.Parse(parameters.GetValueOrDefault("smoothness", "0.5"));
var color = parameters.GetValueOrDefault("color", "#000000");
var rounded = bool.Parse(parameters.GetValueOrDefault("rounded", "true"));
// Create vignette canvas
var vignetteCanvas = GameObject.Find("VignetteCanvas");
if (vignetteCanvas == null)
{
vignetteCanvas = new GameObject("VignetteCanvas");
var canvas = vignetteCanvas.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 9998;
var canvasScaler = vignetteCanvas.AddComponent<UnityEngine.UI.CanvasScaler>();
canvasScaler.uiScaleMode = UnityEngine.UI.CanvasScaler.ScaleMode.ScaleWithScreenSize;
}
// Create vignette image
var vignetteImage = new GameObject("VignetteEffect");
vignetteImage.transform.SetParent(vignetteCanvas.transform, false);
var rectTransform = vignetteImage.AddComponent<RectTransform>();
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.offsetMin = Vector2.zero;
rectTransform.offsetMax = Vector2.zero;
var image = vignetteImage.AddComponent<UnityEngine.UI.Image>();
var vignetteController = vignetteImage.AddComponent<VignetteController>();
vignetteController.intensity = intensity;
vignetteController.smoothness = smoothness;
vignetteController.rounded = rounded;
if (ColorUtility.TryParseHtmlString(color, out Color vignetteColor))
{
vignetteController.vignetteColor = vignetteColor;
}
lastCreatedObject = vignetteImage;
Selection.activeGameObject = vignetteImage;
return $"[NexusVisual] Vignette Effect created successfully!\n" +
$" Intensity: {intensity}\n" +
$" Smoothness: {smoothness}\n" +
$" Color: {color}\n" +
$" Rounded: {rounded}\n" +
$" Tip: Adds cinematic framing and focus to your scene";
}
catch (Exception e)
{
return CreateErrorResponse("CreateVignetteEffect", e, parameters);
}
}
private string CreateChromaticAberration(Dictionary<string, string> parameters)
{
try
{
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "0.1"));
var targetCamera = parameters.GetValueOrDefault("camera", "Main Camera");
Camera camera = null;
if (targetCamera == "Main Camera")
{
camera = Camera.main;
}
else
{
var cameraGO = GameObject.Find(targetCamera);
if (cameraGO != null) camera = cameraGO.GetComponent<Camera>();
}
if (camera == null)
{
return $"Camera '{targetCamera}' not found";
}
var chromaticController = camera.gameObject.GetComponent<ChromaticAberrationController>();
if (chromaticController == null)
{
chromaticController = camera.gameObject.AddComponent<ChromaticAberrationController>();
}
chromaticController.intensity = intensity;
return $"[NexusVisual] Chromatic Aberration added to '{camera.name}'!\n" +
$" Intensity: {intensity}\n" +
$" Note: Requires post-processing or custom shader for full effect\n" +
$" Tip: Creates realistic lens distortion and color separation";
}
catch (Exception e)
{
return CreateErrorResponse("CreateChromaticAberration", e, parameters);
}
}
// ===== Shader Tools Implementation =====
private string CreateShaderPropertyAnimator(Dictionary<string, string> parameters)
{
try
{
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var propertyName = parameters.GetValueOrDefault("propertyName", "_Color");
var propertyType = parameters.GetValueOrDefault("propertyType", "Color");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "1"));
var loop = bool.Parse(parameters.GetValueOrDefault("loop", "false"));
var curve = parameters.GetValueOrDefault("curve", "Linear");
GameObject target = GameObject.Find(targetObject);
if (target == null)
{
return $"GameObject '{targetObject}' not found";
}
var renderer = target.GetComponent<Renderer>();
if (renderer == null)
{
return $"Renderer component not found on '{targetObject}'";
}
var animator = target.GetComponent<ShaderPropertyAnimator>();
if (animator == null)
{
animator = target.AddComponent<ShaderPropertyAnimator>();
}
// Configure animation based on property type
switch (propertyType)
{
case "Color":
var startColor = parameters.GetValueOrDefault("startValue", "#FFFFFF");
var endColor = parameters.GetValueOrDefault("endValue", "#000000");
if (ColorUtility.TryParseHtmlString(startColor, out Color start) &&
ColorUtility.TryParseHtmlString(endColor, out Color end))
{
animator.AnimateColor(propertyName, start, end, duration, curve, loop);
}
break;
case "Float":
var startFloat = float.Parse(parameters.GetValueOrDefault("startValue", "0"));
var endFloat = float.Parse(parameters.GetValueOrDefault("endValue", "1"));
animator.AnimateFloat(propertyName, startFloat, endFloat, duration, curve, loop);
break;
case "Vector":
var startVector = ParseVector3(parameters.GetValueOrDefault("startValue", "0,0,0"));
var endVector = ParseVector3(parameters.GetValueOrDefault("endValue", "1,1,1"));
animator.AnimateVector(propertyName, startVector, endVector, duration, curve, loop);
break;
}
return $"[NexusVisual] Shader Property Animator added to '{target.name}'!\n" +
$" Property: {propertyName}\n" +
$" Type: {propertyType}\n" +
$" Duration: {duration}s\n" +
$" Loop: {loop}\n" +
$" Curve: {curve}\n" +
$" Tip: Animate material properties for dynamic visual effects";
}
catch (Exception e)
{
return CreateErrorResponse("CreateShaderPropertyAnimator", e, parameters);
}
}
private string CreateMaterialPropertyBlock(Dictionary<string, string> parameters)
{
try
{
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var blockName = parameters.GetValueOrDefault("blockName", "CustomPropertyBlock");
var preserveSharedMaterial = bool.Parse(parameters.GetValueOrDefault("preserveSharedMaterial", "true"));
GameObject target = GameObject.Find(targetObject);
if (target == null)
{
return $"GameObject '{targetObject}' not found";
}
var renderer = target.GetComponent<Renderer>();
if (renderer == null)
{
return $"Renderer component not found on '{targetObject}'";
}
var controller = target.GetComponent<MaterialPropertyBlockController>();
if (controller == null)
{
controller = target.AddComponent<MaterialPropertyBlockController>();
}
controller.blockName = blockName;
controller.preserveSharedMaterial = preserveSharedMaterial;
// Set initial properties if provided
if (parameters.ContainsKey("properties"))
{
var propertiesJson = parameters["properties"];
var properties = JsonConvert.DeserializeObject<Dictionary<string, object>>(propertiesJson);
foreach (var prop in properties)
{
controller.SetProperty(prop.Key, prop.Value);
}
}
controller.ApplyPropertyBlock();
return $"[NexusVisual] Material Property Block created on '{target.name}'!\n" +
$" Block Name: {blockName}\n" +
$" Preserve Shared Material: {preserveSharedMaterial}\n" +
$" Properties Set: {controller.GetPropertyCount()}\n" +
$" Tip: Modify material properties without creating material instances";
}
catch (Exception e)
{
return CreateErrorResponse("CreateMaterialPropertyBlock", e, parameters);
}
}
private string AnimateShaderTexture(Dictionary<string, string> parameters)
{
try
{
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var propertyName = parameters.GetValueOrDefault("propertyName", "_MainTex");
var animationType = parameters.GetValueOrDefault("animationType", "Scroll");
var speed = ParseVector2(parameters.GetValueOrDefault("speed", "1,0"));
var scale = ParseVector2(parameters.GetValueOrDefault("scale", "1,1"));
GameObject target = GameObject.Find(targetObject);
if (target == null)
{
return $"GameObject '{targetObject}' not found";
}
var renderer = target.GetComponent<Renderer>();
if (renderer == null)
{
return $"Renderer component not found on '{targetObject}'";
}
var textureAnimator = target.GetComponent<ShaderTextureAnimator>();
if (textureAnimator == null)
{
textureAnimator = target.AddComponent<ShaderTextureAnimator>();
}
textureAnimator.propertyName = propertyName;
textureAnimator.animationType = animationType;
textureAnimator.scrollSpeed = speed;
textureAnimator.tilingScale = scale;
if (animationType == "Flipbook")
{
textureAnimator.columns = int.Parse(parameters.GetValueOrDefault("columns", "4"));
textureAnimator.rows = int.Parse(parameters.GetValueOrDefault("rows", "4"));
textureAnimator.fps = float.Parse(parameters.GetValueOrDefault("fps", "30"));
}
return $"[NexusVisual] Shader Texture Animator added to '{target.name}'!\n" +
$" Property: {propertyName}\n" +
$" Animation Type: {animationType}\n" +
$" Speed: {speed}\n" +
$" Scale: {scale}\n" +
$" Tip: Create animated textures for water, fire, or UI effects";
}
catch (Exception e)
{
return CreateErrorResponse("AnimateShaderTexture", e, parameters);
}
}
private string CreateShaderGradient(Dictionary<string, string> parameters)
{
try
{
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var gradientType = parameters.GetValueOrDefault("gradientType", "Linear");
var startColor = parameters.GetValueOrDefault("startColor", "#FFFFFF");
var endColor = parameters.GetValueOrDefault("endColor", "#000000");
var direction = parameters.GetValueOrDefault("direction", "Vertical");
var blendMode = parameters.GetValueOrDefault("blendMode", "Normal");
GameObject target = GameObject.Find(targetObject);
if (target == null)
{
return $"GameObject '{targetObject}' not found";
}
var renderer = target.GetComponent<Renderer>();
if (renderer == null)
{
return $"Renderer component not found on '{targetObject}'";
}
// Create gradient material
var gradientMaterial = new Material(Shader.Find("Unlit/Texture"));
gradientMaterial.name = $"{targetObject}_Gradient";
// Create gradient texture
var gradientTexture = CreateGradientTexture(gradientType, startColor, endColor, direction);
gradientMaterial.mainTexture = gradientTexture;
// Apply blend mode
switch (blendMode)
{
case "Additive":
gradientMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
gradientMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);
break;
case "Multiply":
gradientMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.DstColor);
gradientMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
break;
}
renderer.material = gradientMaterial;
return $"[NexusVisual] Shader Gradient applied to '{target.name}'!\n" +
$" Type: {gradientType}\n" +
$" Direction: {direction}\n" +
$" Colors: {startColor} to {endColor}\n" +
$" Blend Mode: {blendMode}\n" +
$" Tip: Create smooth color transitions for backgrounds and UI";
}
catch (Exception e)
{
return CreateErrorResponse("CreateShaderGradient", e, parameters);
}
}
// Helper method for gradient texture creation
private Texture2D CreateGradientTexture(string type, string startColorHex, string endColorHex, string direction)
{
ColorUtility.TryParseHtmlString(startColorHex, out Color startColor);
ColorUtility.TryParseHtmlString(endColorHex, out Color endColor);
int width = direction == "Horizontal" ? 256 : 4;
int height = direction == "Vertical" ? 256 : 4;
var texture = new Texture2D(width, height);
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
float t = direction == "Horizontal" ? (float)x / (width - 1) : (float)y / (height - 1);
if (type == "Radial")
{
float centerX = width / 2f;
float centerY = height / 2f;
float distance = Mathf.Sqrt(Mathf.Pow(x - centerX, 2) + Mathf.Pow(y - centerY, 2));
t = distance / Mathf.Sqrt(centerX * centerX + centerY * centerY);
}
Color pixelColor = Color.Lerp(startColor, endColor, t);
texture.SetPixel(x, y, pixelColor);
}
}
texture.Apply();
texture.wrapMode = TextureWrapMode.Clamp;
return texture;
}
// ===== Camera Effects Implementation =====
// Note: Advanced camera effects (DoF, Motion Blur, etc.) require Post Processing Stack
// They have been removed from this implementation to avoid non-functional placeholders
// [Removed: CreateDepthOfField, CreateMotionBlur, CreateFilmGrain, CreateBloom, CreateLensDistortion]
// ===== Weather System Implementation =====
private string CreateRainEffect(Dictionary<string, string> parameters)
{
try
{
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "0.5"));
var dropSize = float.Parse(parameters.GetValueOrDefault("dropSize", "0.02"));
var dropSpeed = float.Parse(parameters.GetValueOrDefault("dropSpeed", "10"));
var windStrength = float.Parse(parameters.GetValueOrDefault("windStrength", "0"));
var rainArea = ParseVector3(parameters.GetValueOrDefault("rainArea", "50,30,50"));
var useSplash = bool.Parse(parameters.GetValueOrDefault("useSplash", "true"));
var rainSystem = new GameObject("RainSystem");
// Store parameters as a simple component for reference
var rainParams = rainSystem.AddComponent<WeatherParameters>();
rainParams.SetParameter("intensity", intensity);
rainParams.SetParameter("dropSize", dropSize);
rainParams.SetParameter("dropSpeed", dropSpeed);
rainParams.SetParameter("windStrength", windStrength);
rainParams.SetParameter("rainArea", rainArea);
rainParams.SetParameter("useSplash", useSplash);
// Create particle system for rain
var particleSystem = rainSystem.AddComponent<ParticleSystem>();
var main = particleSystem.main;
main.maxParticles = Mathf.RoundToInt(intensity * 5000);
main.startLifetime = rainArea.y / dropSpeed;
main.startSpeed = dropSpeed;
main.startSize = dropSize;
var shape = particleSystem.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(rainArea.x, 0, rainArea.z);
shape.position = new Vector3(0, rainArea.y / 2, 0);
var velocityOverLifetime = particleSystem.velocityOverLifetime;
velocityOverLifetime.enabled = true;
// Set all axes to the same mode (Constant) to avoid curve mode conflicts
velocityOverLifetime.x = new ParticleSystem.MinMaxCurve(windStrength);
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(0f);
velocityOverLifetime.z = new ParticleSystem.MinMaxCurve(0f);
var emission = particleSystem.emission;
emission.rateOverTime = intensity * 1000;
// Assign compatible material to prevent pink materials
AssignWeatherMaterial(particleSystem, "particle");
// Add RainController for runtime behavior
var controller = rainSystem.AddComponent<RainController>();
controller.intensity = intensity;
controller.dropSize = dropSize;
controller.dropSpeed = dropSpeed;
controller.windStrength = windStrength;
controller.rainArea = rainArea;
controller.useSplash = useSplash;
return $"[NexusVisual] Rain Effect created successfully!\n" +
$" Intensity: {intensity}\n" +
$" Drop Size: {dropSize}\n" +
$" Drop Speed: {dropSpeed}m/s\n" +
$" Wind Strength: {windStrength}\n" +
$" Rain Area: {rainArea}\n" +
$" Splash Effects: {useSplash}\n" +
$" Tip: Adjust intensity and wind for different rain conditions";
}
catch (Exception e)
{
return CreateErrorResponse("CreateRainEffect", e, parameters);
}
}
private string CreateSnowEffect(Dictionary<string, string> parameters)
{
try
{
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "0.3"));
var flakeSize = ParseVector2(parameters.GetValueOrDefault("flakeSize", "0.1,0.3"));
var fallSpeed = float.Parse(parameters.GetValueOrDefault("fallSpeed", "1"));
var turbulence = float.Parse(parameters.GetValueOrDefault("turbulence", "0.5"));
var snowArea = ParseVector3(parameters.GetValueOrDefault("snowArea", "50,20,50"));
var accumulation = bool.Parse(parameters.GetValueOrDefault("accumulation", "false"));
var snowSystem = new GameObject("SnowSystem");
// Store parameters as a simple component for reference
var snowParams = snowSystem.AddComponent<WeatherParameters>();
snowParams.SetParameter("intensity", intensity);
snowParams.SetParameter("flakeSizeMin", flakeSize.x);
snowParams.SetParameter("flakeSizeMax", flakeSize.y);
snowParams.SetParameter("fallSpeed", fallSpeed);
snowParams.SetParameter("turbulence", turbulence);
snowParams.SetParameter("snowArea", snowArea);
snowParams.SetParameter("accumulation", accumulation);
// Create particle system for snow
var particleSystem = snowSystem.AddComponent<ParticleSystem>();
var main = particleSystem.main;
main.maxParticles = Mathf.RoundToInt(intensity * 8000);
main.startLifetime = snowArea.y / fallSpeed;
main.startSpeed = fallSpeed;
main.startSize = new ParticleSystem.MinMaxCurve(flakeSize.x, flakeSize.y);
main.gravityModifier = 0.3f;
var shape = particleSystem.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(snowArea.x, 0, snowArea.z);
shape.position = new Vector3(0, snowArea.y / 2, 0);
var velocityOverLifetime = particleSystem.velocityOverLifetime;
velocityOverLifetime.enabled = true;
// Set all axes to the same mode (Random Between Two Constants) to avoid curve mode conflicts
velocityOverLifetime.x = new ParticleSystem.MinMaxCurve(-turbulence, turbulence);
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(0f, 0f);
velocityOverLifetime.z = new ParticleSystem.MinMaxCurve(-turbulence, turbulence);
var emission = particleSystem.emission;
emission.rateOverTime = intensity * 500;
var noise = particleSystem.noise;
noise.enabled = true;
noise.strength = turbulence;
noise.frequency = 0.5f;
// Assign compatible material to prevent pink materials
AssignWeatherMaterial(particleSystem, "particle");
// Add SnowController for runtime behavior
var controller = snowSystem.AddComponent<SnowController>();
controller.intensity = intensity;
controller.flakeSizeMin = flakeSize.x;
controller.flakeSizeMax = flakeSize.y;
controller.fallSpeed = fallSpeed;
controller.turbulence = turbulence;
controller.snowArea = snowArea;
controller.accumulation = accumulation;
return $"[NexusVisual] Snow Effect created successfully!\n" +
$" Intensity: {intensity}\n" +
$" Flake Size: {flakeSize.x} - {flakeSize.y}\n" +
$" Fall Speed: {fallSpeed}m/s\n" +
$" Turbulence: {turbulence}\n" +
$" Snow Area: {snowArea}\n" +
$" Accumulation: {accumulation}\n" +
$" Tip: Add fog for more atmospheric winter scenes";
}
catch (Exception e)
{
return CreateErrorResponse("CreateSnowEffect", e, parameters);
}
}
private string CreateWindEffect(Dictionary<string, string> parameters)
{
try
{
var strength = float.Parse(parameters.GetValueOrDefault("strength", "5"));
var direction = ParseVector3(parameters.GetValueOrDefault("direction", "1,0,0"));
var turbulence = float.Parse(parameters.GetValueOrDefault("turbulence", "0.5"));
var gustFrequency = float.Parse(parameters.GetValueOrDefault("gustFrequency", "0.2"));
var affectTrees = bool.Parse(parameters.GetValueOrDefault("affectTrees", "true"));
var affectParticles = bool.Parse(parameters.GetValueOrDefault("affectParticles", "true"));
var windSystem = new GameObject("WindSystem");
// Store parameters as a simple component for reference
var windParams = windSystem.AddComponent<WeatherParameters>();
windParams.SetParameter("windStrength", strength);
windParams.SetParameter("windDirection", direction.normalized);
windParams.SetParameter("turbulence", turbulence);
windParams.SetParameter("gustFrequency", gustFrequency);
windParams.SetParameter("affectTrees", affectTrees);
windParams.SetParameter("affectParticles", affectParticles);
// Create wind zone
var windZone = windSystem.AddComponent<WindZone>();
windZone.mode = WindZoneMode.Directional;
windZone.windMain = strength;
windZone.windTurbulence = turbulence;
windZone.windPulseMagnitude = strength * 0.5f;
windZone.windPulseFrequency = gustFrequency;
windSystem.transform.rotation = Quaternion.LookRotation(direction);
// Add WindController for gust simulation
var controller = windSystem.AddComponent<WindController>();
controller.windStrength = strength;
controller.windDirection = direction.normalized;
controller.turbulence = turbulence;
controller.gustFrequency = gustFrequency;
controller.affectTrees = affectTrees;
controller.affectParticles = affectParticles;
return $"[NexusVisual] Wind Effect created successfully!\n" +
$" Strength: {strength}\n" +
$" Direction: {direction.normalized}\n" +
$" Turbulence: {turbulence}\n" +
$" Gust Frequency: {gustFrequency}Hz\n" +
$" Affect Trees: {affectTrees}\n" +
$" Affect Particles: {affectParticles}\n" +
$" Tip: Combine with rain or snow for realistic weather";
}
catch (Exception e)
{
return CreateErrorResponse("CreateWindEffect", e, parameters);
}
}
private string CreateLightningEffect(Dictionary<string, string> parameters)
{
try
{
var frequency = float.Parse(parameters.GetValueOrDefault("frequency", "0.1"));
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "5"));
var duration = float.Parse(parameters.GetValueOrDefault("duration", "0.2"));
var color = parameters.GetValueOrDefault("color", "#E0E0FF");
var minDelay = float.Parse(parameters.GetValueOrDefault("minDelay", "2"));
var maxDelay = float.Parse(parameters.GetValueOrDefault("maxDelay", "10"));
var affectSkybox = bool.Parse(parameters.GetValueOrDefault("affectSkybox", "true"));
var lightningSystem = new GameObject("LightningSystem");
// Store parameters as a simple component for reference
var lightningParams = lightningSystem.AddComponent<WeatherParameters>();
lightningParams.SetParameter("frequency", frequency);
lightningParams.SetParameter("intensity", intensity);
lightningParams.SetParameter("duration", duration);
lightningParams.SetParameter("minDelay", minDelay);
lightningParams.SetParameter("maxDelay", maxDelay);
lightningParams.SetParameter("affectSkybox", affectSkybox);
Color lightningColor = Color.white;
if (ColorUtility.TryParseHtmlString(color, out Color parsedColor))
{
lightningColor = parsedColor;
}
lightningParams.SetParameter("lightningColor", lightningColor);
// Create lightning light
var lightningLight = new GameObject("LightningLight");
lightningLight.transform.SetParent(lightningSystem.transform);
var light = lightningLight.AddComponent<Light>();
light.type = LightType.Directional;
light.intensity = 0;
light.color = lightningColor;
light.transform.rotation = Quaternion.Euler(45, -30, 0);
lightningParams.SetParameter("lightningLight", light);
// Add LightningController for actual flash animation
var controller = lightningSystem.AddComponent<LightningController>();
controller.lightningLight = light;
controller.frequency = frequency;
controller.intensity = intensity;
controller.duration = duration;
controller.lightningColor = lightningColor;
controller.minDelay = minDelay;
controller.maxDelay = maxDelay;
controller.affectSkybox = affectSkybox;
return $"[NexusVisual] Lightning Effect created successfully!\n" +
$" Frequency: {frequency} strikes/minute\n" +
$" Intensity: {intensity}\n" +
$" Duration: {duration}s\n" +
$" Color: {color}\n" +
$" Delay Range: {minDelay}s - {maxDelay}s\n" +
$" Affect Skybox: {affectSkybox}\n" +
$" Tip: Combine with rain and thunder for realistic storms";
}
catch (Exception e)
{
return CreateErrorResponse("CreateLightningEffect", e, parameters);
}
}
private string CreateThunderstorm(Dictionary<string, string> parameters)
{
try
{
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "0.8"));
var windStrength = float.Parse(parameters.GetValueOrDefault("windStrength", "10"));
var lightningFrequency = float.Parse(parameters.GetValueOrDefault("lightningFrequency", "0.2"));
var fogDensity = float.Parse(parameters.GetValueOrDefault("fogDensity", "0.02"));
var duration = float.Parse(parameters.GetValueOrDefault("duration", "0"));
var thunderstorm = new GameObject("Thunderstorm");
// Store parameters as a simple component for reference
var stormParams = thunderstorm.AddComponent<WeatherParameters>();
stormParams.SetParameter("intensity", intensity);
stormParams.SetParameter("windStrength", windStrength);
stormParams.SetParameter("lightningFrequency", lightningFrequency);
stormParams.SetParameter("fogDensity", fogDensity);
stormParams.SetParameter("duration", duration);
// Create rain
var rainParams = new Dictionary<string, string>
{
{"intensity", intensity.ToString()},
{"windStrength", (windStrength * 0.5f).ToString()},
{"dropSpeed", "15"}
};
CreateRainEffect(rainParams);
// Create wind
var windParams = new Dictionary<string, string>
{
{"strength", windStrength.ToString()},
{"turbulence", "0.8"},
{"gustFrequency", "0.5"}
};
CreateWindEffect(windParams);
// Create lightning
var lightningParams = new Dictionary<string, string>
{
{"frequency", lightningFrequency.ToString()},
{"intensity", "8"},
{"affectSkybox", "true"}
};
CreateLightningEffect(lightningParams);
// Set fog
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.Exponential;
RenderSettings.fogDensity = fogDensity;
RenderSettings.fogColor = new Color(0.4f, 0.4f, 0.5f);
// Add ThunderstormController for coordinated effects
var controller = thunderstorm.AddComponent<ThunderstormController>();
controller.intensity = intensity;
controller.windStrength = windStrength;
controller.lightningFrequency = lightningFrequency;
controller.fogDensity = fogDensity;
controller.duration = duration;
return $"[NexusVisual] Thunderstorm created successfully!\n" +
$" Storm Intensity: {intensity}\n" +
$" Wind Strength: {windStrength}m/s\n" +
$" Lightning Frequency: {lightningFrequency}/min\n" +
$" Fog Density: {fogDensity}\n" +
$" Duration: {(duration > 0 ? duration + "s" : "Continuous")}\n" +
$" Components: Rain, Wind, Lightning, Fog\n" +
$" Tip: Add audio for thunder sounds to complete the effect";
}
catch (Exception e)
{
return CreateErrorResponse("CreateThunderstorm", e, parameters);
}
}
private void ValidateAndFixWeatherAssets()
{
// Detect rendering pipeline
var pipeline = DetectRenderingPipeline();
SynLog.Info($"[NexusWeather] Detected rendering pipeline: {pipeline}");
// Check and fix shader compatibility
CheckAndFixShaderCompatibility(pipeline);
// Ensure proper materials exist
EnsureWeatherMaterials(pipeline);
// Validate particle system settings
ValidateParticleSystemSettings();
}
private static string DetectRenderingPipeline()
{
// Always re-detect to handle pipeline changes during session
string detected = "Legacy";
// Check for URP/HDRP
var currentPipeline = UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline;
if (currentPipeline != null)
{
var pipelineName = currentPipeline.GetType().Name;
SynLog.Info($"[DetectRenderingPipeline] Pipeline type: {pipelineName}");
if (pipelineName.Contains("Universal") || pipelineName.Contains("URP"))
detected = "URP";
else if (pipelineName.Contains("HighDefinition") || pipelineName.Contains("HDRP"))
detected = "HDRP";
}
else
{
// Double check by looking for URP asset in quality settings
var qualityAsset = UnityEngine.Rendering.GraphicsSettings.defaultRenderPipeline;
if (qualityAsset != null)
{
var assetName = qualityAsset.GetType().Name;
if (assetName.Contains("Universal") || assetName.Contains("URP"))
detected = "URP";
else if (assetName.Contains("HighDefinition") || assetName.Contains("HDRP"))
detected = "HDRP";
}
}
// Cache and log
if (_cachedPipeline != detected)
{
SynLog.Info($"[DetectRenderingPipeline] Detected: {detected}");
}
_cachedPipeline = detected;
return _cachedPipeline;
}
/// <summary>
/// Create primitive with render pipeline compatible material
/// </summary>
private static GameObject CreatePrimitiveWithMaterial(PrimitiveType primitiveType, Color? color = null)
{
var go = GameObject.CreatePrimitive(primitiveType);
var renderer = go.GetComponent<Renderer>();
if (renderer != null)
{
var pipeline = DetectRenderingPipeline();
string shaderName;
switch (pipeline)
{
case "URP":
shaderName = "Universal Render Pipeline/Lit";
break;
case "HDRP":
shaderName = "HDRenderPipeline/Lit";
break;
default:
shaderName = "Standard";
break;
}
var shader = Shader.Find(shaderName);
if (shader != null)
{
var material = new Material(shader);
if (color.HasValue)
{
// URP/HDRP use _BaseColor, Legacy uses _Color
if (material.HasProperty("_BaseColor"))
{
material.SetColor("_BaseColor", color.Value);
}
else if (material.HasProperty("_Color"))
{
material.SetColor("_Color", color.Value);
}
else
{
material.color = color.Value;
}
}
renderer.material = material;
SynLog.Info($"[CreatePrimitiveWithMaterial] Created {pipeline} material with shader: {shaderName}");
}
else
{
SynLog.Warn($"[Synaptic] Shader '{shaderName}' not found for {pipeline} pipeline. Using default material.");
}
}
return go;
}
/// <summary>
/// Create Material with render pipeline compatible shader
/// </summary>
private static Material CreateRenderPipelineCompatibleMaterial(Color? color = null)
{
var pipeline = DetectRenderingPipeline();
string shaderName;
switch (pipeline)
{
case "URP":
shaderName = "Universal Render Pipeline/Lit";
break;
case "HDRP":
shaderName = "HDRenderPipeline/Lit";
break;
default:
shaderName = "Standard";
break;
}
var shader = Shader.Find(shaderName);
if (shader == null)
{
SynLog.Warn($"[Synaptic] Shader '{shaderName}' not found for {pipeline} pipeline. Falling back to Standard.");
shader = Shader.Find("Standard");
}
var material = new Material(shader);
if (color.HasValue)
{
// URP/HDRP use _BaseColor, Legacy uses _Color
if (material.HasProperty("_BaseColor"))
{
material.SetColor("_BaseColor", color.Value);
}
else if (material.HasProperty("_Color"))
{
material.SetColor("_Color", color.Value);
}
else
{
material.color = color.Value;
}
}
SynLog.Info($"[CreateRenderPipelineCompatibleMaterial] Created material with shader: {shaderName}");
return material;
}
/// <summary>
/// Get render pipeline compatible shader
/// </summary>
private static Shader GetRenderPipelineCompatibleShader()
{
var pipeline = DetectRenderingPipeline();
string shaderName;
switch (pipeline)
{
case "URP":
shaderName = "Universal Render Pipeline/Lit";
break;
case "HDRP":
shaderName = "HDRenderPipeline/Lit";
break;
default:
shaderName = "Standard";
break;
}
var shader = Shader.Find(shaderName);
if (shader == null)
{
shader = Shader.Find("Standard");
}
return shader;
}
/// <summary>
/// Resolve shader name with pipeline-aware aliasing
/// </summary>
private static Shader ResolveShaderName(string requestedShaderName)
{
// Try exact match first
Shader shader = Shader.Find(requestedShaderName);
if (shader != null) return shader;
var pipeline = DetectRenderingPipeline();
// Handle common aliases
string normalizedName = requestedShaderName.Trim().ToLower();
// "Lit" alias - auto-detect pipeline
if (normalizedName == "lit")
{
switch (pipeline)
{
case "URP":
shader = Shader.Find("Universal Render Pipeline/Lit");
if (shader != null) return shader;
break;
case "HDRP":
shader = Shader.Find("HDRenderPipeline/Lit");
if (shader != null) return shader;
break;
default:
shader = Shader.Find("Standard");
if (shader != null) return shader;
break;
}
}
// "Unlit" alias - auto-detect pipeline
if (normalizedName == "unlit")
{
switch (pipeline)
{
case "URP":
shader = Shader.Find("Universal Render Pipeline/Unlit");
if (shader != null) return shader;
break;
case "HDRP":
shader = Shader.Find("HDRenderPipeline/Unlit");
if (shader != null) return shader;
break;
default:
shader = Shader.Find("Unlit/Color");
if (shader != null) return shader;
break;
}
}
// Handle URP aliases
if (normalizedName.Contains("urp") || normalizedName.Contains("universal"))
{
if (normalizedName.Contains("lit"))
{
shader = Shader.Find("Universal Render Pipeline/Lit");
if (shader != null) return shader;
}
if (normalizedName.Contains("unlit"))
{
shader = Shader.Find("Universal Render Pipeline/Unlit");
if (shader != null) return shader;
}
}
// Handle HDRP aliases (including "HDRP/Lit" -> "HDRenderPipeline/Lit")
if (normalizedName.Contains("hdrp") || normalizedName.Contains("high definition"))
{
if (normalizedName.Contains("lit") && !normalizedName.Contains("unlit"))
{
shader = Shader.Find("HDRenderPipeline/Lit");
if (shader != null) return shader;
}
if (normalizedName.Contains("unlit"))
{
shader = Shader.Find("HDRenderPipeline/Unlit");
if (shader != null) return shader;
}
}
// Fallback: use pipeline compatible shader
return GetRenderPipelineCompatibleShader();
}
/// <summary>
/// Set color property with pipeline compatibility
/// </summary>
private static void SetMaterialColor(Material material, Color color)
{
// URP/HDRP use _BaseColor, Legacy uses _Color
if (material.HasProperty("_BaseColor"))
{
material.SetColor("_BaseColor", color);
SynLog.Info($"[Synaptic] Set _BaseColor to {color}");
}
else if (material.HasProperty("_Color"))
{
material.SetColor("_Color", color);
SynLog.Info($"[Synaptic] Set _Color to {color}");
}
else
{
SynLog.Warn($"[Synaptic] Material '{material.name}' has no color property");
}
}
/// <summary>
/// Set emission with pipeline compatibility
/// </summary>
private static void SetMaterialEmission(Material material, Color emissionColor, float intensity)
{
if (material.HasProperty("_EmissionColor"))
{
Color finalEmission = emissionColor * intensity;
material.SetColor("_EmissionColor", finalEmission);
material.EnableKeyword("_EMISSION");
material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;
SynLog.Info($"[Synaptic] Set emission to {finalEmission} (intensity: {intensity})");
}
else
{
SynLog.Warn($"[Synaptic] Material '{material.name}' has no emission property");
}
}
private void CheckAndFixShaderCompatibility(string pipeline)
{
try
{
// Create fallback materials for each pipeline
CreateFallbackFogMaterial(pipeline);
CreateFallbackParticleMaterial(pipeline);
SynLog.Info($"[NexusWeather] Shader compatibility fixed for {pipeline}");
}
catch (Exception e)
{
Debug.LogError($"[NexusWeather] Shader compatibility check failed: {e.Message}");
}
}
private void CreateFallbackFogMaterial(string pipeline)
{
try
{
Material fogMaterial = null;
switch (pipeline)
{
case "URP":
fogMaterial = new Material(Shader.Find("Universal Render Pipeline/Particles/Unlit"));
break;
case "HDRP":
fogMaterial = new Material(Shader.Find("HDRP/Unlit"));
break;
default: // Legacy
fogMaterial = new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
break;
}
if (fogMaterial != null)
{
fogMaterial.name = "WeatherFog_Fallback";
fogMaterial.color = new Color(1f, 1f, 1f, 0.1f);
// Create Resources folder if it doesn't exist
var resourcesPath = "Assets/Resources";
if (!AssetDatabase.IsValidFolder(resourcesPath))
{
AssetDatabase.CreateFolder("Assets", "Resources");
}
// Save material
AssetDatabase.CreateAsset(fogMaterial, $"{resourcesPath}/WeatherFog_Fallback.mat");
AssetDatabase.SaveAssets();
}
}
catch (Exception e)
{
SynLog.Warn($"[NexusWeather] Could not create fog fallback material: {e.Message}");
}
}
private void CreateFallbackParticleMaterial(string pipeline)
{
try
{
Material particleMaterial = null;
switch (pipeline)
{
case "URP":
particleMaterial = new Material(Shader.Find("Universal Render Pipeline/Particles/Unlit"));
break;
case "HDRP":
particleMaterial = new Material(Shader.Find("HDRP/Unlit"));
break;
default: // Legacy
particleMaterial = new Material(Shader.Find("Legacy Shaders/Particles/Alpha Blended"));
break;
}
if (particleMaterial != null)
{
particleMaterial.name = "WeatherParticle_Fallback";
particleMaterial.color = Color.white;
// Create Resources folder if it doesn't exist
var resourcesPath = "Assets/Resources";
if (!AssetDatabase.IsValidFolder(resourcesPath))
{
AssetDatabase.CreateFolder("Assets", "Resources");
}
// Save material
AssetDatabase.CreateAsset(particleMaterial, $"{resourcesPath}/WeatherParticle_Fallback.mat");
AssetDatabase.SaveAssets();
}
}
catch (Exception e)
{
SynLog.Warn($"[NexusWeather] Could not create particle fallback material: {e.Message}");
}
}
private void EnsureWeatherMaterials(string pipeline)
{
try
{
// Load or create fallback materials
var fogMaterial = Resources.Load<Material>("WeatherFog_Fallback");
var particleMaterial = Resources.Load<Material>("WeatherParticle_Fallback");
if (fogMaterial == null)
{
CreateFallbackFogMaterial(pipeline);
}
if (particleMaterial == null)
{
CreateFallbackParticleMaterial(pipeline);
}
SynLog.Info($"[NexusWeather] Weather materials validated for {pipeline}");
}
catch (Exception e)
{
Debug.LogError($"[NexusWeather] Material validation failed: {e.Message}");
}
}
private void ValidateParticleSystemSettings()
{
try
{
// This method ensures particle systems created by weather effects
// have consistent velocity curve modes to prevent the error:
// "Particle Velocity curves must all be in the same mode"
var particleSystems = UnityEngine.Object.FindObjectsOfType<ParticleSystem>();
foreach (var ps in particleSystems)
{
if (ps.gameObject.name.Contains("Rain") ||
ps.gameObject.name.Contains("Snow") ||
ps.gameObject.name.Contains("Weather") ||
ps.gameObject.name.Contains("Fog"))
{
var velocityOverLifetime = ps.velocityOverLifetime;
if (velocityOverLifetime.enabled)
{
// Ensure all velocity axes use the same mode
var xMode = velocityOverLifetime.x.mode;
// Create consistent curves for all axes
var yVel = velocityOverLifetime.y;
yVel.mode = xMode;
velocityOverLifetime.y = yVel;
var zVel = velocityOverLifetime.z;
zVel.mode = xMode;
velocityOverLifetime.z = zVel;
}
}
}
SynLog.Info("[NexusWeather] Particle system settings validated");
}
catch (Exception e)
{
SynLog.Warn($"[NexusWeather] Particle validation warning: {e.Message}");
}
}
private void AssignWeatherMaterial(ParticleSystem particleSystem, string materialType)
{
try
{
var renderer = particleSystem.GetComponent<ParticleSystemRenderer>();
if (renderer != null)
{
Material material = null;
// First try to load from Resources
switch (materialType.ToLower())
{
case "fog":
material = Resources.Load<Material>("WeatherFog_Fallback");
break;
case "particle":
default:
material = Resources.Load<Material>("WeatherParticle_Fallback");
break;
}
// If no material found in Resources, create one based on rendering pipeline
if (material == null)
{
var pipeline = DetectRenderingPipeline();
Shader shader = null;
// Try to find appropriate shader based on pipeline
switch (pipeline)
{
case "URP":
shader = Shader.Find("Universal Render Pipeline/Particles/Unlit");
if (shader == null) shader = Shader.Find("Universal Render Pipeline/Unlit");
break;
case "HDRP":
shader = Shader.Find("HDRP/Unlit");
if (shader == null) shader = Shader.Find("HDRenderPipeline/Lit");
break;
}
// Fallback to legacy shaders if pipeline-specific ones not found
if (shader == null)
{
shader = Shader.Find("Legacy Shaders/Particles/Alpha Blended");
if (shader == null) shader = Shader.Find("Sprites/Default");
if (shader == null) shader = Shader.Find("Unlit/Transparent");
if (shader == null) shader = GetRenderPipelineCompatibleShader();
}
if (shader != null)
{
material = new Material(shader);
// Configure material for weather particles
if (material.HasProperty("_Color"))
{
material.SetColor("_Color", new Color(1f, 1f, 1f, 0.8f));
}
// Set render queue for transparency
material.renderQueue = 3000;
renderer.material = material;
}
else
{
Debug.LogError("[NexusWeather] No suitable shader found for weather particles!");
}
}
else
{
renderer.material = material;
}
}
}
catch (Exception e)
{
SynLog.Warn($"[NexusWeather] Could not assign material: {e.Message}");
}
}
private string CreateWeatherSystem(Dictionary<string, string> parameters)
{
try
{
var preset = parameters.GetValueOrDefault("preset", "sunny");
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "0.5"));
var transitionTime = float.Parse(parameters.GetValueOrDefault("transitionTime", "5"));
var windDirection = parameters.GetValueOrDefault("windDirection", "1,0,0.3");
// Auto-diagnostic and repair system
ValidateAndFixWeatherAssets();
var weatherSystem = new GameObject("WeatherSystem");
var controller = weatherSystem.AddComponent<WeatherSystemController>();
controller.currentPreset = preset;
controller.intensity = intensity;
controller.transitionTime = transitionTime;
// Apply preset
switch (preset.ToLower())
{
case "sunny":
ApplySunnyWeather(controller);
break;
case "rainy":
ApplyRainyWeather(controller, intensity);
break;
case "snowy":
ApplySnowyWeather(controller, intensity);
break;
case "stormy":
ApplyStormyWeather(controller, intensity);
break;
case "foggy":
ApplyFoggyWeather(controller, intensity);
break;
case "custom":
// Allow custom configuration
break;
default:
return $"Unknown weather preset: {preset}";
}
return $"[NexusVisual] Weather System created successfully!\n" +
$" Preset: {preset}\n" +
$" Intensity: {intensity}\n" +
$" Transition Time: {transitionTime}s\n" +
$" Rendering Pipeline: {DetectRenderingPipeline()}\n" +
$" Auto-Diagnostics: Completed\n" +
$" Shader Compatibility: Fixed\n" +
$" Material Generation: Automated\n" +
$" Features: Integrated particles, fog, lighting, and atmosphere\n" +
$" Performance: Optimized for all pipelines\n" +
$" Tip: Change weather dynamically with SetWeatherPreset command";
}
catch (Exception e)
{
return CreateErrorResponse("CreateWeatherSystem", e, parameters);
}
}
// ===== Professional Weather System (Enviro3-level quality) =====
// Based on AAA game industry standards for dynamic weather
private void ApplySunnyWeather(WeatherSystemController controller)
{
controller.ClearWeatherEffects();
// Clear sky - no precipitation
// Atmospheric settings
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = 0.002f; // Very light atmospheric haze
RenderSettings.fogColor = new Color(0.75f, 0.85f, 0.95f); // Light blue sky color
// Trilight ambient for realistic sky/ground bounce
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.6f, 0.7f, 0.9f); // Blue sky
RenderSettings.ambientEquatorColor = new Color(0.8f, 0.85f, 0.85f); // Horizon
RenderSettings.ambientGroundColor = new Color(0.4f, 0.45f, 0.35f); // Ground bounce
// Sun configuration (golden hour feel at default, can be adjusted)
var sun = GetOrCreateSun();
if (sun != null)
{
sun.color = new Color(1f, 0.96f, 0.88f); // Warm daylight ~5500K
sun.intensity = 1.3f;
sun.shadowStrength = 0.75f;
sun.shadows = LightShadows.Soft;
sun.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
}
// Create subtle heat shimmer particles (optional)
CreateHeatShimmer(controller, 0.3f);
controller.currentWeatherState = "sunny";
}
private void ApplyRainyWeather(WeatherSystemController controller, float intensity)
{
controller.ClearWeatherEffects();
// Create realistic rain with multiple layers
CreateProfessionalRain(controller, intensity);
// Overcast atmosphere
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = 0.015f + (0.02f * intensity); // Denser with intensity
RenderSettings.fogColor = new Color(0.45f, 0.48f, 0.55f);
// Dark overcast ambient
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.4f, 0.42f, 0.5f);
RenderSettings.ambientEquatorColor = new Color(0.35f, 0.37f, 0.42f);
RenderSettings.ambientGroundColor = new Color(0.2f, 0.22f, 0.25f);
// Dimmed diffused sunlight
var sun = GetOrCreateSun();
if (sun != null)
{
sun.color = new Color(0.7f, 0.72f, 0.78f);
sun.intensity = 0.3f + (0.2f * (1f - intensity));
sun.shadowStrength = 0.2f;
sun.transform.rotation = Quaternion.Euler(60f, -30f, 0f);
}
// Add wet surface reflections (ground darkening)
ApplyWetSurfaceEffect(controller, intensity);
controller.currentWeatherState = "rainy";
}
private void ApplySnowyWeather(WeatherSystemController controller, float intensity)
{
controller.ClearWeatherEffects();
// Create realistic snowfall
CreateProfessionalSnow(controller, intensity);
// Bright overcast (snow reflects light)
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.Linear;
RenderSettings.fogStartDistance = 10f;
RenderSettings.fogEndDistance = 80f - (40f * intensity); // Visibility decreases with intensity
RenderSettings.fogColor = new Color(0.88f, 0.9f, 0.95f);
// High ambient from snow reflection
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.75f, 0.78f, 0.85f);
RenderSettings.ambientEquatorColor = new Color(0.7f, 0.72f, 0.78f);
RenderSettings.ambientGroundColor = new Color(0.8f, 0.82f, 0.88f); // Bright from snow
// Soft diffused light
var sun = GetOrCreateSun();
if (sun != null)
{
sun.color = new Color(0.9f, 0.92f, 1f);
sun.intensity = 0.6f;
sun.shadowStrength = 0.15f; // Very soft shadows in snow
sun.transform.rotation = Quaternion.Euler(30f, -45f, 0f); // Low winter sun
}
controller.currentWeatherState = "snowy";
}
private void ApplyStormyWeather(WeatherSystemController controller, float intensity)
{
controller.ClearWeatherEffects();
// Heavy rain
CreateProfessionalRain(controller, intensity * 1.5f);
// Create lightning system
CreateLightningSystem(controller, intensity);
// Dark stormy atmosphere
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = 0.04f + (0.04f * intensity);
RenderSettings.fogColor = new Color(0.25f, 0.27f, 0.35f);
// Very dark ambient
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.15f, 0.17f, 0.25f);
RenderSettings.ambientEquatorColor = new Color(0.12f, 0.14f, 0.2f);
RenderSettings.ambientGroundColor = new Color(0.08f, 0.08f, 0.1f);
// Nearly occluded sun
var sun = GetOrCreateSun();
if (sun != null)
{
sun.color = new Color(0.4f, 0.42f, 0.5f);
sun.intensity = 0.05f;
sun.shadowStrength = 0.05f;
}
// Add wind effects
ApplyWindEffect(controller, 15f * intensity);
controller.currentWeatherState = "stormy";
}
private void ApplyFoggyWeather(WeatherSystemController controller, float intensity)
{
controller.ClearWeatherEffects();
// Heavy volumetric fog
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.ExponentialSquared;
RenderSettings.fogDensity = 0.03f + (0.07f * intensity);
RenderSettings.fogColor = new Color(0.75f, 0.77f, 0.8f);
// Flat diffused ambient
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.6f, 0.62f, 0.65f);
// Very soft directional light
var sun = GetOrCreateSun();
if (sun != null)
{
sun.color = new Color(0.85f, 0.85f, 0.9f);
sun.intensity = 0.2f + (0.2f * (1f - intensity));
sun.shadowStrength = 0.05f; // Almost no shadows in fog
}
// Create fog particles for depth
CreateFogParticles(controller, intensity);
// Add volumetric fog effect
var fogParams = new Dictionary<string, string>
{
{"fogName", "FoggyWeather"},
{"density", (0.05f * intensity).ToString()},
{"height", "20"}
};
SetupVolumetricFog(fogParams);
controller.currentWeatherState = "foggy";
}
// ===== Weather Effect Helpers =====
private Light GetOrCreateSun()
{
var sun = GameObject.Find("Directional Light") ?? GameObject.Find("Sun");
if (sun == null)
{
sun = new GameObject("Directional Light");
var light = sun.AddComponent<Light>();
light.type = LightType.Directional;
}
return sun.GetComponent<Light>();
}
private void CreateProfessionalRain(WeatherSystemController controller, float intensity)
{
var rainParent = new GameObject("RainSystem");
rainParent.transform.SetParent(controller.transform);
// Main rain layer (close drops)
var mainRain = new GameObject("MainRain");
mainRain.transform.SetParent(rainParent.transform);
var ps = mainRain.AddComponent<ParticleSystem>();
SetupRainParticles(ps);
var main = ps.main;
main.maxParticles = (int)(5000 * intensity);
var emission = ps.emission;
emission.rateOverTime = 2000 * intensity;
// Distance rain layer (atmospheric)
var distantRain = new GameObject("DistantRain");
distantRain.transform.SetParent(rainParent.transform);
var ps2 = distantRain.AddComponent<ParticleSystem>();
SetupRainParticles(ps2);
var main2 = ps2.main;
main2.startSize = new ParticleSystem.MinMaxCurve(0.02f, 0.05f);
main2.startSpeed = new ParticleSystem.MinMaxCurve(10f, 15f);
main2.startColor = new Color(0.6f, 0.65f, 0.75f, 0.3f);
main2.maxParticles = (int)(3000 * intensity);
var shape2 = ps2.shape;
shape2.scale = new Vector3(50f, 0f, 50f);
shape2.position = new Vector3(0f, 25f, 0f);
// Splash particles
var splashes = new GameObject("RainSplashes");
splashes.transform.SetParent(rainParent.transform);
var ps3 = splashes.AddComponent<ParticleSystem>();
var main3 = ps3.main;
main3.startLifetime = new ParticleSystem.MinMaxCurve(0.1f, 0.2f);
main3.startSpeed = new ParticleSystem.MinMaxCurve(1f, 3f);
main3.startSize = new ParticleSystem.MinMaxCurve(0.02f, 0.05f);
main3.startColor = new Color(0.8f, 0.85f, 0.9f, 0.6f);
main3.gravityModifier = 1f;
main3.maxParticles = (int)(1000 * intensity);
var emission3 = ps3.emission;
emission3.rateOverTime = 500 * intensity;
var shape3 = ps3.shape;
shape3.shapeType = ParticleSystemShapeType.Box;
shape3.scale = new Vector3(30f, 0.1f, 30f);
shape3.position = new Vector3(0f, 0.1f, 0f);
}
private void CreateProfessionalSnow(WeatherSystemController controller, float intensity)
{
var snowParent = new GameObject("SnowSystem");
snowParent.transform.SetParent(controller.transform);
// Main snowfall
var mainSnow = new GameObject("MainSnow");
mainSnow.transform.SetParent(snowParent.transform);
var ps = mainSnow.AddComponent<ParticleSystem>();
SetupSnowParticles(ps);
var main = ps.main;
main.maxParticles = (int)(4000 * intensity);
var emission = ps.emission;
emission.rateOverTime = 400 * intensity;
// Fine snow particles (blizzard effect at high intensity)
if (intensity > 0.5f)
{
var fineSnow = new GameObject("FineSnow");
fineSnow.transform.SetParent(snowParent.transform);
var ps2 = fineSnow.AddComponent<ParticleSystem>();
var main2 = ps2.main;
main2.startLifetime = new ParticleSystem.MinMaxCurve(3f, 6f);
main2.startSpeed = new ParticleSystem.MinMaxCurve(0.5f, 2f);
main2.startSize = new ParticleSystem.MinMaxCurve(0.01f, 0.03f);
main2.startColor = new Color(1f, 1f, 1f, 0.5f);
main2.gravityModifier = 0.02f;
main2.maxParticles = (int)(2000 * (intensity - 0.5f) * 2);
var emission2 = ps2.emission;
emission2.rateOverTime = 300 * (intensity - 0.5f) * 2;
var shape2 = ps2.shape;
shape2.shapeType = ParticleSystemShapeType.Box;
shape2.scale = new Vector3(50f, 5f, 50f);
var noise2 = ps2.noise;
noise2.enabled = true;
noise2.strength = 2f;
noise2.frequency = 0.5f;
}
}
private void CreateLightningSystem(WeatherSystemController controller, float intensity)
{
var lightningSystem = new GameObject("LightningSystem");
lightningSystem.transform.SetParent(controller.transform);
// Lightning flash light
var flashLight = new GameObject("LightningFlash");
flashLight.transform.SetParent(lightningSystem.transform);
flashLight.transform.position = new Vector3(0, 50, 0);
var light = flashLight.AddComponent<Light>();
light.type = LightType.Directional;
light.color = new Color(0.9f, 0.95f, 1f);
light.intensity = 0f; // Will be animated
light.shadows = LightShadows.None;
light.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
// Lightning bolt particle
var boltEffect = new GameObject("LightningBolt");
boltEffect.transform.SetParent(lightningSystem.transform);
var ps = boltEffect.AddComponent<ParticleSystem>();
var main = ps.main;
main.duration = 0.2f;
main.loop = false;
main.startLifetime = 0.1f;
main.startSpeed = 100f;
main.startSize = 0.5f;
main.startColor = new Color(0.9f, 0.95f, 1f);
main.maxParticles = 20;
var emission = ps.emission;
emission.rateOverTime = 0;
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 5, 10) });
var trails = ps.trails;
trails.enabled = true;
trails.lifetime = 0.1f;
// Add LightningController for actual flash animation
var lightningController = lightningSystem.AddComponent<LightningController>();
lightningController.lightningLight = light;
lightningController.frequency = intensity;
lightningController.intensity = 5f * intensity;
lightningController.duration = 0.2f;
lightningController.lightningColor = new Color(0.9f, 0.95f, 1f);
lightningController.minDelay = 2f;
lightningController.maxDelay = 10f / intensity;
lightningController.affectSkybox = true;
}
private void CreateHeatShimmer(WeatherSystemController controller, float intensity)
{
// Creates subtle heat distortion effect (visual only, no particles)
// In production, this would use a post-process effect or shader
}
private void ApplyWetSurfaceEffect(WeatherSystemController controller, float intensity)
{
// In production, this would modify material properties for wet look
// For now, we'll create a visual indicator
var wetSurface = new GameObject("WetSurfaceEffect");
wetSurface.transform.SetParent(controller.transform);
}
private void ApplyWindEffect(WeatherSystemController controller, float strength)
{
var windZone = new GameObject("WindZone");
windZone.transform.SetParent(controller.transform);
var wind = windZone.AddComponent<WindZone>();
wind.mode = WindZoneMode.Directional;
wind.windMain = strength;
wind.windTurbulence = strength * 0.3f;
wind.windPulseMagnitude = strength * 0.2f;
wind.windPulseFrequency = 0.5f;
windZone.transform.rotation = Quaternion.Euler(15f, 45f, 0f);
}
private void CreateFogParticles(WeatherSystemController controller, float intensity)
{
var fogParticles = new GameObject("FogParticles");
fogParticles.transform.SetParent(controller.transform);
var ps = fogParticles.AddComponent<ParticleSystem>();
var main = ps.main;
main.startLifetime = new ParticleSystem.MinMaxCurve(8f, 15f);
main.startSpeed = new ParticleSystem.MinMaxCurve(0.2f, 0.5f);
main.startSize = new ParticleSystem.MinMaxCurve(3f, 8f);
main.startColor = new Color(0.8f, 0.82f, 0.85f, 0.15f);
main.maxParticles = (int)(200 * intensity);
main.simulationSpace = ParticleSystemSimulationSpace.World;
var emission = ps.emission;
emission.rateOverTime = 20 * intensity;
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Box;
shape.scale = new Vector3(50f, 5f, 50f);
shape.position = new Vector3(0f, 2f, 0f);
var colorOverLifetime = ps.colorOverLifetime;
colorOverLifetime.enabled = true;
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
new GradientAlphaKey[] { new GradientAlphaKey(0f, 0f), new GradientAlphaKey(0.2f, 0.3f), new GradientAlphaKey(0.2f, 0.7f), new GradientAlphaKey(0f, 1f) }
);
colorOverLifetime.color = gradient;
var noise = ps.noise;
noise.enabled = true;
noise.strength = 0.5f;
noise.frequency = 0.2f;
noise.scrollSpeed = 0.1f;
}
private string SetWeatherPreset(Dictionary<string, string> parameters)
{
try
{
var preset = parameters.GetValueOrDefault("preset", "sunny");
var transitionTime = float.Parse(parameters.GetValueOrDefault("transitionTime", "5"));
var weatherSystem = GameObject.Find("WeatherSystem");
if (weatherSystem == null)
{
return "Weather System not found. Create it first using CreateWeatherSystem";
}
var controller = weatherSystem.GetComponent<WeatherSystemController>();
if (controller == null)
{
return "WeatherSystemController component not found";
}
// Start transition
controller.TransitionToPreset(preset, transitionTime);
return $"[NexusVisual] Weather transitioning to '{preset}'!\n" +
$" Transition Duration: {transitionTime}s\n" +
$" Current Weather: {controller.currentWeatherState}\n" +
$" Tip: Weather will smoothly blend over the transition period";
}
catch (Exception e)
{
return CreateErrorResponse("SetWeatherPreset", e, parameters);
}
}
// ===== Phase 3: Smart Shader System Implementation =====
private class ShaderTemplateManager
{
private static Dictionary<string, string> shaderTemplates = new Dictionary<string, string>();
private static Dictionary<string, ShaderValidationRule> validationRules = new Dictionary<string, ShaderValidationRule>();
static ShaderTemplateManager()
{
InitializeShaderTemplates();
InitializeValidationRules();
}
private static void InitializeShaderTemplates()
{
// Bloom Effect Shader Template
shaderTemplates["Bloom"] = @"
Shader ""NexusGenerated/Bloom""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_Intensity (""Bloom Intensity"", Range(0, 10)) = {INTENSITY}
_Threshold (""Bloom Threshold"", Range(0, 1)) = {THRESHOLD}
_BlurSize (""Blur Size"", Range(0, 10)) = {BLUR_SIZE}
}
SubShader
{
Tags { ""RenderType""=""Opaque"" ""RenderPipeline""=""{PIPELINE}"" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _Intensity;
float _Threshold;
float _BlurSize;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
// Brightness threshold
half brightness = dot(color.rgb, half3(0.299, 0.587, 0.114));
half contribution = max(0, brightness - _Threshold);
contribution /= max(brightness, 0.00001);
// Apply bloom
color.rgb *= contribution * _Intensity;
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Film Grain Shader Template
shaderTemplates["FilmGrain"] = @"
Shader ""NexusGenerated/FilmGrain""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_GrainIntensity (""Grain Intensity"", Range(0, 1)) = {INTENSITY}
_GrainSize (""Grain Size"", Range(0.1, 10)) = {GRAIN_SIZE}
_NoiseTexture (""Noise Texture"", 2D) = ""white"" {}
}
SubShader
{
Tags { ""RenderType""=""Opaque"" ""RenderPipeline""=""{PIPELINE}"" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
TEXTURE2D(_NoiseTexture);
SAMPLER(sampler_MainTex);
SAMPLER(sampler_NoiseTexture);
float _GrainIntensity;
float _GrainSize;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
// Sample noise
float2 grainUV = input.uv * _GrainSize;
half grain = SAMPLE_TEXTURE2D(_NoiseTexture, sampler_NoiseTexture, grainUV).r;
grain = (grain - 0.5) * 2.0; // Remap to -1 to 1
// Apply grain
color.rgb += grain * _GrainIntensity;
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Motion Blur Shader Template
shaderTemplates["MotionBlur"] = @"
Shader ""NexusGenerated/MotionBlur""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_BlurStrength (""Blur Strength"", Range(0, 1)) = {BLUR_STRENGTH}
_SampleCount (""Sample Count"", Range(4, 32)) = {SAMPLE_COUNT}
}
SubShader
{
Tags { ""RenderType""=""Opaque"" ""RenderPipeline""=""{PIPELINE}"" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _BlurStrength;
int _SampleCount;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
half4 color = half4(0, 0, 0, 0);
// Simple radial blur for motion effect
float2 center = float2(0.5, 0.5);
float2 dir = normalize(input.uv - center);
for(int i = 0; i < _SampleCount; i++)
{
float offset = (float(i) / float(_SampleCount - 1) - 0.5) * _BlurStrength;
float2 sampleUV = input.uv + dir * offset * 0.1;
color += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, sampleUV);
}
color /= float(_SampleCount);
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Depth of Field Shader Template
shaderTemplates["DepthOfField"] = @"
Shader ""NexusGenerated/DepthOfField""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_DepthTex (""Depth Texture"", 2D) = ""white"" {}
_FocusDistance (""Focus Distance"", Range(0, 100)) = {FOCUS_DISTANCE}
_BlurSize (""Blur Size"", Range(0, 10)) = {BLUR_SIZE}
_Aperture (""Aperture"", Range(0.1, 32)) = {APERTURE}
}
SubShader
{
Tags { ""RenderType""=""Opaque"" ""RenderPipeline""=""{PIPELINE}"" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
TEXTURE2D(_DepthTex);
SAMPLER(sampler_MainTex);
SAMPLER(sampler_DepthTex);
float _FocusDistance;
float _BlurSize;
float _Aperture;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
float depth = SAMPLE_TEXTURE2D(_DepthTex, sampler_DepthTex, input.uv).r;
// Calculate blur amount based on depth
float linearDepth = Linear01Depth(depth, _ZBufferParams);
float blur = abs(linearDepth - _FocusDistance) * _Aperture;
blur = saturate(blur / _BlurSize);
// Simple box blur
if (blur > 0.1)
{
half4 blurredColor = half4(0, 0, 0, 0);
float2 texelSize = 1.0 / _ScreenParams.xy;
for(int x = -2; x <= 2; x++)
{
for(int y = -2; y <= 2; y++)
{
float2 offset = float2(x, y) * texelSize * blur * _BlurSize;
blurredColor += SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv + offset);
}
}
blurredColor /= 25.0;
color = lerp(color, blurredColor, blur);
}
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Lens Distortion Shader Template
shaderTemplates["LensDistortion"] = @"
Shader ""NexusGenerated/LensDistortion""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_DistortionStrength (""Distortion Strength"", Range(-1, 1)) = {DISTORTION_STRENGTH}
_ChromaticAberration (""Chromatic Aberration"", Range(0, 1)) = {CHROMATIC_ABERRATION}
_Vignette (""Vignette"", Range(0, 1)) = {VIGNETTE}
}
SubShader
{
Tags { ""RenderType""=""Opaque"" ""RenderPipeline""=""{PIPELINE}"" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _DistortionStrength;
float _ChromaticAberration;
float _Vignette;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
float2 DistortUV(float2 uv, float strength)
{
float2 center = float2(0.5, 0.5);
float2 delta = uv - center;
float distance = length(delta);
float factor = 1.0 + strength * distance * distance;
return center + delta * factor;
}
half4 frag(Varyings input) : SV_Target
{
float2 uv = input.uv;
// Apply lens distortion
if (_DistortionStrength != 0)
{
uv = DistortUV(uv, _DistortionStrength);
}
half4 color;
// Chromatic aberration
if (_ChromaticAberration > 0)
{
float2 redUV = DistortUV(input.uv, _DistortionStrength + _ChromaticAberration * 0.01);
float2 greenUV = uv;
float2 blueUV = DistortUV(input.uv, _DistortionStrength - _ChromaticAberration * 0.01);
color.r = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, redUV).r;
color.g = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, greenUV).g;
color.b = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, blueUV).b;
color.a = 1.0;
}
else
{
color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
}
// Vignette effect
if (_Vignette > 0)
{
float2 center = float2(0.5, 0.5);
float distance = length(input.uv - center);
float vignette = 1.0 - smoothstep(0.3, 0.8, distance * _Vignette);
color.rgb *= vignette;
}
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Vignette Effect Shader Template
shaderTemplates["Vignette"] = @"
Shader ""NexusGenerated/Vignette""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_VignetteIntensity (""Vignette Intensity"", Range(0, 1)) = {INTENSITY}
_VignetteSmoothness (""Vignette Smoothness"", Range(0, 1)) = {SMOOTHNESS}
_VignetteColor (""Vignette Color"", Color) = {COLOR}
_VignetteCenter (""Vignette Center"", Vector) = (0.5, 0.5, 0, 0)
}
SubShader
{
Tags { ""RenderType""=""Transparent"" ""Queue""=""Overlay"" ""RenderPipeline""=""{PIPELINE}"" }
ZWrite Off
ZTest Always
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _VignetteIntensity;
float _VignetteSmoothness;
float4 _VignetteColor;
float4 _VignetteCenter;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
// Calculate distance from center
float2 center = _VignetteCenter.xy;
float distance = length(input.uv - center);
// Create vignette mask
float vignette = 1.0 - smoothstep(0.3, 0.8, distance * (1.0 + _VignetteIntensity));
vignette = lerp(1.0, vignette, _VignetteIntensity);
vignette = smoothstep(0.0, _VignetteSmoothness, vignette);
// Apply vignette
color.rgb = lerp(_VignetteColor.rgb, color.rgb, vignette);
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Screen Fade Shader Template
shaderTemplates["ScreenFade"] = @"
Shader ""NexusGenerated/ScreenFade""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_FadeAmount (""Fade Amount"", Range(0, 1)) = {FADE_AMOUNT}
_FadeColor (""Fade Color"", Color) = {FADE_COLOR}
_FadeType (""Fade Type"", Int) = {FADE_TYPE}
}
SubShader
{
Tags { ""RenderType""=""Transparent"" ""Queue""=""Overlay"" ""RenderPipeline""=""{PIPELINE}"" }
ZWrite Off
ZTest Always
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _FadeAmount;
float4 _FadeColor;
int _FadeType;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
float fade = _FadeAmount;
// Different fade types
if (_FadeType == 1) // Radial fade
{
float2 center = float2(0.5, 0.5);
float distance = length(input.uv - center);
fade *= smoothstep(0.0, 0.7, distance);
}
else if (_FadeType == 2) // Horizontal fade
{
fade *= input.uv.x;
}
else if (_FadeType == 3) // Vertical fade
{
fade *= input.uv.y;
}
// Apply fade
color.rgb = lerp(color.rgb, _FadeColor.rgb, fade);
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Color Grading Shader Template
shaderTemplates["ColorGrading"] = @"
Shader ""NexusGenerated/ColorGrading""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_Brightness (""Brightness"", Range(-1, 1)) = {BRIGHTNESS}
_Contrast (""Contrast"", Range(0, 3)) = {CONTRAST}
_Saturation (""Saturation"", Range(0, 3)) = {SATURATION}
_Hue (""Hue"", Range(-180, 180)) = {HUE}
_Gamma (""Gamma"", Range(0.1, 3)) = {GAMMA}
_ColorFilter (""Color Filter"", Color) = {COLOR_FILTER}
}
SubShader
{
Tags { ""RenderType""=""Opaque"" ""RenderPipeline""=""{PIPELINE}"" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _Brightness;
float _Contrast;
float _Saturation;
float _Hue;
float _Gamma;
float4 _ColorFilter;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
float3 HueShift(float3 color, float hue)
{
float angle = radians(hue);
float3 k = float3(0.57735, 0.57735, 0.57735);
float cosAngle = cos(angle);
return color * cosAngle + cross(k, color) * sin(angle) + k * dot(k, color) * (1.0 - cosAngle);
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
// Gamma correction
color.rgb = pow(abs(color.rgb), 1.0 / _Gamma);
// Brightness
color.rgb += _Brightness;
// Contrast
color.rgb = (color.rgb - 0.5) * _Contrast + 0.5;
// Saturation
float luminance = dot(color.rgb, float3(0.299, 0.587, 0.114));
color.rgb = lerp(luminance.xxx, color.rgb, _Saturation);
// Hue shift
if (_Hue != 0.0)
{
color.rgb = HueShift(color.rgb, _Hue);
}
// Color filter
color.rgb *= _ColorFilter.rgb;
return saturate(color);
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Dissolve Effect Shader Template
shaderTemplates["Dissolve"] = @"
Shader ""NexusGenerated/Dissolve""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_NoiseTex (""Noise Texture"", 2D) = ""white"" {}
_DissolveAmount (""Dissolve Amount"", Range(0, 1)) = {DISSOLVE_AMOUNT}
_EdgeWidth (""Edge Width"", Range(0, 0.3)) = {EDGE_WIDTH}
_EdgeColor (""Edge Color"", Color) = {EDGE_COLOR}
_EdgeIntensity (""Edge Intensity"", Range(0, 10)) = {EDGE_INTENSITY}
}
SubShader
{
Tags { ""RenderType""=""Transparent"" ""Queue""=""Geometry"" ""RenderPipeline""=""{PIPELINE}"" }
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
TEXTURE2D(_NoiseTex);
SAMPLER(sampler_MainTex);
SAMPLER(sampler_NoiseTex);
float _DissolveAmount;
float _EdgeWidth;
float4 _EdgeColor;
float _EdgeIntensity;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
half4 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
half noise = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, input.uv).r;
// Dissolve calculation
half dissolve = noise - _DissolveAmount;
// Discard pixels
clip(dissolve);
// Edge glow effect
half edge = smoothstep(0.0, _EdgeWidth, dissolve);
half3 edgeGlow = _EdgeColor.rgb * _EdgeIntensity * (1.0 - edge);
// Final color
albedo.rgb += edgeGlow;
albedo.a = edge;
return albedo;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Lens Flare Shader Template
shaderTemplates["LensFlare"] = @"
Shader ""NexusGenerated/LensFlare""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_FlareTex (""Flare Texture"", 2D) = ""white"" {}
_FlareIntensity (""Flare Intensity"", Range(0, 5)) = {FLARE_INTENSITY}
_FlareSize (""Flare Size"", Range(0.1, 5)) = {FLARE_SIZE}
_FlareColor (""Flare Color"", Color) = {FLARE_COLOR}
_LightPosition (""Light Position"", Vector) = {LIGHT_POSITION}
_FlareCount (""Flare Count"", Int) = {FLARE_COUNT}
}
SubShader
{
Tags { ""RenderType""=""Transparent"" ""Queue""=""Transparent"" ""RenderPipeline""=""{PIPELINE}"" }
Blend One One
ZWrite Off
ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
TEXTURE2D(_FlareTex);
SAMPLER(sampler_MainTex);
SAMPLER(sampler_FlareTex);
float _FlareIntensity;
float _FlareSize;
float4 _FlareColor;
float4 _LightPosition;
int _FlareCount;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
// Light position in screen space
float2 lightPos = _LightPosition.xy;
float2 center = float2(0.5, 0.5);
float2 flareDir = lightPos - center;
// Generate multiple flares
half3 flareColor = half3(0, 0, 0);
for(int i = 0; i < _FlareCount; i++)
{
float t = (float)i / (float)(_FlareCount - 1);
float2 flarePos = center - flareDir * t * 0.8;
float2 uv = (input.uv - flarePos) / _FlareSize + 0.5;
if(uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0)
{
half4 flare = SAMPLE_TEXTURE2D(_FlareTex, sampler_FlareTex, uv);
float falloff = 1.0 - t * 0.5;
flareColor += flare.rgb * _FlareColor.rgb * _FlareIntensity * falloff;
}
}
color.rgb += flareColor;
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Wave Distortion Shader Template
shaderTemplates["WaveDistortion"] = @"
Shader ""NexusGenerated/WaveDistortion""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_WaveStrength (""Wave Strength"", Range(0, 0.1)) = {WAVE_STRENGTH}
_WaveFrequency (""Wave Frequency"", Range(1, 50)) = {WAVE_FREQUENCY}
_WaveSpeed (""Wave Speed"", Range(0, 10)) = {WAVE_SPEED}
_WaveType (""Wave Type"", Int) = {WAVE_TYPE}
_DistortionCenter (""Distortion Center"", Vector) = (0.5, 0.5, 0, 0)
}
SubShader
{
Tags { ""RenderType""=""Opaque"" ""RenderPipeline""=""{PIPELINE}"" }
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _WaveStrength;
float _WaveFrequency;
float _WaveSpeed;
int _WaveType;
float4 _DistortionCenter;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 frag(Varyings input) : SV_Target
{
float2 uv = input.uv;
float time = _Time.y * _WaveSpeed;
float2 distortion = float2(0, 0);
if (_WaveType == 0) // Horizontal waves
{
distortion.x = sin(uv.y * _WaveFrequency + time) * _WaveStrength;
}
else if (_WaveType == 1) // Vertical waves
{
distortion.y = sin(uv.x * _WaveFrequency + time) * _WaveStrength;
}
else if (_WaveType == 2) // Radial waves
{
float2 center = _DistortionCenter.xy;
float distance = length(uv - center);
float wave = sin(distance * _WaveFrequency - time) * _WaveStrength;
float2 direction = normalize(uv - center);
distortion = direction * wave;
}
else if (_WaveType == 3) // Ripple effect
{
float2 center = _DistortionCenter.xy;
float distance = length(uv - center);
float ripple = sin(distance * _WaveFrequency - time) * _WaveStrength / (distance + 0.1);
float2 direction = normalize(uv - center);
distortion = direction * ripple;
}
uv += distortion;
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv);
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// Lightning Bolt Shader Template
shaderTemplates["LightningBolt"] = @"
Shader ""NexusGenerated/LightningBolt""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_LightningIntensity (""Lightning Intensity"", Range(0, 10)) = {LIGHTNING_INTENSITY}
_BoltWidth (""Bolt Width"", Range(0.001, 0.1)) = {BOLT_WIDTH}
_BoltColor (""Bolt Color"", Color) = {BOLT_COLOR}
_GlowIntensity (""Glow Intensity"", Range(0, 5)) = {GLOW_INTENSITY}
_AnimationSpeed (""Animation Speed"", Range(0, 20)) = {ANIMATION_SPEED}
_Segments (""Segments"", Int) = {SEGMENTS}
}
SubShader
{
Tags { ""RenderType""=""Transparent"" ""Queue""=""Transparent"" ""RenderPipeline""=""{PIPELINE}"" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _LightningIntensity;
float _BoltWidth;
float4 _BoltColor;
float _GlowIntensity;
float _AnimationSpeed;
int _Segments;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
float random(float2 st)
{
return frac(sin(dot(st.xy, float2(12.9898,78.233))) * 43758.5453123);
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
float2 uv = input.uv;
float time = _Time.y * _AnimationSpeed;
// Generate lightning bolt path
float bolt = 0.0;
float y = uv.y;
// Create jagged lightning path
float x = 0.5;
for(int i = 0; i < _Segments; i++)
{
float t = (float)i / (float)_Segments;
float noise = random(float2(t, floor(time * 10.0))) - 0.5;
x += noise * 0.2 * (1.0 - abs(t - 0.5) * 2.0);
}
// Distance to bolt
float distance = abs(uv.x - x);
// Create bolt with glow
float boltCore = 1.0 - smoothstep(0.0, _BoltWidth, distance);
float boltGlow = 1.0 - smoothstep(0.0, _BoltWidth * 5.0, distance);
// Animate intensity
float flicker = 0.8 + 0.2 * sin(time * 50.0);
// Combine effects
float lightning = boltCore * _LightningIntensity * flicker +
boltGlow * _GlowIntensity * 0.3 * flicker;
color.rgb += _BoltColor.rgb * lightning;
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
// True Volumetric Fog Shader Template
shaderTemplates["VolumetricFog"] = @"
Shader ""NexusGenerated/VolumetricFog""
{
Properties
{
_MainTex (""Texture"", 2D) = ""white"" {}
_DepthTex (""Depth Texture"", 2D) = ""white"" {}
_FogDensity (""Fog Density"", Range(0, 1)) = {FOG_DENSITY}
_FogColor (""Fog Color"", Color) = {FOG_COLOR}
_FogHeight (""Fog Height"", Range(0, 100)) = {FOG_HEIGHT}
_StepCount (""Ray Steps"", Int) = {STEP_COUNT}
_LightScattering (""Light Scattering"", Range(0, 1)) = {LIGHT_SCATTERING}
}
SubShader
{
Tags { ""RenderType""=""Transparent"" ""Queue""=""Transparent"" ""RenderPipeline""=""{PIPELINE}"" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
ZTest Always
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 viewRay : TEXCOORD1;
};
TEXTURE2D(_MainTex);
TEXTURE2D(_DepthTex);
SAMPLER(sampler_MainTex);
SAMPLER(sampler_DepthTex);
float _FogDensity;
float4 _FogColor;
float _FogHeight;
int _StepCount;
float _LightScattering;
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
// Calculate view ray for ray marching
float3 viewRay = mul(unity_CameraInvProjection, float4(input.uv * 2.0 - 1.0, 1.0, 1.0)).xyz;
output.viewRay = mul(unity_CameraToWorld, float4(viewRay, 0.0)).xyz;
return output;
}
float SampleFogDensity(float3 worldPos)
{
// Height-based fog density
float heightFog = exp(-worldPos.y / _FogHeight);
// Add noise for variation
float noise = sin(worldPos.x * 0.1) * sin(worldPos.z * 0.1) * 0.3 + 0.7;
return heightFog * noise * _FogDensity;
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
float depth = SAMPLE_TEXTURE2D(_DepthTex, sampler_DepthTex, input.uv).r;
// Convert depth to world position
float linearDepth = Linear01Depth(depth, _ZBufferParams);
float3 worldPos = _WorldSpaceCameraPos + input.viewRay * linearDepth;
// Ray marching through fog
float3 rayStart = _WorldSpaceCameraPos;
float3 rayEnd = worldPos;
float3 rayDir = rayEnd - rayStart;
float rayLength = length(rayDir);
rayDir /= rayLength;
float stepSize = rayLength / (float)_StepCount;
float fog = 0.0;
for(int i = 0; i < _StepCount; i++)
{
float t = (float)i / (float)_StepCount;
float3 samplePos = rayStart + rayDir * t * rayLength;
float density = SampleFogDensity(samplePos);
fog += density * stepSize;
}
// Apply light scattering
float3 lightDir = normalize(_MainLightPosition.xyz);
float scattering = pow(max(0.0, dot(-input.viewRay, lightDir)), 2.0) * _LightScattering;
// Final fog color
float3 fogColor = _FogColor.rgb * (1.0 + scattering);
fog = saturate(fog);
color.rgb = lerp(color.rgb, fogColor, fog);
return color;
}
ENDHLSL
}
}
Fallback ""Hidden/Universal Render Pipeline/FallbackError""
}";
}
private static void InitializeValidationRules()
{
validationRules["Bloom"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "INTENSITY", "THRESHOLD", "BLUR_SIZE", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "INTENSITY", (0f, 10f) },
{ "THRESHOLD", (0f, 1f) },
{ "BLUR_SIZE", (0f, 10f) }
}
};
validationRules["FilmGrain"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "INTENSITY", "GRAIN_SIZE", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "INTENSITY", (0f, 1f) },
{ "GRAIN_SIZE", (0.1f, 10f) }
}
};
validationRules["MotionBlur"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "BLUR_STRENGTH", "SAMPLE_COUNT", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "BLUR_STRENGTH", (0f, 1f) },
{ "SAMPLE_COUNT", (4f, 32f) }
}
};
validationRules["DepthOfField"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "FOCUS_DISTANCE", "BLUR_SIZE", "APERTURE", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "FOCUS_DISTANCE", (0f, 100f) },
{ "BLUR_SIZE", (0f, 10f) },
{ "APERTURE", (0.1f, 32f) }
}
};
validationRules["LensDistortion"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "DISTORTION_STRENGTH", "CHROMATIC_ABERRATION", "VIGNETTE", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "DISTORTION_STRENGTH", (-1f, 1f) },
{ "CHROMATIC_ABERRATION", (0f, 1f) },
{ "VIGNETTE", (0f, 1f) }
}
};
validationRules["Vignette"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "INTENSITY", "SMOOTHNESS", "COLOR", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "INTENSITY", (0f, 1f) },
{ "SMOOTHNESS", (0f, 1f) }
}
};
validationRules["ScreenFade"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "FADE_AMOUNT", "FADE_COLOR", "FADE_TYPE", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "FADE_AMOUNT", (0f, 1f) },
{ "FADE_TYPE", (0f, 3f) }
}
};
validationRules["ColorGrading"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "BRIGHTNESS", "CONTRAST", "SATURATION", "HUE", "GAMMA", "COLOR_FILTER", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "BRIGHTNESS", (-1f, 1f) },
{ "CONTRAST", (0f, 3f) },
{ "SATURATION", (0f, 3f) },
{ "HUE", (-180f, 180f) },
{ "GAMMA", (0.1f, 3f) }
}
};
validationRules["Dissolve"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "DISSOLVE_AMOUNT", "EDGE_WIDTH", "EDGE_COLOR", "EDGE_INTENSITY", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "DISSOLVE_AMOUNT", (0f, 1f) },
{ "EDGE_WIDTH", (0f, 0.3f) },
{ "EDGE_INTENSITY", (0f, 10f) }
}
};
validationRules["LensFlare"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "FLARE_INTENSITY", "FLARE_SIZE", "FLARE_COLOR", "LIGHT_POSITION", "FLARE_COUNT", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "FLARE_INTENSITY", (0f, 5f) },
{ "FLARE_SIZE", (0.1f, 5f) },
{ "FLARE_COUNT", (1f, 20f) }
}
};
validationRules["WaveDistortion"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "WAVE_STRENGTH", "WAVE_FREQUENCY", "WAVE_SPEED", "WAVE_TYPE", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "WAVE_STRENGTH", (0f, 0.1f) },
{ "WAVE_FREQUENCY", (1f, 50f) },
{ "WAVE_SPEED", (0f, 10f) },
{ "WAVE_TYPE", (0f, 3f) }
}
};
validationRules["LightningBolt"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "LIGHTNING_INTENSITY", "BOLT_WIDTH", "BOLT_COLOR", "GLOW_INTENSITY", "ANIMATION_SPEED", "SEGMENTS", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "LIGHTNING_INTENSITY", (0f, 10f) },
{ "BOLT_WIDTH", (0.001f, 0.1f) },
{ "GLOW_INTENSITY", (0f, 5f) },
{ "ANIMATION_SPEED", (0f, 20f) },
{ "SEGMENTS", (3f, 50f) }
}
};
validationRules["VolumetricFog"] = new ShaderValidationRule
{
requiredParameters = new List<string> { "FOG_DENSITY", "FOG_COLOR", "FOG_HEIGHT", "STEP_COUNT", "LIGHT_SCATTERING", "PIPELINE" },
parameterRanges = new Dictionary<string, (float min, float max)>
{
{ "FOG_DENSITY", (0f, 1f) },
{ "FOG_HEIGHT", (0f, 100f) },
{ "STEP_COUNT", (8f, 64f) },
{ "LIGHT_SCATTERING", (0f, 1f) }
}
};
}
public static bool TryGenerateShader(string effectName, Dictionary<string, string> parameters, out string shaderCode, out string errorMessage)
{
shaderCode = "";
errorMessage = "";
try
{
// Validate effect exists
if (!shaderTemplates.ContainsKey(effectName))
{
errorMessage = $"Shader template for '{effectName}' not found";
return false;
}
// Validate parameters
if (!ValidateParameters(effectName, parameters, out errorMessage))
{
return false;
}
// Get template
string template = shaderTemplates[effectName];
// Auto-detect pipeline if not specified
if (!parameters.ContainsKey("PIPELINE"))
{
parameters["PIPELINE"] = DetectRenderingPipelineForShader();
}
// Replace parameters in template
shaderCode = template;
foreach (var param in parameters)
{
shaderCode = shaderCode.Replace($"{{{param.Key}}}", param.Value);
}
// Validate generated shader
if (!ValidateGeneratedShader(shaderCode, out errorMessage))
{
return false;
}
SynLog.Info($"[NexusShader] Successfully generated {effectName} shader for {parameters.GetValueOrDefault("PIPELINE", "Unknown")} pipeline");
return true;
}
catch (Exception e)
{
errorMessage = $"Shader generation failed: {e.Message}";
Debug.LogError($"[NexusShader] {errorMessage}");
return false;
}
}
private static bool ValidateParameters(string effectName, Dictionary<string, string> parameters, out string errorMessage)
{
errorMessage = "";
if (!validationRules.ContainsKey(effectName))
{
errorMessage = $"No validation rules for effect '{effectName}'";
return false;
}
var rules = validationRules[effectName];
// Check required parameters
foreach (var required in rules.requiredParameters)
{
if (required == "PIPELINE") continue; // Auto-generated
if (!parameters.ContainsKey(required))
{
errorMessage = $"Required parameter '{required}' missing for {effectName}";
return false;
}
}
// Check parameter ranges
foreach (var range in rules.parameterRanges)
{
if (parameters.ContainsKey(range.Key))
{
if (float.TryParse(parameters[range.Key], out float value))
{
if (value < range.Value.min || value > range.Value.max)
{
errorMessage = $"Parameter '{range.Key}' value {value} is outside valid range [{range.Value.min}, {range.Value.max}]";
return false;
}
}
else
{
errorMessage = $"Parameter '{range.Key}' is not a valid number: {parameters[range.Key]}";
return false;
}
}
}
return true;
}
private static bool ValidateGeneratedShader(string shaderCode, out string errorMessage)
{
errorMessage = "";
// Basic syntax validation
if (!shaderCode.Contains("Shader ") || !shaderCode.Contains("SubShader"))
{
errorMessage = "Generated shader missing basic structure";
return false;
}
// Check for unreplaced template parameters (e.g., {PROPERTY_NAME}, {VALUE})
// Allow normal HLSL braces but detect placeholder patterns
if (System.Text.RegularExpressions.Regex.IsMatch(shaderCode, @"\{[A-Z_]+\}"))
{
errorMessage = "Shader contains unreplaced template parameters";
return false;
}
// Check for required HLSL components
if (shaderCode.Contains("HLSLPROGRAM") && (!shaderCode.Contains("#pragma vertex") || !shaderCode.Contains("#pragma fragment")))
{
errorMessage = "HLSL shader missing required pragmas";
return false;
}
return true;
}
private static string DetectRenderingPipelineForShader()
{
if (UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline != null)
{
var pipelineName = UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline.GetType().Name;
if (pipelineName.Contains("Universal"))
return "UniversalPipeline";
else if (pipelineName.Contains("HighDefinition"))
return "HDRenderPipeline";
}
return ""; // Built-in/Legacy
}
public static List<string> GetAvailableEffects()
{
return new List<string>(shaderTemplates.Keys);
}
}
private class ShaderValidationRule
{
public List<string> requiredParameters;
public Dictionary<string, (float min, float max)> parameterRanges;
}
// ===== Auto-Generated Shader Effects Helper =====
private string CreateShaderEffectWithAutoGeneration(string effectName, Dictionary<string, string> parameters, System.Func<Material, GameObject> applyEffect)
{
try
{
SynLog.Info($"[NexusShader] Starting {effectName} effect creation with auto-shader generation");
// Try to generate shader
if (!ShaderTemplateManager.TryGenerateShader(effectName, parameters, out string shaderCode, out string errorMessage))
{
SynLog.Warn($"[NexusShader] Shader generation failed for {effectName}: {errorMessage}");
// Try fallback approach
return CreateFallbackEffect(effectName, parameters, errorMessage);
}
// Create shader asset
var shader = CreateShaderAsset(effectName, shaderCode);
if (shader == null)
{
SynLog.Warn($"[NexusShader] Shader asset creation failed for {effectName}");
return CreateFallbackEffect(effectName, parameters, "Shader asset creation failed");
}
// Validate shader compilation
if (!ValidateShaderCompilation(shader, effectName))
{
SynLog.Warn($"[NexusShader] Shader compilation failed for {effectName}");
return CreateFallbackEffect(effectName, parameters, "Shader compilation failed");
}
// Create material
var material = new Material(shader);
material.name = $"{effectName}_AutoGenerated";
// Set material properties from parameters
SetMaterialProperties(material, parameters);
// Validate material
if (!ValidateMaterial(material, effectName))
{
SynLog.Warn($"[NexusShader] Material validation failed for {effectName}");
return CreateFallbackEffect(effectName, parameters, "Material validation failed");
}
// Apply effect
var effectObject = applyEffect(material);
if (effectObject == null)
{
SynLog.Warn($"[NexusShader] Effect object creation failed for {effectName}");
return CreateFallbackEffect(effectName, parameters, "Effect object creation failed");
}
SynLog.Info($"[NexusShader] Successfully created {effectName} effect with auto-generated shader");
return $"[NexusShader] {effectName} effect created successfully!\n" +
$" Auto-Generated Shader: Created & Compiled\n" +
$" Pipeline: {DetectRenderingPipeline()}\n" +
$" Material: {material.name}\n" +
$" Effect Object: {effectObject.name}\n" +
$" Shader Validation: Passed\n" +
$" Material Validation: Passed\n" +
$" Performance: Optimized for current pipeline\n" +
$" Tip: Effect uses automatically generated shader with full error checking";
}
catch (Exception e)
{
Debug.LogError($"[NexusShader] Critical error in {effectName} creation: {e.Message}");
return CreateFallbackEffect(effectName, parameters, $"Critical error: {e.Message}");
}
}
private string CreateFallbackEffect(string effectName, Dictionary<string, string> parameters, string reason)
{
try
{
SynLog.Info($"[NexusShader] Creating fallback effect for {effectName} due to: {reason}");
// Create simple fallback using render pipeline compatible shader
Material fallbackMaterial = null;
var pipeline = DetectRenderingPipeline();
// Try Unlit shader first (most compatible for effects)
Shader unlitShader = null;
switch (pipeline)
{
case "URP":
unlitShader = Shader.Find("Universal Render Pipeline/Unlit");
if (unlitShader == null) unlitShader = Shader.Find("Shader Graphs/Unlit");
if (unlitShader == null) unlitShader = Shader.Find("Universal Render Pipeline/Lit");
break;
case "HDRP":
unlitShader = Shader.Find("HDRP/Unlit");
if (unlitShader == null) unlitShader = Shader.Find("HDRenderPipeline/Lit");
break;
default:
unlitShader = Shader.Find("Unlit/Transparent");
if (unlitShader == null) unlitShader = Shader.Find("Unlit/Color");
break;
}
// If specific shader not found, use render pipeline compatible shader
if (unlitShader == null)
{
unlitShader = GetRenderPipelineCompatibleShader();
}
if (unlitShader != null)
{
fallbackMaterial = new Material(unlitShader);
}
else
{
// Ultimate fallback - create using helper
fallbackMaterial = CreateRenderPipelineCompatibleMaterial();
}
fallbackMaterial.name = $"{effectName}_Fallback";
// Create basic effect object
var fallbackGO = new GameObject($"{effectName}Effect_Fallback");
var fallbackComponent = fallbackGO.AddComponent<FallbackEffectComponent>();
fallbackComponent.effectName = effectName;
fallbackComponent.fallbackMaterial = fallbackMaterial;
fallbackComponent.originalParameters = parameters;
fallbackComponent.fallbackReason = reason;
lastCreatedObject = fallbackGO;
Selection.activeGameObject = fallbackGO;
return $"[NexusShader] {effectName} fallback effect created!\n" +
$" Fallback Reason: {reason}\n" +
$" Pipeline: {pipeline}\n" +
$" Fallback Material: {fallbackMaterial.name}\n" +
$" Status: Basic functionality available\n" +
$" Note: Using simplified implementation due to shader generation issues\n" +
$" Tip: Check Unity console for detailed error information";
}
catch (Exception e)
{
return CreateErrorResponse($"CreateFallbackEffect{effectName}", e, parameters);
}
}
private bool ValidateShaderCompilation(Shader shader, string effectName)
{
try
{
if (shader == null)
{
Debug.LogError($"[NexusShader] Shader is null for {effectName}");
return false;
}
// Check if shader has any compilation errors
var shaderMessages = ShaderUtil.GetShaderMessages(shader);
if (shaderMessages != null && shaderMessages.Length > 0)
{
foreach (var message in shaderMessages)
{
if (message.severity == ShaderCompilerMessageSeverity.Error)
{
Debug.LogError($"[NexusShader] Shader compilation error in {effectName}: {message.message}");
return false;
}
else if (message.severity == ShaderCompilerMessageSeverity.Warning)
{
SynLog.Warn($"[NexusShader] Shader compilation warning in {effectName}: {message.message}");
}
}
}
// Check if shader is supported on current platform
if (!shader.isSupported)
{
Debug.LogError($"[NexusShader] Shader {effectName} is not supported on current platform");
return false;
}
return true;
}
catch (Exception e)
{
Debug.LogError($"[NexusShader] Error validating shader compilation for {effectName}: {e.Message}");
return false;
}
}
private bool ValidateMaterial(Material material, string effectName)
{
try
{
if (material == null)
{
Debug.LogError($"[NexusShader] Material is null for {effectName}");
return false;
}
if (material.shader == null)
{
Debug.LogError($"[NexusShader] Material shader is null for {effectName}");
return false;
}
// Try to access main texture to ensure material is properly initialized
var mainTex = material.mainTexture;
return true;
}
catch (Exception e)
{
Debug.LogError($"[NexusShader] Error validating material for {effectName}: {e.Message}");
return false;
}
}
private Shader CreateShaderAsset(string effectName, string shaderCode)
{
try
{
// Create shader folder if it doesn't exist
var shaderFolderPath = "Assets/NexusGeneratedShaders";
if (!AssetDatabase.IsValidFolder(shaderFolderPath))
{
if (!AssetDatabase.IsValidFolder("Assets"))
{
AssetDatabase.CreateFolder("", "Assets");
}
AssetDatabase.CreateFolder("Assets", "NexusGeneratedShaders");
}
// Write shader file
var shaderPath = $"{shaderFolderPath}/{effectName}_Generated.shader";
System.IO.File.WriteAllText(shaderPath, shaderCode);
// Import and get shader
AssetDatabase.ImportAsset(shaderPath);
AssetDatabase.Refresh();
var shader = AssetDatabase.LoadAssetAtPath<Shader>(shaderPath);
if (shader == null)
{
Debug.LogError($"[NexusShader] Failed to load generated shader at {shaderPath}");
return null;
}
SynLog.Info($"[NexusShader] Successfully created shader asset: {shaderPath}");
return shader;
}
catch (Exception e)
{
Debug.LogError($"[NexusShader] Failed to create shader asset: {e.Message}");
return null;
}
}
private void SetMaterialProperties(Material material, Dictionary<string, string> parameters)
{
foreach (var param in parameters)
{
try
{
var propertyName = $"_{param.Key.Replace("_", "").Replace("STRENGTH", "Strength").Replace("SIZE", "Size").Replace("INTENSITY", "Intensity").Replace("THRESHOLD", "Threshold").Replace("DISTANCE", "Distance").Replace("ABERRATION", "Aberration").Replace("VIGNETTE", "Vignette").Replace("APERTURE", "Aperture").Replace("COUNT", "Count")}";
if (material.HasProperty(propertyName))
{
if (float.TryParse(param.Value, out float floatValue))
{
material.SetFloat(propertyName, floatValue);
}
else if (int.TryParse(param.Value, out int intValue))
{
material.SetInt(propertyName, intValue);
}
}
}
catch (Exception e)
{
SynLog.Warn($"[NexusShader] Could not set material property {param.Key}: {e.Message}");
}
}
}
// ===== Restored Visual Effects with Auto-Generated Shaders =====
private string CreateBloomEffect(Dictionary<string, string> parameters)
{
var intensity = parameters.GetValueOrDefault("INTENSITY", "2.0");
var threshold = parameters.GetValueOrDefault("THRESHOLD", "0.9");
var blurSize = parameters.GetValueOrDefault("BLUR_SIZE", "3.0");
var shaderParams = new Dictionary<string, string>
{
{"INTENSITY", intensity},
{"THRESHOLD", threshold},
{"BLUR_SIZE", blurSize}
};
return CreateShaderEffectWithAutoGeneration("Bloom", shaderParams, (material) =>
{
var bloomGO = new GameObject("BloomEffect");
var bloomComponent = bloomGO.AddComponent<BloomEffectComponent>();
bloomComponent.bloomMaterial = material;
bloomComponent.intensity = float.Parse(intensity);
bloomComponent.threshold = float.Parse(threshold);
bloomComponent.blurSize = float.Parse(blurSize);
lastCreatedObject = bloomGO;
Selection.activeGameObject = bloomGO;
return bloomGO;
});
}
private string CreateFilmGrainEffect(Dictionary<string, string> parameters)
{
var intensity = parameters.GetValueOrDefault("INTENSITY", "0.3");
var grainSize = parameters.GetValueOrDefault("GRAIN_SIZE", "2.0");
var shaderParams = new Dictionary<string, string>
{
{"INTENSITY", intensity},
{"GRAIN_SIZE", grainSize}
};
return CreateShaderEffectWithAutoGeneration("FilmGrain", shaderParams, (material) =>
{
var grainGO = new GameObject("FilmGrainEffect");
var grainComponent = grainGO.AddComponent<FilmGrainEffectComponent>();
grainComponent.grainMaterial = material;
grainComponent.intensity = float.Parse(intensity);
grainComponent.grainSize = float.Parse(grainSize);
lastCreatedObject = grainGO;
Selection.activeGameObject = grainGO;
return grainGO;
});
}
private string CreateMotionBlurEffect(Dictionary<string, string> parameters)
{
var blurStrength = parameters.GetValueOrDefault("BLUR_STRENGTH", "0.5");
var sampleCount = parameters.GetValueOrDefault("SAMPLE_COUNT", "16");
var shaderParams = new Dictionary<string, string>
{
{"BLUR_STRENGTH", blurStrength},
{"SAMPLE_COUNT", sampleCount}
};
return CreateShaderEffectWithAutoGeneration("MotionBlur", shaderParams, (material) =>
{
var blurGO = new GameObject("MotionBlurEffect");
var blurComponent = blurGO.AddComponent<MotionBlurEffectComponent>();
blurComponent.blurMaterial = material;
blurComponent.blurStrength = float.Parse(blurStrength);
blurComponent.sampleCount = int.Parse(sampleCount);
lastCreatedObject = blurGO;
Selection.activeGameObject = blurGO;
return blurGO;
});
}
private string CreateDepthOfFieldEffect(Dictionary<string, string> parameters)
{
var focusDistance = parameters.GetValueOrDefault("FOCUS_DISTANCE", "10.0");
var blurSize = parameters.GetValueOrDefault("BLUR_SIZE", "2.0");
var aperture = parameters.GetValueOrDefault("APERTURE", "5.6");
var shaderParams = new Dictionary<string, string>
{
{"FOCUS_DISTANCE", focusDistance},
{"BLUR_SIZE", blurSize},
{"APERTURE", aperture}
};
return CreateShaderEffectWithAutoGeneration("DepthOfField", shaderParams, (material) =>
{
var dofGO = new GameObject("DepthOfFieldEffect");
var dofComponent = dofGO.AddComponent<DepthOfFieldEffectComponent>();
dofComponent.dofMaterial = material;
dofComponent.focusDistance = float.Parse(focusDistance);
dofComponent.blurSize = float.Parse(blurSize);
dofComponent.aperture = float.Parse(aperture);
lastCreatedObject = dofGO;
Selection.activeGameObject = dofGO;
return dofGO;
});
}
private string CreateLensDistortionEffect(Dictionary<string, string> parameters)
{
var distortionStrength = parameters.GetValueOrDefault("DISTORTION_STRENGTH", "0.2");
var chromaticAberration = parameters.GetValueOrDefault("CHROMATIC_ABERRATION", "0.1");
var vignette = parameters.GetValueOrDefault("VIGNETTE", "0.3");
var shaderParams = new Dictionary<string, string>
{
{"DISTORTION_STRENGTH", distortionStrength},
{"CHROMATIC_ABERRATION", chromaticAberration},
{"VIGNETTE", vignette}
};
return CreateShaderEffectWithAutoGeneration("LensDistortion", shaderParams, (material) =>
{
var lensGO = new GameObject("LensDistortionEffect");
var lensComponent = lensGO.AddComponent<LensDistortionEffectComponent>();
lensComponent.distortionMaterial = material;
lensComponent.distortionStrength = float.Parse(distortionStrength);
lensComponent.chromaticAberration = float.Parse(chromaticAberration);
lensComponent.vignette = float.Parse(vignette);
lastCreatedObject = lensGO;
Selection.activeGameObject = lensGO;
return lensGO;
});
}
// ===== Effect Component Classes =====
public class FallbackEffectComponent : MonoBehaviour
{
public string effectName;
public Material fallbackMaterial;
public Dictionary<string, string> originalParameters;
public string fallbackReason;
void Start()
{
SynLog.Warn($"[NexusShader] Using fallback effect for {effectName}: {fallbackReason}");
}
}
public class BloomEffectComponent : MonoBehaviour
{
public Material bloomMaterial;
public float intensity = 2.0f;
public float threshold = 0.9f;
public float blurSize = 3.0f;
void Start()
{
SynLog.Info($"[NexusShader] Bloom effect initialized with intensity: {intensity}, threshold: {threshold}");
}
}
public class FilmGrainEffectComponent : MonoBehaviour
{
public Material grainMaterial;
public float intensity = 0.3f;
public float grainSize = 2.0f;
void Start()
{
SynLog.Info($"[NexusShader] Film grain effect initialized with intensity: {intensity}, size: {grainSize}");
}
}
public class MotionBlurEffectComponent : MonoBehaviour
{
public Material blurMaterial;
public float blurStrength = 0.5f;
public int sampleCount = 16;
void Start()
{
SynLog.Info($"[NexusShader] Motion blur effect initialized with strength: {blurStrength}, samples: {sampleCount}");
}
}
public class DepthOfFieldEffectComponent : MonoBehaviour
{
public Material dofMaterial;
public float focusDistance = 10.0f;
public float blurSize = 2.0f;
public float aperture = 5.6f;
void Start()
{
SynLog.Info($"[NexusShader] Depth of field effect initialized with focus: {focusDistance}, aperture: {aperture}");
}
}
public class LensDistortionEffectComponent : MonoBehaviour
{
public Material distortionMaterial;
public float distortionStrength = 0.2f;
public float chromaticAberration = 0.1f;
public float vignette = 0.3f;
void Start()
{
SynLog.Info($"[NexusShader] Lens distortion effect initialized with distortion: {distortionStrength}, aberration: {chromaticAberration}");
}
}
// ===== Phase 4 Component Classes =====
public class HDRPSettingsController : MonoBehaviour
{
public string platform = "pc";
public string qualityLevel = "high";
public bool enableRayTracing = false;
public bool enableVolumetrics = true;
public string reflectionMode = "screenspace";
public int targetFrameRate = -1;
public float renderScale = 1.0f;
public string shadowQuality = "High";
public bool rayTracedReflections = false;
public bool rayTracedGI = false;
public bool rayTracedShadows = false;
public string volumetricFogQuality = "High";
public bool volumetricClouds = true;
public string reflectionQuality = "ScreenSpace";
}
public class PostProcessingController : MonoBehaviour
{
public string profileName = "cinematic";
public GameObject postProcessVolume;
public bool bloomEnabled = true;
public float bloomIntensity = 0.8f;
public bool colorGradingEnabled = true;
public float colorTemperature = 0f;
public float saturation = 0f;
public float contrast = 0f;
public bool vignetteEnabled = true;
public float vignetteIntensity = 0.4f;
public bool filmGrainEnabled = false;
public float filmGrainIntensity = 0.2f;
public bool motionBlurEnabled = false;
}
public class ShaderGraphController : MonoBehaviour
{
public string shaderType = "surface";
public string targetPipeline = "urp";
public List<string> features = new List<string>();
public bool animated = false;
public Material generatedMaterial;
}
public class LightingScenarioController : MonoBehaviour
{
public string scenario = "noon";
public float ambientIntensity = 1.0f;
public float shadowStrength = 1.0f;
public float colorTemperature = 6500f;
public bool fogEnabled = false;
public float fogDensity = 1.0f;
}
// ===== Phase 4: Advanced Rendering & VFX Systems =====
private string SetupURPSettings(Dictionary<string, string> parameters)
{
try
{
var platform = parameters.GetValueOrDefault("platform", "pc").ToLower();
var quality = parameters.GetValueOrDefault("quality", "medium").ToLower();
var shadows = parameters.GetValueOrDefault("shadows", "medium").ToLower();
var antiAliasing = parameters.GetValueOrDefault("antialiasing", "fxaa").ToLower();
var renderScale = float.Parse(parameters.GetValueOrDefault("renderscale", "1.0"));
var hdr = bool.Parse(parameters.GetValueOrDefault("hdr", "true"));
var msaa = parameters.GetValueOrDefault("msaa", "4x").ToLower();
SynLog.Info($"[NexusRendering] Setting up URP for {platform} platform with {quality} quality");
// Check if URP is available
var urpAsset = UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline;
if (urpAsset == null || !urpAsset.GetType().Name.Contains("Universal"))
{
return "[NexusRendering] URP not detected. Please install Universal Render Pipeline package first.";
}
// Platform-specific optimizations
var settings = new URPSettingsProfile();
switch (platform)
{
case "mobile":
case "android":
case "ios":
ApplyMobileURPSettings(settings, quality);
break;
case "console":
case "playstation":
case "xbox":
ApplyConsoleURPSettings(settings, quality);
break;
case "pc":
case "desktop":
default:
ApplyDesktopURPSettings(settings, quality);
break;
}
// Apply specific settings
ApplyURPQualitySettings(settings, shadows, antiAliasing, renderScale, hdr, msaa);
// Apply to Unity
ApplyURPSettingsToUnity(settings);
return $"[NexusRendering] URP Settings applied successfully!\n" +
$" Platform: {platform.ToUpper()}\n" +
$" Quality Level: {quality.ToUpper()}\n" +
$" Shadow Quality: {shadows.ToUpper()}\n" +
$" Anti-Aliasing: {antiAliasing.ToUpper()}\n" +
$" Render Scale: {renderScale}x\n" +
$" HDR: {(hdr ? "Enabled" : "Disabled")}\n" +
$" MSAA: {msaa.ToUpper()}\n" +
$" Optimizations: Platform-specific applied\n" +
$" Tip: Use natural language like 'mobile high quality' for quick setup";
}
catch (Exception e)
{
return CreateErrorResponse("SetupURPSettings", e, parameters);
}
}
private class URPSettingsProfile
{
public int shadowDistance = 50;
public int shadowCascades = 4;
public float shadowResolution = 2048;
public bool softShadows = true;
public int maxLights = 8;
public bool additionalLights = true;
public float renderScale = 1.0f;
public bool hdr = true;
public string antiAliasing = "FXAA";
public string msaa = "4x";
public bool depthTexture = true;
public bool opaqueTexture = false;
public int maxPixelLights = 4;
}
private void ApplyMobileURPSettings(URPSettingsProfile settings, string quality)
{
// Mobile optimizations
settings.maxLights = quality == "high" ? 4 : quality == "medium" ? 2 : 1;
settings.shadowDistance = quality == "high" ? 30 : quality == "medium" ? 20 : 10;
settings.shadowCascades = quality == "high" ? 2 : 1;
settings.shadowResolution = quality == "high" ? 1024 : quality == "medium" ? 512 : 256;
settings.softShadows = quality != "low";
settings.additionalLights = quality != "low";
settings.hdr = quality == "high";
settings.depthTexture = quality != "low";
settings.opaqueTexture = false; // Always false for mobile
settings.maxPixelLights = quality == "high" ? 2 : 1;
}
private void ApplyConsoleURPSettings(URPSettingsProfile settings, string quality)
{
// Console optimizations
settings.maxLights = quality == "high" ? 16 : quality == "medium" ? 8 : 4;
settings.shadowDistance = quality == "high" ? 100 : quality == "medium" ? 75 : 50;
settings.shadowCascades = 4;
settings.shadowResolution = quality == "high" ? 4096 : quality == "medium" ? 2048 : 1024;
settings.softShadows = true;
settings.additionalLights = true;
settings.hdr = true;
settings.depthTexture = true;
settings.opaqueTexture = quality == "high";
settings.maxPixelLights = quality == "high" ? 8 : quality == "medium" ? 4 : 2;
}
private void ApplyDesktopURPSettings(URPSettingsProfile settings, string quality)
{
// Desktop/PC optimizations
settings.maxLights = quality == "high" ? 32 : quality == "medium" ? 16 : 8;
settings.shadowDistance = quality == "high" ? 150 : quality == "medium" ? 100 : 75;
settings.shadowCascades = 4;
settings.shadowResolution = quality == "high" ? 4096 : quality == "medium" ? 2048 : 1024;
settings.softShadows = true;
settings.additionalLights = true;
settings.hdr = true;
settings.depthTexture = true;
settings.opaqueTexture = quality != "low";
settings.maxPixelLights = quality == "high" ? 16 : quality == "medium" ? 8 : 4;
}
private void ApplyURPQualitySettings(URPSettingsProfile settings, string shadows, string antiAliasing, float renderScale, bool hdr, string msaa)
{
// Shadow settings
switch (shadows.ToLower())
{
case "off":
case "disabled":
settings.shadowDistance = 0;
settings.softShadows = false;
break;
case "low":
settings.shadowDistance = Mathf.Min(settings.shadowDistance, 25);
settings.shadowResolution = 512;
settings.softShadows = false;
break;
case "medium":
settings.shadowDistance = Mathf.Min(settings.shadowDistance, 75);
settings.shadowResolution = 1024;
settings.softShadows = true;
break;
case "high":
case "ultra":
settings.shadowDistance = Mathf.Max(settings.shadowDistance, 100);
settings.shadowResolution = 2048;
settings.softShadows = true;
break;
}
// Anti-aliasing
settings.antiAliasing = antiAliasing.ToUpper();
settings.msaa = msaa;
// Other settings
settings.renderScale = renderScale;
settings.hdr = hdr;
}
private void ApplyURPSettingsToUnity(URPSettingsProfile settings)
{
try
{
// Apply shadow settings
QualitySettings.shadowDistance = settings.shadowDistance;
QualitySettings.shadowResolution = settings.shadowResolution >= 2048 ? ShadowResolution.VeryHigh :
settings.shadowResolution >= 1024 ? ShadowResolution.High :
settings.shadowResolution >= 512 ? ShadowResolution.Medium : ShadowResolution.Low;
QualitySettings.shadows = settings.shadowDistance > 0 ? ShadowQuality.All : ShadowQuality.Disable;
// Apply MSAA
int msaaValue = settings.msaa switch
{
"2x" => 2,
"4x" => 4,
"8x" => 8,
_ => 0
};
QualitySettings.antiAliasing = msaaValue;
// Apply pixel light count
QualitySettings.pixelLightCount = settings.maxPixelLights;
SynLog.Info($"[NexusRendering] Applied URP settings: Shadows={settings.shadowDistance}m, MSAA={settings.msaa}, Lights={settings.maxLights}");
}
catch (Exception e)
{
Debug.LogError($"[NexusRendering] Failed to apply URP settings: {e.Message}");
}
}
private string SetupHDRPSettings(Dictionary<string, string> parameters)
{
try
{
var platform = parameters.GetValueOrDefault("platform", "pc");
var quality = parameters.GetValueOrDefault("quality", "high");
var raytracing = bool.Parse(parameters.GetValueOrDefault("raytracing", "false"));
var volumetrics = bool.Parse(parameters.GetValueOrDefault("volumetrics", "true"));
var reflections = parameters.GetValueOrDefault("reflections", "screenspace");
var hdrpController = new GameObject("HDRPSettingsController").AddComponent<HDRPSettingsController>();
hdrpController.platform = platform;
hdrpController.qualityLevel = quality;
hdrpController.enableRayTracing = raytracing;
hdrpController.enableVolumetrics = volumetrics;
hdrpController.reflectionMode = reflections;
// Apply platform-specific optimizations
ApplyHDRPQualitySettings(hdrpController, platform, quality);
// Configure ray tracing if enabled
if (raytracing)
{
ConfigureRayTracing(hdrpController);
}
// Setup volumetric lighting
if (volumetrics)
{
ConfigureVolumetrics(hdrpController);
}
// Configure reflection settings
ConfigureReflections(hdrpController, reflections);
return $"[NexusRendering] HDRP configured for {platform} with {quality} quality. Ray tracing: {raytracing}, Volumetrics: {volumetrics}";
}
catch (Exception e)
{
return CreateErrorResponse("SetupHDRPSettings", e, parameters);
}
}
private void ApplyHDRPQualitySettings(HDRPSettingsController controller, string platform, string quality)
{
// Configure based on platform and quality
switch (platform.ToLower())
{
case "console":
case "playstation":
case "xbox":
ApplyConsoleHDRPSettings(controller, quality);
break;
case "pc":
case "desktop":
ApplyDesktopHDRPSettings(controller, quality);
break;
}
}
private void ApplyConsoleHDRPSettings(HDRPSettingsController controller, string quality)
{
// Console-specific optimizations
controller.targetFrameRate = quality == "ultra" ? 60 : 30;
controller.renderScale = quality == "ultra" ? 1.0f : 0.9f;
controller.shadowQuality = quality == "ultra" ? "High" : "Medium";
}
private void ApplyDesktopHDRPSettings(HDRPSettingsController controller, string quality)
{
// Desktop PC optimizations
controller.targetFrameRate = -1; // Uncapped
controller.renderScale = 1.0f;
controller.shadowQuality = quality == "ultra" ? "Ultra" : "High";
}
private void ConfigureRayTracing(HDRPSettingsController controller)
{
controller.rayTracedReflections = true;
controller.rayTracedGI = true;
controller.rayTracedShadows = true;
}
private void ConfigureVolumetrics(HDRPSettingsController controller)
{
controller.volumetricFogQuality = "High";
controller.volumetricClouds = true;
}
private void ConfigureReflections(HDRPSettingsController controller, string mode)
{
controller.reflectionQuality = mode == "raytraced" ? "RayTraced" : "ScreenSpace";
}
private string SetupPostProcessing(Dictionary<string, string> parameters)
{
try
{
var profile = parameters.GetValueOrDefault("profile", "cinematic");
var bloom = bool.Parse(parameters.GetValueOrDefault("bloom", "true"));
var colorgrading = bool.Parse(parameters.GetValueOrDefault("colorgrading", "true"));
var vignette = bool.Parse(parameters.GetValueOrDefault("vignette", "true"));
var filmgrain = bool.Parse(parameters.GetValueOrDefault("filmgrain", "false"));
var motionblur = bool.Parse(parameters.GetValueOrDefault("motionblur", "false"));
var ppController = new GameObject("PostProcessingController").AddComponent<PostProcessingController>();
ppController.profileName = profile;
// Apply preset profile
switch (profile.ToLower())
{
case "cinematic":
ApplyCinematicProfile(ppController);
break;
case "realistic":
ApplyRealisticProfile(ppController);
break;
case "stylized":
ApplyStylizedProfile(ppController);
break;
case "mobile":
ApplyMobileProfile(ppController);
break;
case "custom":
// Use individual settings
ppController.bloomEnabled = bloom;
ppController.colorGradingEnabled = colorgrading;
ppController.vignetteEnabled = vignette;
ppController.filmGrainEnabled = filmgrain;
ppController.motionBlurEnabled = motionblur;
break;
}
// Create post-process volume
CreatePostProcessVolume(ppController);
var effects = new List<string>();
if (ppController.bloomEnabled) effects.Add("Bloom");
if (ppController.colorGradingEnabled) effects.Add("Color Grading");
if (ppController.vignetteEnabled) effects.Add("Vignette");
if (ppController.filmGrainEnabled) effects.Add("Film Grain");
if (ppController.motionBlurEnabled) effects.Add("Motion Blur");
return $"[NexusRendering] Post-Processing configured with {profile} profile. Active effects: {string.Join(", ", effects)}";
}
catch (Exception e)
{
return CreateErrorResponse("SetupPostProcessing", e, parameters);
}
}
private void ApplyCinematicProfile(PostProcessingController controller)
{
controller.bloomEnabled = true;
controller.bloomIntensity = 0.8f;
controller.colorGradingEnabled = true;
controller.colorTemperature = -5f;
controller.vignetteEnabled = true;
controller.vignetteIntensity = 0.4f;
controller.filmGrainEnabled = true;
controller.filmGrainIntensity = 0.2f;
}
private void ApplyRealisticProfile(PostProcessingController controller)
{
controller.bloomEnabled = true;
controller.bloomIntensity = 0.3f;
controller.colorGradingEnabled = true;
controller.colorTemperature = 0f;
controller.vignetteEnabled = false;
controller.filmGrainEnabled = false;
}
private void ApplyStylizedProfile(PostProcessingController controller)
{
controller.bloomEnabled = true;
controller.bloomIntensity = 1.5f;
controller.colorGradingEnabled = true;
controller.saturation = 20f;
controller.contrast = 15f;
controller.vignetteEnabled = true;
controller.vignetteIntensity = 0.5f;
}
private void ApplyMobileProfile(PostProcessingController controller)
{
controller.bloomEnabled = true;
controller.bloomIntensity = 0.5f;
controller.colorGradingEnabled = true;
controller.vignetteEnabled = false;
controller.filmGrainEnabled = false;
controller.motionBlurEnabled = false;
}
private void CreatePostProcessVolume(PostProcessingController controller)
{
var volume = new GameObject("PostProcessVolume");
volume.transform.SetParent(controller.transform);
controller.postProcessVolume = volume;
// Add collider for local volumes
var col = volume.AddComponent<BoxCollider>();
col.isTrigger = true;
col.size = new Vector3(100, 100, 100);
}
private string CreateVFXGraph(Dictionary<string, string> parameters)
{
try
{
var effectType = parameters.GetValueOrDefault("effectType", "fire").ToLower();
var effectName = parameters.GetValueOrDefault("name", $"VFX_{effectType}");
var intensity = float.Parse(parameters.GetValueOrDefault("intensity", "1.0"));
var duration = float.Parse(parameters.GetValueOrDefault("duration", "5.0"));
var looping = bool.Parse(parameters.GetValueOrDefault("looping", "true"));
var particleCount = int.Parse(parameters.GetValueOrDefault("particleCount", "1000"));
var color = parameters.GetValueOrDefault("color", "");
var endColor = parameters.GetValueOrDefault("endColor", "");
var positionStr = parameters.GetValueOrDefault("position", "0,0,0");
// 拡張パラメータ
var shapeType = parameters.GetValueOrDefault("shapeType", ""); // mesh, sdf, spline, point_cache
var meshPath = parameters.GetValueOrDefault("meshPath", ""); // メッシュアセットパス
var meshName = parameters.GetValueOrDefault("meshName", ""); // シーン内メッシュ名
var splinePoints = parameters.GetValueOrDefault("splinePoints", ""); // "x,y,z;x,y,z;..."
var sdfShape = parameters.GetValueOrDefault("sdfShape", "sphere"); // sphere, box, torus, custom
var outputMode = parameters.GetValueOrDefault("outputMode", "particle"); // particle, mesh, decal, line
var blendMode = parameters.GetValueOrDefault("blendMode", "additive"); // additive, alpha, opaque
// Parse position
var posParts = positionStr.Split(',');
var position = new Vector3(
float.Parse(posParts.Length > 0 ? posParts[0] : "0"),
float.Parse(posParts.Length > 1 ? posParts[1] : "0"),
float.Parse(posParts.Length > 2 ? posParts[2] : "0")
);
// VFXパラメータをまとめる
var vfxParams = new VFXGraphParameters
{
effectName = effectName,
effectType = effectType,
intensity = intensity,
duration = duration,
looping = looping,
particleCount = particleCount,
color = color,
endColor = endColor,
position = position,
shapeType = shapeType,
meshPath = meshPath,
meshName = meshName,
splinePoints = splinePoints,
sdfShape = sdfShape,
outputMode = outputMode,
blendMode = blendMode
};
#if VFX_GRAPH_PACKAGE
// VFX Graph パッケージがインストールされている場合
return CreateVFXGraphWithPackage(vfxParams);
#else
// VFX Graph パッケージがない場合は Particle System で代替
return CreateVFXWithParticleSystem(effectName, effectType, intensity, duration, looping, particleCount, color, position);
#endif
}
catch (Exception e)
{
return CreateErrorResponse("CreateVFXGraph", e, parameters);
}
}
// VFX Graph用パラメータクラス
private class VFXGraphParameters
{
public string effectName;
public string effectType;
public float intensity;
public float duration;
public bool looping;
public int particleCount;
public string color;
public string endColor;
public Vector3 position;
public string shapeType;
public string meshPath;
public string meshName;
public string splinePoints;
public string sdfShape;
public string outputMode;
public string blendMode;
}
#if VFX_GRAPH_PACKAGE
private string CreateVFXGraphWithPackage(VFXGraphParameters p)
{
try
{
// Use NexusVFXBuilder (reflection-based) to avoid internal type access issues
string folderPath = "Assets/VFX";
// Map effectType to preset
string preset = p.effectType.ToLower() switch
{
"fire" => "fire",
"smoke" => "smoke",
"sparks" => "sparks",
"trail" or "cyber" => "trail",
"explosion" => "explosion",
_ => "fire" // default
};
// Create VFX using NexusVFXBuilder preset system
var result = NexusVFXBuilder.CreatePreset(p.effectName, preset, folderPath);
if (result.StartsWith("Error"))
{
SynLog.Warn($"[NexusVFX] VFX Graph creation failed: {result}");
return CreateVFXWithParticleSystem(p.effectName, p.effectType, p.intensity, p.duration, p.looping, p.particleCount, p.color, p.position);
}
string assetPath = $"{folderPath}/{p.effectName}.vfx";
var vfxAsset = AssetDatabase.LoadAssetAtPath<VisualEffectAsset>(assetPath);
if (vfxAsset != null)
{
// GameObjectを作成してVisualEffectコンポーネントを追加
var go = new GameObject(p.effectName);
go.transform.position = p.position;
var vfx = go.AddComponent<VisualEffect>();
vfx.visualEffectAsset = vfxAsset;
// Exposed Parametersを設定
ConfigureVFXExposedParameters(vfx, p);
// Undoに登録
UnityEditor.Undo.RegisterCreatedObjectUndo(go, $"Create VFX Graph {p.effectName}");
Selection.activeGameObject = go;
}
return $"[NexusVFX] Created VFX Graph: {p.effectName}\n" +
$" Type: {p.effectType}\n" +
$" Asset: {assetPath}\n" +
$" Particles: {p.particleCount}\n" +
$" Position: {p.position}";
}
catch (Exception e)
{
SynLog.Warn($"[NexusVFX] VFX Graph creation failed: {e.Message}\n{e.StackTrace}");
// フォールバック
return CreateVFXWithParticleSystem(p.effectName, p.effectType, p.intensity, p.duration, p.looping, p.particleCount, p.color, p.position);
}
}
private void ConfigureVFXExposedParameters(VisualEffect vfx, VFXGraphParameters p)
{
// Exposed Parametersを設定
if (vfx.HasFloat("Intensity")) vfx.SetFloat("Intensity", p.intensity);
if (vfx.HasInt("ParticleCount")) vfx.SetInt("ParticleCount", p.particleCount);
// 色の設定
if (!string.IsNullOrEmpty(p.color) && vfx.HasVector4("Color"))
{
if (ColorUtility.TryParseHtmlString(p.color, out Color parsedColor))
{
vfx.SetVector4("Color", new Vector4(parsedColor.r, parsedColor.g, parsedColor.b, parsedColor.a));
}
}
}
#endif
// VFX Property Operations (works with both VFX Graph and Particle System)
private string SetVFXProperty(Dictionary<string, string> parameters)
{
try
{
var gameObjectName = parameters.GetValueOrDefault("gameObjectName", "");
var propertyName = parameters.GetValueOrDefault("propertyName", "");
var propertyType = parameters.GetValueOrDefault("propertyType", "float");
var value = parameters.GetValueOrDefault("value", "");
if (string.IsNullOrEmpty(gameObjectName))
return "Error: gameObjectName is required";
var go = FindGameObjectByNameOrId(gameObjectName);
if (go == null)
return $"Error: GameObject '{gameObjectName}' not found";
#if VFX_GRAPH_PACKAGE
// VFX Graphの場合
var vfx = go.GetComponent<VisualEffect>();
if (vfx != null)
{
return SetVFXGraphProperty(vfx, propertyName, propertyType, value);
}
#endif
// Particle Systemの場合
var ps = go.GetComponent<ParticleSystem>();
if (ps != null)
{
return SetParticleSystemProperty(ps, propertyName, propertyType, value);
}
return $"Error: No VisualEffect or ParticleSystem found on '{gameObjectName}'";
}
catch (Exception e)
{
return CreateErrorResponse("SetVFXProperty", e, parameters);
}
}
#if VFX_GRAPH_PACKAGE
private string SetVFXGraphProperty(VisualEffect vfx, string propertyName, string propertyType, string value)
{
switch (propertyType.ToLower())
{
case "float":
if (vfx.HasFloat(propertyName))
{
vfx.SetFloat(propertyName, float.Parse(value));
return $"Set {propertyName} = {value}";
}
break;
case "int":
if (vfx.HasInt(propertyName))
{
vfx.SetInt(propertyName, int.Parse(value));
return $"Set {propertyName} = {value}";
}
break;
case "bool":
if (vfx.HasBool(propertyName))
{
vfx.SetBool(propertyName, bool.Parse(value));
return $"Set {propertyName} = {value}";
}
break;
case "vector2":
if (vfx.HasVector2(propertyName))
{
var parts2 = value.Split(',');
vfx.SetVector2(propertyName, new Vector2(float.Parse(parts2[0]), float.Parse(parts2[1])));
return $"Set {propertyName} = {value}";
}
break;
case "vector3":
if (vfx.HasVector3(propertyName))
{
var parts3 = value.Split(',');
vfx.SetVector3(propertyName, new Vector3(float.Parse(parts3[0]), float.Parse(parts3[1]), float.Parse(parts3[2])));
return $"Set {propertyName} = {value}";
}
break;
case "vector4":
case "color":
if (vfx.HasVector4(propertyName))
{
Vector4 vec4;
if (value.StartsWith("#") || !value.Contains(","))
{
if (ColorUtility.TryParseHtmlString(value, out Color col))
{
vec4 = new Vector4(col.r, col.g, col.b, col.a);
}
else
{
return $"Error: Invalid color format '{value}'";
}
}
else
{
var parts4 = value.Split(',');
vec4 = new Vector4(
float.Parse(parts4[0]),
float.Parse(parts4[1]),
float.Parse(parts4[2]),
parts4.Length > 3 ? float.Parse(parts4[3]) : 1f
);
}
vfx.SetVector4(propertyName, vec4);
return $"Set {propertyName} = {value}";
}
break;
case "texture":
if (vfx.HasTexture(propertyName))
{
var tex = AssetDatabase.LoadAssetAtPath<Texture>(value);
if (tex != null)
{
vfx.SetTexture(propertyName, tex);
return $"Set {propertyName} = {value}";
}
return $"Error: Texture not found at '{value}'";
}
break;
case "mesh":
if (vfx.HasMesh(propertyName))
{
var mesh = AssetDatabase.LoadAssetAtPath<Mesh>(value);
if (mesh != null)
{
vfx.SetMesh(propertyName, mesh);
return $"Set {propertyName} = {value}";
}
return $"Error: Mesh not found at '{value}'";
}
break;
}
return $"Error: Property '{propertyName}' of type '{propertyType}' not found";
}
#endif
private string SetParticleSystemProperty(ParticleSystem ps, string propertyName, string propertyType, string value)
{
var main = ps.main;
var emission = ps.emission;
switch (propertyName.ToLower())
{
case "startcolor":
case "color":
if (ColorUtility.TryParseHtmlString(value, out Color col))
{
main.startColor = col;
return $"Set startColor = {value}";
}
break;
case "startsize":
case "size":
main.startSize = float.Parse(value);
return $"Set startSize = {value}";
case "startspeed":
case "speed":
main.startSpeed = float.Parse(value);
return $"Set startSpeed = {value}";
case "startlifetime":
case "lifetime":
main.startLifetime = float.Parse(value);
return $"Set startLifetime = {value}";
case "maxparticles":
case "particlecount":
main.maxParticles = int.Parse(value);
return $"Set maxParticles = {value}";
case "emissionrate":
case "rate":
emission.rateOverTime = float.Parse(value);
return $"Set emissionRate = {value}";
case "gravitymodifier":
case "gravity":
main.gravityModifier = float.Parse(value);
return $"Set gravityModifier = {value}";
case "loop":
case "looping":
main.loop = bool.Parse(value);
return $"Set loop = {value}";
case "duration":
main.duration = float.Parse(value);
return $"Set duration = {value}";
}
return $"Error: Property '{propertyName}' not supported for ParticleSystem";
}
private string GetVFXProperties(Dictionary<string, string> parameters)
{
try
{
var gameObjectName = parameters.GetValueOrDefault("gameObjectName", "");
if (string.IsNullOrEmpty(gameObjectName))
return "Error: gameObjectName is required";
var go = FindGameObjectByNameOrId(gameObjectName);
if (go == null)
return $"Error: GameObject '{gameObjectName}' not found";
var sb = new StringBuilder();
sb.AppendLine($"VFX Properties for '{gameObjectName}':");
#if VFX_GRAPH_PACKAGE
var vfx = go.GetComponent<VisualEffect>();
if (vfx != null && vfx.visualEffectAsset != null)
{
sb.AppendLine("\n[VFX Graph Exposed Properties]");
// Use reflection to get exposed properties (API varies by Unity version)
var vfxAsset = vfx.visualEffectAsset;
var assetType = vfxAsset.GetType();
// Try to get exposed properties via reflection
var exposedCountProp = assetType.GetProperty("exposedPropertyCount");
if (exposedCountProp != null)
{
int count = (int)exposedCountProp.GetValue(vfxAsset);
var getNameMethod = assetType.GetMethod("GetExposedPropertyName");
var getTypeMethod = assetType.GetMethod("GetExposedPropertyType");
for (int i = 0; i < count; i++)
{
var name = getNameMethod?.Invoke(vfxAsset, new object[] { i })?.ToString() ?? $"Property{i}";
var type = getTypeMethod?.Invoke(vfxAsset, new object[] { i })?.ToString() ?? "Unknown";
string currentValue = "N/A";
if (vfx.HasFloat(name)) currentValue = vfx.GetFloat(name).ToString("F3");
else if (vfx.HasInt(name)) currentValue = vfx.GetInt(name).ToString();
else if (vfx.HasBool(name)) currentValue = vfx.GetBool(name).ToString();
else if (vfx.HasVector2(name)) currentValue = vfx.GetVector2(name).ToString();
else if (vfx.HasVector3(name)) currentValue = vfx.GetVector3(name).ToString();
else if (vfx.HasVector4(name)) currentValue = vfx.GetVector4(name).ToString();
sb.AppendLine($" {name} ({type}): {currentValue}");
}
}
else
{
// Fallback: check common property names
string[] commonProps = { "Intensity", "Color", "ParticleCount", "Size", "Speed", "Lifetime" };
foreach (var propName in commonProps)
{
if (vfx.HasFloat(propName))
sb.AppendLine($" {propName} (float): {vfx.GetFloat(propName):F3}");
else if (vfx.HasInt(propName))
sb.AppendLine($" {propName} (int): {vfx.GetInt(propName)}");
else if (vfx.HasVector4(propName))
sb.AppendLine($" {propName} (Vector4): {vfx.GetVector4(propName)}");
}
}
return sb.ToString();
}
#endif
var ps = go.GetComponent<ParticleSystem>();
if (ps != null)
{
var main = ps.main;
var emission = ps.emission;
sb.AppendLine("\n[Particle System Properties]");
sb.AppendLine($" duration: {main.duration}");
sb.AppendLine($" loop: {main.loop}");
sb.AppendLine($" startLifetime: {main.startLifetime.constant}");
sb.AppendLine($" startSpeed: {main.startSpeed.constant}");
sb.AppendLine($" startSize: {main.startSize.constant}");
sb.AppendLine($" startColor: {main.startColor.color}");
sb.AppendLine($" gravityModifier: {main.gravityModifier.constant}");
sb.AppendLine($" maxParticles: {main.maxParticles}");
sb.AppendLine($" emissionRate: {emission.rateOverTime.constant}");
return sb.ToString();
}
return $"Error: No VisualEffect or ParticleSystem found on '{gameObjectName}'";
}
catch (Exception e)
{
return CreateErrorResponse("GetVFXProperties", e, parameters);
}
}
private string TriggerVFXEvent(Dictionary<string, string> parameters)
{
try
{
var gameObjectName = parameters.GetValueOrDefault("gameObjectName", "");
var eventName = parameters.GetValueOrDefault("eventName", "OnPlay");
if (string.IsNullOrEmpty(gameObjectName))
return "Error: gameObjectName is required";
var go = FindGameObjectByNameOrId(gameObjectName);
if (go == null)
return $"Error: GameObject '{gameObjectName}' not found";
#if VFX_GRAPH_PACKAGE
var vfx = go.GetComponent<VisualEffect>();
if (vfx != null)
{
switch (eventName.ToLower())
{
case "onplay":
case "play":
vfx.Play();
return $"Triggered Play on '{gameObjectName}'";
case "onstop":
case "stop":
vfx.Stop();
return $"Triggered Stop on '{gameObjectName}'";
case "reinit":
vfx.Reinit();
return $"Triggered Reinit on '{gameObjectName}'";
default:
vfx.SendEvent(eventName);
return $"Sent event '{eventName}' to '{gameObjectName}'";
}
}
#endif
var ps = go.GetComponent<ParticleSystem>();
if (ps != null)
{
switch (eventName.ToLower())
{
case "onplay":
case "play":
ps.Play();
return $"Triggered Play on '{gameObjectName}'";
case "onstop":
case "stop":
ps.Stop();
return $"Triggered Stop on '{gameObjectName}'";
case "pause":
ps.Pause();
return $"Triggered Pause on '{gameObjectName}'";
case "clear":
ps.Clear();
return $"Cleared particles on '{gameObjectName}'";
default:
return $"Error: Unknown event '{eventName}' for ParticleSystem";
}
}
return $"Error: No VisualEffect or ParticleSystem found on '{gameObjectName}'";
}
catch (Exception e)
{
return CreateErrorResponse("TriggerVFXEvent", e, parameters);
}
}
// ==================== VFX Graph Builder API ====================
private string VFXCreate(Dictionary<string, string> parameters)
{
var name = parameters.GetValueOrDefault("name", "NewVFX");
var folder = parameters.GetValueOrDefault("folder", "Assets/VFX");
return NexusVFXBuilder.CreateVFXGraph(name, folder);
}
private string VFXAddContext(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
var contextType = parameters.GetValueOrDefault("contextType", "");
if (string.IsNullOrEmpty(vfxPath) || string.IsNullOrEmpty(contextType))
return "Error: vfxPath and contextType are required";
// Parse additional settings
var settings = new Dictionary<string, object>();
foreach (var kvp in parameters)
{
if (kvp.Key != "vfxPath" && kvp.Key != "contextType")
{
settings[kvp.Key] = ParseValue(kvp.Value);
}
}
return NexusVFXBuilder.AddContext(vfxPath, contextType, settings.Count > 0 ? settings : null);
}
private string VFXAddBlock(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
var contextIndexStr = parameters.GetValueOrDefault("contextIndex", "0");
var blockType = parameters.GetValueOrDefault("blockType", "");
if (string.IsNullOrEmpty(vfxPath) || string.IsNullOrEmpty(blockType))
return "Error: vfxPath and blockType are required";
if (!int.TryParse(contextIndexStr, out int contextIndex))
return "Error: contextIndex must be a number";
// Parse additional settings
var settings = new Dictionary<string, object>();
foreach (var kvp in parameters)
{
if (kvp.Key != "vfxPath" && kvp.Key != "contextIndex" && kvp.Key != "blockType")
{
settings[kvp.Key] = ParseValue(kvp.Value);
}
}
return NexusVFXBuilder.AddBlock(vfxPath, contextIndex, blockType, settings.Count > 0 ? settings : null);
}
private string VFXAddOperator(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
var operatorType = parameters.GetValueOrDefault("operatorType", "");
if (string.IsNullOrEmpty(vfxPath) || string.IsNullOrEmpty(operatorType))
return "Error: vfxPath and operatorType are required";
// Parse additional settings
var settings = new Dictionary<string, object>();
foreach (var kvp in parameters)
{
if (kvp.Key != "vfxPath" && kvp.Key != "operatorType")
{
settings[kvp.Key] = ParseValue(kvp.Value);
}
}
return NexusVFXBuilder.AddOperator(vfxPath, operatorType, settings.Count > 0 ? settings : null);
}
private string VFXLinkContexts(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
var fromIndexStr = parameters.GetValueOrDefault("fromIndex", "0");
var toIndexStr = parameters.GetValueOrDefault("toIndex", "1");
if (string.IsNullOrEmpty(vfxPath))
return "Error: vfxPath is required";
if (!int.TryParse(fromIndexStr, out int fromIndex) || !int.TryParse(toIndexStr, out int toIndex))
return "Error: fromIndex and toIndex must be numbers";
return NexusVFXBuilder.LinkContexts(vfxPath, fromIndex, toIndex);
}
private string VFXGetStructure(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
if (string.IsNullOrEmpty(vfxPath))
return "Error: vfxPath is required";
return NexusVFXBuilder.GetStructure(vfxPath);
}
private string VFXCompile(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
if (string.IsNullOrEmpty(vfxPath))
return "Error: vfxPath is required";
return NexusVFXBuilder.CompileVFX(vfxPath);
}
private string VFXGetAvailableTypes(Dictionary<string, string> parameters)
{
var typeCategory = parameters.GetValueOrDefault("category", "all").ToLower();
var sb = new StringBuilder();
sb.AppendLine("Available VFX Graph Types:");
sb.AppendLine("=".PadRight(50, '='));
if (typeCategory == "all" || typeCategory == "context" || typeCategory == "contexts")
{
sb.AppendLine("\nCONTEXTS:");
sb.AppendLine(NexusVFXBuilder.GetAvailableContexts());
}
if (typeCategory == "all" || typeCategory == "block" || typeCategory == "blocks")
{
sb.AppendLine("\nBLOCKS:");
sb.AppendLine(NexusVFXBuilder.GetAvailableBlocks());
}
if (typeCategory == "all" || typeCategory == "operator" || typeCategory == "operators")
{
sb.AppendLine("\nOPERATORS:");
sb.AppendLine(NexusVFXBuilder.GetAvailableOperators());
}
return sb.ToString();
}
private string VFXAddParameter(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
var paramName = parameters.GetValueOrDefault("name", "");
var paramType = parameters.GetValueOrDefault("type", "float");
var defaultValue = parameters.GetValueOrDefault("defaultValue", null);
var exposedStr = parameters.GetValueOrDefault("exposed", "true");
if (string.IsNullOrEmpty(vfxPath) || string.IsNullOrEmpty(paramName))
return "Error: vfxPath and name are required";
bool.TryParse(exposedStr, out bool exposed);
exposed = true; // Default to true
object defVal = defaultValue != null ? ParseValue(defaultValue) : null;
return NexusVFXBuilder.AddParameter(vfxPath, paramName, paramType, defVal, exposed);
}
private string VFXConnectSlots(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
var sourceNodeIndexStr = parameters.GetValueOrDefault("sourceNodeIndex", "0");
var sourceSlotIndexStr = parameters.GetValueOrDefault("sourceSlotIndex", "0");
var targetNodeIndexStr = parameters.GetValueOrDefault("targetNodeIndex", "0");
var targetSlotIndexStr = parameters.GetValueOrDefault("targetSlotIndex", "0");
var sourceType = parameters.GetValueOrDefault("sourceType", "operator");
var targetType = parameters.GetValueOrDefault("targetType", "block");
if (string.IsNullOrEmpty(vfxPath))
return "Error: vfxPath is required";
if (!int.TryParse(sourceNodeIndexStr, out int sourceNodeIndex) ||
!int.TryParse(sourceSlotIndexStr, out int sourceSlotIndex) ||
!int.TryParse(targetNodeIndexStr, out int targetNodeIndex) ||
!int.TryParse(targetSlotIndexStr, out int targetSlotIndex))
{
return "Error: Node and slot indices must be numbers";
}
return NexusVFXBuilder.ConnectSlots(vfxPath, sourceNodeIndex, sourceSlotIndex,
targetNodeIndex, targetSlotIndex, sourceType, targetType);
}
private string VFXSetAttribute(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
var contextIndexStr = parameters.GetValueOrDefault("contextIndex", "0");
var blockIndexStr = parameters.GetValueOrDefault("blockIndex", "0");
var attributeName = parameters.GetValueOrDefault("attribute", "");
var value = parameters.GetValueOrDefault("value", "");
var compositionMode = parameters.GetValueOrDefault("composition", "overwrite");
if (string.IsNullOrEmpty(vfxPath) || string.IsNullOrEmpty(attributeName))
return "Error: vfxPath and attribute are required";
if (!int.TryParse(contextIndexStr, out int contextIndex) ||
!int.TryParse(blockIndexStr, out int blockIndex))
{
return "Error: contextIndex and blockIndex must be numbers";
}
return NexusVFXBuilder.SetBlockAttribute(vfxPath, contextIndex, blockIndex,
attributeName, value, compositionMode);
}
private string VFXCreatePreset(Dictionary<string, string> parameters)
{
var name = parameters.GetValueOrDefault("name", "NewVFX");
var presetType = parameters.GetValueOrDefault("preset", "fire");
var folder = parameters.GetValueOrDefault("folder", "Assets/VFX");
return NexusVFXBuilder.CreatePreset(name, presetType, folder);
}
private string VFXConfigureOutput(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
var contextIndexStr = parameters.GetValueOrDefault("contextIndex", "0");
if (string.IsNullOrEmpty(vfxPath))
return "Error: vfxPath is required";
if (!int.TryParse(contextIndexStr, out int contextIndex))
return "Error: contextIndex must be a number";
// Parse settings
var settings = new Dictionary<string, object>();
var excludeKeys = new[] { "vfxPath", "contextIndex", "operationId" };
foreach (var kvp in parameters)
{
if (!excludeKeys.Contains(kvp.Key))
{
settings[kvp.Key] = kvp.Value;
}
}
return NexusVFXBuilder.ConfigureOutput(vfxPath, contextIndex, settings);
}
private string VFXSetOutput(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("path", parameters.GetValueOrDefault("vfxPath", ""));
var contextIndexStr = parameters.GetValueOrDefault("contextIndex", "3"); // Output is usually index 3
if (string.IsNullOrEmpty(vfxPath))
return JsonConvert.SerializeObject(new { success = false, error = "path is required" });
if (!int.TryParse(contextIndexStr, out int contextIndex))
return JsonConvert.SerializeObject(new { success = false, error = "contextIndex must be a number" });
// Parse settings from remaining parameters
var settings = new Dictionary<string, object>();
var excludeKeys = new[] { "path", "vfxPath", "contextIndex", "operationId" };
foreach (var kvp in parameters)
{
if (!excludeKeys.Contains(kvp.Key))
{
settings[kvp.Key] = kvp.Value;
}
}
return NexusVFXBuilder.SetOutputSettings(vfxPath, contextIndex, settings);
}
private string VFXSetBlockValue(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("path", parameters.GetValueOrDefault("vfxPath", ""));
var contextIndexStr = parameters.GetValueOrDefault("contextIndex", "1");
var blockIndexStr = parameters.GetValueOrDefault("blockIndex", "0");
var propertyName = parameters.GetValueOrDefault("property", parameters.GetValueOrDefault("propertyName", "value"));
var value = parameters.GetValueOrDefault("value", "");
if (string.IsNullOrEmpty(vfxPath))
return JsonConvert.SerializeObject(new { success = false, error = "path is required" });
if (!int.TryParse(contextIndexStr, out int contextIndex))
return JsonConvert.SerializeObject(new { success = false, error = "contextIndex must be a number" });
if (!int.TryParse(blockIndexStr, out int blockIndex))
return JsonConvert.SerializeObject(new { success = false, error = "blockIndex must be a number" });
return NexusVFXBuilder.SetBlockValue(vfxPath, contextIndex, blockIndex, propertyName, value);
}
private string VFXSetSpawnRate(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("path", parameters.GetValueOrDefault("vfxPath", ""));
var rateStr = parameters.GetValueOrDefault("rate", parameters.GetValueOrDefault("spawnRate", "100"));
if (string.IsNullOrEmpty(vfxPath))
return JsonConvert.SerializeObject(new { success = false, error = "path is required" });
if (!float.TryParse(rateStr, out float rate))
return JsonConvert.SerializeObject(new { success = false, error = "rate must be a number" });
return NexusVFXBuilder.SetSpawnRate(vfxPath, rate);
}
private string VFXListBlocks(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("path", parameters.GetValueOrDefault("vfxPath", ""));
if (string.IsNullOrEmpty(vfxPath))
return JsonConvert.SerializeObject(new { success = false, error = "path is required" });
return NexusVFXBuilder.ListBlocks(vfxPath);
}
private string VFXRemoveBlock(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("path", parameters.GetValueOrDefault("vfxPath", ""));
var contextIndexStr = parameters.GetValueOrDefault("contextIndex", "0");
var blockIndexStr = parameters.GetValueOrDefault("blockIndex", "0");
if (string.IsNullOrEmpty(vfxPath))
return JsonConvert.SerializeObject(new { success = false, error = "path is required" });
if (!int.TryParse(contextIndexStr, out int contextIndex))
return JsonConvert.SerializeObject(new { success = false, error = "contextIndex must be a number" });
if (!int.TryParse(blockIndexStr, out int blockIndex))
return JsonConvert.SerializeObject(new { success = false, error = "blockIndex must be a number" });
return NexusVFXBuilder.RemoveBlock(vfxPath, contextIndex, blockIndex);
}
private string VFXGetBlockInfo(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("path", parameters.GetValueOrDefault("vfxPath", ""));
var contextIndexStr = parameters.GetValueOrDefault("contextIndex", "0");
var blockIndexStr = parameters.GetValueOrDefault("blockIndex", "0");
if (string.IsNullOrEmpty(vfxPath))
return JsonConvert.SerializeObject(new { success = false, error = "path is required" });
if (!int.TryParse(contextIndexStr, out int contextIndex))
return JsonConvert.SerializeObject(new { success = false, error = "contextIndex must be a number" });
if (!int.TryParse(blockIndexStr, out int blockIndex))
return JsonConvert.SerializeObject(new { success = false, error = "blockIndex must be a number" });
return NexusVFXBuilder.GetBlockInfo(vfxPath, contextIndex, blockIndex);
}
private string VFXSetColorGradient(Dictionary<string, string> parameters)
{
var vfxPath = parameters.GetValueOrDefault("vfxPath", "");
var contextIndexStr = parameters.GetValueOrDefault("contextIndex", "-1");
var blockIndexStr = parameters.GetValueOrDefault("blockIndex", "-1");
var colorsStr = parameters.GetValueOrDefault("colors", "");
var timesStr = parameters.GetValueOrDefault("times", "");
if (string.IsNullOrEmpty(vfxPath))
return "Error: vfxPath is required";
if (!int.TryParse(contextIndexStr, out int contextIndex))
return "Error: contextIndex must be a number";
if (!int.TryParse(blockIndexStr, out int blockIndex))
return "Error: blockIndex must be a number";
// Parse colors (comma or JSON array)
string[] colors;
if (colorsStr.StartsWith("["))
{
try
{
colors = JsonConvert.DeserializeObject<string[]>(colorsStr);
}
catch
{
colors = colorsStr.Trim('[', ']').Split(',').Select(s => s.Trim().Trim('"')).ToArray();
}
}
else
{
colors = colorsStr.Split(',').Select(s => s.Trim()).ToArray();
}
// Parse times (optional)
float[] times = null;
if (!string.IsNullOrEmpty(timesStr))
{
if (timesStr.StartsWith("["))
{
try
{
times = JsonConvert.DeserializeObject<float[]>(timesStr);
}
catch
{
times = timesStr.Trim('[', ']').Split(',').Select(s => float.Parse(s.Trim())).ToArray();
}
}
else
{
times = timesStr.Split(',').Select(s => float.Parse(s.Trim())).ToArray();
}
}
return NexusVFXBuilder.SetColorGradient(vfxPath, contextIndex, blockIndex, colors, times);
}
private object ParseValue(string value)
{
if (string.IsNullOrEmpty(value))
return value;
// JSON object or array - return as string (will be parsed later if needed)
if ((value.StartsWith("{") && value.EndsWith("}")) ||
(value.StartsWith("[") && value.EndsWith("]")))
{
// Try to parse as dictionary for nested values
try
{
var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(value);
if (dict != null)
return dict;
}
catch
{
// Not valid JSON dict, return as string
}
return value;
}
// Try parse as number (but not if it looks like a coordinate string)
if (!value.Contains(","))
{
if (float.TryParse(value, out float f))
return f;
if (int.TryParse(value, out int i))
return i;
}
if (bool.TryParse(value, out bool b))
return b;
// Vector format: "x,y,z" - only if all parts are numeric
if (value.Contains(",") && !value.Contains("{") && !value.Contains("["))
{
var parts = value.Split(',');
bool allNumeric = parts.All(p => float.TryParse(p.Trim(), out _));
if (allNumeric)
{
try
{
if (parts.Length == 2)
return new Vector2(float.Parse(parts[0].Trim()), float.Parse(parts[1].Trim()));
if (parts.Length == 3)
return new Vector3(float.Parse(parts[0].Trim()), float.Parse(parts[1].Trim()), float.Parse(parts[2].Trim()));
if (parts.Length == 4)
return new Vector4(float.Parse(parts[0].Trim()), float.Parse(parts[1].Trim()), float.Parse(parts[2].Trim()), float.Parse(parts[3].Trim()));
}
catch
{
// Parsing failed, return as string
}
}
}
// Color format: "#RRGGBB"
if (value.StartsWith("#") && ColorUtility.TryParseHtmlString(value, out Color color))
return color;
return value;
}
/// <summary>
/// VFX Graph パッケージがない場合の代替実装(Particle Systemを使用)
/// </summary>
private string CreateVFXWithParticleSystem(string effectName, string effectType, float intensity, float duration, bool looping, int particleCount, string color, Vector3 position)
{
// GameObjectを作成
var go = new GameObject(effectName);
go.transform.position = position;
// Particle Systemを追加
var ps = go.AddComponent<ParticleSystem>();
var main = ps.main;
var emission = ps.emission;
var shape = ps.shape;
var colorOverLifetime = ps.colorOverLifetime;
var sizeOverLifetime = ps.sizeOverLifetime;
var velocityOverLifetime = ps.velocityOverLifetime;
var noise = ps.noise;
var renderer = go.GetComponent<ParticleSystemRenderer>();
// 基本設定
main.loop = looping;
main.duration = duration;
main.maxParticles = particleCount;
// エフェクトタイプに応じた設定
VFXPreset preset = GetVFXPreset(effectType, intensity);
// Main Module
main.startLifetime = new ParticleSystem.MinMaxCurve(preset.lifetimeMin, preset.lifetimeMax);
main.startSpeed = new ParticleSystem.MinMaxCurve(preset.speedMin * intensity, preset.speedMax * intensity);
main.startSize = new ParticleSystem.MinMaxCurve(preset.sizeMin, preset.sizeMax);
main.gravityModifier = preset.gravity;
main.simulationSpace = preset.worldSpace ? ParticleSystemSimulationSpace.World : ParticleSystemSimulationSpace.Local;
// Emission
emission.rateOverTime = preset.emissionRate * intensity;
if (preset.burst)
{
emission.rateOverTime = 0;
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, (short)(particleCount * 0.8f)) });
}
// Shape
shape.shapeType = preset.shapeType;
shape.angle = preset.shapeAngle;
shape.radius = preset.shapeRadius;
// Color over Lifetime
if (preset.colorGradient != null)
{
colorOverLifetime.enabled = true;
colorOverLifetime.color = preset.colorGradient;
}
// カスタム色が指定されている場合
if (!string.IsNullOrEmpty(color))
{
if (ColorUtility.TryParseHtmlString(color, out Color parsedColor))
{
main.startColor = parsedColor;
}
}
else
{
main.startColor = preset.startColor;
}
// Size over Lifetime
if (preset.sizeOverLifetimeCurve != null)
{
sizeOverLifetime.enabled = true;
sizeOverLifetime.size = new ParticleSystem.MinMaxCurve(1f, preset.sizeOverLifetimeCurve);
}
// Noise (Turbulence)
if (preset.turbulence > 0)
{
noise.enabled = true;
noise.strength = preset.turbulence;
noise.frequency = 0.5f;
noise.scrollSpeed = 1f;
}
// Renderer設定
renderer.renderMode = preset.renderMode;
// デフォルトマテリアルを設定
var defaultMaterial = GetOrCreateParticleMaterial(effectType);
if (defaultMaterial != null)
{
renderer.material = defaultMaterial;
}
// Trail(エネルギー系エフェクト用)
if (preset.useTrails)
{
var trails = ps.trails;
trails.enabled = true;
trails.lifetime = new ParticleSystem.MinMaxCurve(0.1f, 0.3f);
trails.widthOverTrail = new ParticleSystem.MinMaxCurve(1f, AnimationCurve.Linear(0, 1, 1, 0));
}
// Collision(スパーク用)
if (preset.useCollision)
{
var collision = ps.collision;
collision.enabled = true;
collision.type = ParticleSystemCollisionType.World;
collision.bounce = 0.3f;
collision.lifetimeLoss = 0.2f;
}
// Sub Emitter(爆発用)
if (preset.useSubEmitter && effectType == "explosion")
{
CreateExplosionSubEmitters(go);
}
// Undoに登録
UnityEditor.Undo.RegisterCreatedObjectUndo(go, $"Create VFX {effectName}");
Selection.activeGameObject = go;
string packageNote = "";
#if !VFX_GRAPH_PACKAGE
packageNote = "\n Note: Install 'Visual Effect Graph' package for advanced GPU particles";
#endif
return $"[NexusVFX] Created Particle System VFX: {effectName}\n" +
$" Type: {effectType}\n" +
$" Particles: {particleCount}\n" +
$" Intensity: {intensity}\n" +
$" Looping: {looping}\n" +
$" Duration: {duration}s\n" +
$" Position: {position}{packageNote}";
}
private class VFXPreset
{
public float lifetimeMin = 1f, lifetimeMax = 2f;
public float speedMin = 1f, speedMax = 5f;
public float sizeMin = 0.1f, sizeMax = 0.5f;
public float gravity = 0f;
public float emissionRate = 100f;
public bool burst = false;
public bool worldSpace = true;
public ParticleSystemShapeType shapeType = ParticleSystemShapeType.Cone;
public float shapeAngle = 25f;
public float shapeRadius = 0.5f;
public Gradient colorGradient;
public Color startColor = Color.white;
public AnimationCurve sizeOverLifetimeCurve;
public float turbulence = 0f;
public ParticleSystemRenderMode renderMode = ParticleSystemRenderMode.Billboard;
public bool useTrails = false;
public bool useCollision = false;
public bool useSubEmitter = false;
}
private VFXPreset GetVFXPreset(string effectType, float intensity)
{
var preset = new VFXPreset();
switch (effectType)
{
case "fire":
preset.lifetimeMin = 0.5f; preset.lifetimeMax = 1.5f;
preset.speedMin = 2f; preset.speedMax = 5f;
preset.sizeMin = 0.2f; preset.sizeMax = 0.8f;
preset.gravity = -0.5f; // 炎は上に昇る
preset.emissionRate = 100f;
preset.shapeType = ParticleSystemShapeType.Cone;
preset.shapeAngle = 15f;
preset.turbulence = 2f;
preset.colorGradient = CreateFireGradient();
preset.startColor = new Color(1f, 0.6f, 0.2f);
preset.sizeOverLifetimeCurve = AnimationCurve.Linear(0, 0.5f, 1, 1.5f);
break;
case "smoke":
preset.lifetimeMin = 3f; preset.lifetimeMax = 5f;
preset.speedMin = 0.5f; preset.speedMax = 2f;
preset.sizeMin = 0.5f; preset.sizeMax = 2f;
preset.gravity = -0.2f;
preset.emissionRate = 30f;
preset.shapeType = ParticleSystemShapeType.Cone;
preset.shapeAngle = 30f;
preset.turbulence = 1.5f;
preset.colorGradient = CreateSmokeGradient();
preset.startColor = new Color(0.3f, 0.3f, 0.35f);
preset.sizeOverLifetimeCurve = AnimationCurve.EaseInOut(0, 0.3f, 1, 1f);
break;
case "explosion":
preset.lifetimeMin = 0.3f; preset.lifetimeMax = 1f;
preset.speedMin = 10f; preset.speedMax = 25f;
preset.sizeMin = 0.3f; preset.sizeMax = 1.5f;
preset.gravity = 0.5f;
preset.burst = true;
preset.shapeType = ParticleSystemShapeType.Sphere;
preset.shapeRadius = 0.1f;
preset.colorGradient = CreateExplosionGradient();
preset.startColor = new Color(1f, 0.8f, 0.3f);
preset.sizeOverLifetimeCurve = AnimationCurve.EaseInOut(0, 1f, 1, 0.1f);
preset.useSubEmitter = true;
break;
case "magic":
preset.lifetimeMin = 1f; preset.lifetimeMax = 2f;
preset.speedMin = 1f; preset.speedMax = 3f;
preset.sizeMin = 0.05f; preset.sizeMax = 0.2f;
preset.gravity = 0f;
preset.emissionRate = 150f;
preset.shapeType = ParticleSystemShapeType.Circle;
preset.shapeRadius = 1f;
preset.colorGradient = CreateMagicGradient();
preset.startColor = new Color(0.5f, 0.3f, 1f);
preset.useTrails = true;
break;
case "water":
case "splash":
preset.lifetimeMin = 0.5f; preset.lifetimeMax = 1.5f;
preset.speedMin = 3f; preset.speedMax = 8f;
preset.sizeMin = 0.05f; preset.sizeMax = 0.15f;
preset.gravity = 2f;
preset.emissionRate = 200f;
preset.shapeType = ParticleSystemShapeType.Hemisphere;
preset.shapeRadius = 0.3f;
preset.colorGradient = CreateWaterGradient();
preset.startColor = new Color(0.4f, 0.7f, 1f, 0.8f);
preset.renderMode = ParticleSystemRenderMode.Stretch;
break;
case "energy":
preset.lifetimeMin = 0.2f; preset.lifetimeMax = 0.5f;
preset.speedMin = 5f; preset.speedMax = 15f;
preset.sizeMin = 0.02f; preset.sizeMax = 0.1f;
preset.gravity = 0f;
preset.emissionRate = 300f;
preset.shapeType = ParticleSystemShapeType.Sphere;
preset.shapeRadius = 0.5f;
preset.colorGradient = CreateEnergyGradient();
preset.startColor = new Color(0.3f, 0.8f, 1f);
preset.useTrails = true;
break;
case "sparks":
preset.lifetimeMin = 0.1f; preset.lifetimeMax = 0.5f;
preset.speedMin = 5f; preset.speedMax = 15f;
preset.sizeMin = 0.02f; preset.sizeMax = 0.08f;
preset.gravity = 3f;
preset.emissionRate = 50f;
preset.burst = true;
preset.shapeType = ParticleSystemShapeType.Cone;
preset.shapeAngle = 45f;
preset.colorGradient = CreateSparksGradient();
preset.startColor = new Color(1f, 0.9f, 0.5f);
preset.useCollision = true;
preset.renderMode = ParticleSystemRenderMode.Stretch;
break;
case "dust":
preset.lifetimeMin = 2f; preset.lifetimeMax = 4f;
preset.speedMin = 0.1f; preset.speedMax = 0.5f;
preset.sizeMin = 0.02f; preset.sizeMax = 0.1f;
preset.gravity = 0.1f;
preset.emissionRate = 20f;
preset.shapeType = ParticleSystemShapeType.Box;
preset.turbulence = 0.5f;
preset.startColor = new Color(0.7f, 0.65f, 0.6f, 0.5f);
break;
case "snow":
preset.lifetimeMin = 5f; preset.lifetimeMax = 10f;
preset.speedMin = 0.5f; preset.speedMax = 1.5f;
preset.sizeMin = 0.02f; preset.sizeMax = 0.08f;
preset.gravity = 0.3f;
preset.emissionRate = 100f;
preset.shapeType = ParticleSystemShapeType.Box;
preset.turbulence = 0.3f;
preset.startColor = Color.white;
break;
case "rain":
preset.lifetimeMin = 0.5f; preset.lifetimeMax = 1f;
preset.speedMin = 15f; preset.speedMax = 25f;
preset.sizeMin = 0.01f; preset.sizeMax = 0.03f;
preset.gravity = 0f; // 速度で制御
preset.emissionRate = 500f;
preset.shapeType = ParticleSystemShapeType.Box;
preset.startColor = new Color(0.7f, 0.8f, 0.9f, 0.6f);
preset.renderMode = ParticleSystemRenderMode.Stretch;
break;
case "leaves":
preset.lifetimeMin = 3f; preset.lifetimeMax = 6f;
preset.speedMin = 0.5f; preset.speedMax = 2f;
preset.sizeMin = 0.1f; preset.sizeMax = 0.3f;
preset.gravity = 0.5f;
preset.emissionRate = 10f;
preset.shapeType = ParticleSystemShapeType.Box;
preset.turbulence = 1f;
preset.startColor = new Color(0.4f, 0.6f, 0.2f);
preset.renderMode = ParticleSystemRenderMode.Billboard;
break;
case "confetti":
preset.lifetimeMin = 3f; preset.lifetimeMax = 5f;
preset.speedMin = 5f; preset.speedMax = 10f;
preset.sizeMin = 0.05f; preset.sizeMax = 0.15f;
preset.gravity = 1f;
preset.burst = true;
preset.shapeType = ParticleSystemShapeType.Cone;
preset.shapeAngle = 30f;
preset.turbulence = 0.5f;
preset.colorGradient = CreateConfettiGradient();
break;
case "trail":
// サイバートレイル / モーショントレイル
preset.lifetimeMin = 0.3f; preset.lifetimeMax = 0.8f;
preset.speedMin = 0.5f; preset.speedMax = 2f;
preset.sizeMin = 0.02f; preset.sizeMax = 0.1f;
preset.gravity = 0f;
preset.emissionRate = 200f;
preset.shapeType = ParticleSystemShapeType.Sphere;
preset.shapeRadius = 0.1f;
preset.useTrails = true;
preset.colorGradient = CreateCyberGradient();
preset.startColor = new Color(0f, 1f, 1f); // シアン
preset.renderMode = ParticleSystemRenderMode.Stretch;
break;
case "cyber":
// サイバーパンク風エフェクト(グリッチ、ネオン)
preset.lifetimeMin = 0.1f; preset.lifetimeMax = 0.4f;
preset.speedMin = 3f; preset.speedMax = 8f;
preset.sizeMin = 0.01f; preset.sizeMax = 0.05f;
preset.gravity = 0f;
preset.emissionRate = 500f;
preset.shapeType = ParticleSystemShapeType.Box;
preset.useTrails = true;
preset.colorGradient = CreateCyberGradient();
preset.startColor = new Color(0f, 1f, 1f);
preset.renderMode = ParticleSystemRenderMode.Stretch;
break;
case "heal":
// 回復エフェクト(上昇する緑の光)
preset.lifetimeMin = 1f; preset.lifetimeMax = 2f;
preset.speedMin = 1f; preset.speedMax = 3f;
preset.sizeMin = 0.05f; preset.sizeMax = 0.2f;
preset.gravity = -0.5f; // 上昇
preset.emissionRate = 50f;
preset.shapeType = ParticleSystemShapeType.Circle;
preset.shapeRadius = 0.5f;
preset.colorGradient = CreateHealGradient();
preset.startColor = new Color(0.3f, 1f, 0.5f);
break;
case "buff":
// バフエフェクト(金色のオーラ)
preset.lifetimeMin = 0.5f; preset.lifetimeMax = 1.5f;
preset.speedMin = 0.5f; preset.speedMax = 2f;
preset.sizeMin = 0.05f; preset.sizeMax = 0.15f;
preset.gravity = -0.3f;
preset.emissionRate = 80f;
preset.shapeType = ParticleSystemShapeType.Circle;
preset.shapeRadius = 0.8f;
preset.colorGradient = CreateBuffGradient();
preset.startColor = new Color(1f, 0.85f, 0.3f);
break;
case "debuff":
// デバフエフェクト(紫の毒々しい)
preset.lifetimeMin = 1f; preset.lifetimeMax = 2f;
preset.speedMin = 0.3f; preset.speedMax = 1f;
preset.sizeMin = 0.1f; preset.sizeMax = 0.3f;
preset.gravity = 0.1f; // ゆっくり落下
preset.emissionRate = 30f;
preset.shapeType = ParticleSystemShapeType.Sphere;
preset.shapeRadius = 0.5f;
preset.turbulence = 0.8f;
preset.colorGradient = CreateDebuffGradient();
preset.startColor = new Color(0.5f, 0.1f, 0.6f);
break;
case "portal":
// ポータルエフェクト(渦巻き)
preset.lifetimeMin = 0.5f; preset.lifetimeMax = 1.5f;
preset.speedMin = 2f; preset.speedMax = 5f;
preset.sizeMin = 0.03f; preset.sizeMax = 0.1f;
preset.gravity = 0f;
preset.emissionRate = 200f;
preset.shapeType = ParticleSystemShapeType.Circle;
preset.shapeRadius = 1f;
preset.useTrails = true;
preset.colorGradient = CreatePortalGradient();
preset.startColor = new Color(0.4f, 0.2f, 1f);
break;
default:
// カスタム/デフォルト
break;
}
return preset;
}
// 追加のグラデーション生成ヘルパー
private Gradient CreateCyberGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0f, 1f, 1f), 0f), // シアン
new GradientColorKey(new Color(1f, 0f, 1f), 0.5f), // マゼンタ
new GradientColorKey(new Color(0f, 0.5f, 1f), 1f) // 青
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(0.8f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateHealGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0.8f, 1f, 0.8f), 0f),
new GradientColorKey(new Color(0.3f, 1f, 0.5f), 0.5f),
new GradientColorKey(new Color(0.1f, 0.8f, 0.3f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(0.8f, 0f),
new GradientAlphaKey(0.6f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateBuffGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(Color.white, 0f),
new GradientColorKey(new Color(1f, 0.9f, 0.4f), 0.3f),
new GradientColorKey(new Color(1f, 0.7f, 0.2f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(0.7f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateDebuffGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0.6f, 0.2f, 0.7f), 0f),
new GradientColorKey(new Color(0.4f, 0.1f, 0.5f), 0.5f),
new GradientColorKey(new Color(0.2f, 0.05f, 0.3f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(0.7f, 0f),
new GradientAlphaKey(0.5f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreatePortalGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0.8f, 0.5f, 1f), 0f),
new GradientColorKey(new Color(0.4f, 0.2f, 1f), 0.5f),
new GradientColorKey(new Color(0.1f, 0.05f, 0.5f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(0.8f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
// グラデーション生成ヘルパー
private Gradient CreateFireGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(1f, 1f, 0.8f), 0f),
new GradientColorKey(new Color(1f, 0.6f, 0.1f), 0.3f),
new GradientColorKey(new Color(0.8f, 0.2f, 0f), 0.7f),
new GradientColorKey(new Color(0.2f, 0.1f, 0.1f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(0.8f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateSmokeGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0.4f, 0.4f, 0.45f), 0f),
new GradientColorKey(new Color(0.3f, 0.3f, 0.35f), 0.5f),
new GradientColorKey(new Color(0.2f, 0.2f, 0.25f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(0.6f, 0f),
new GradientAlphaKey(0.4f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateExplosionGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(Color.white, 0f),
new GradientColorKey(new Color(1f, 0.9f, 0.3f), 0.1f),
new GradientColorKey(new Color(1f, 0.5f, 0f), 0.3f),
new GradientColorKey(new Color(0.3f, 0.1f, 0f), 0.7f),
new GradientColorKey(new Color(0.1f, 0.1f, 0.1f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(0.8f, 0.3f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateMagicGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0.8f, 0.5f, 1f), 0f),
new GradientColorKey(new Color(0.4f, 0.2f, 0.8f), 0.5f),
new GradientColorKey(new Color(0.2f, 0.1f, 0.5f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(0.7f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateWaterGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(0.6f, 0.85f, 1f), 0f),
new GradientColorKey(new Color(0.3f, 0.6f, 0.9f), 0.5f),
new GradientColorKey(new Color(0.2f, 0.4f, 0.7f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(0.8f, 0f),
new GradientAlphaKey(0.5f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateEnergyGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(Color.white, 0f),
new GradientColorKey(new Color(0.3f, 0.8f, 1f), 0.3f),
new GradientColorKey(new Color(0.1f, 0.4f, 0.8f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(0.6f, 0.5f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateSparksGradient()
{
var gradient = new Gradient();
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(Color.white, 0f),
new GradientColorKey(new Color(1f, 0.9f, 0.5f), 0.2f),
new GradientColorKey(new Color(1f, 0.5f, 0.1f), 0.7f),
new GradientColorKey(new Color(0.3f, 0.1f, 0f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(0.8f, 0.3f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private Gradient CreateConfettiGradient()
{
var gradient = new Gradient();
// ランダムな鮮やかな色
gradient.SetKeys(
new GradientColorKey[] {
new GradientColorKey(new Color(1f, 0.2f, 0.3f), 0f),
new GradientColorKey(new Color(0.2f, 0.8f, 0.3f), 0.25f),
new GradientColorKey(new Color(0.2f, 0.4f, 1f), 0.5f),
new GradientColorKey(new Color(1f, 0.8f, 0.2f), 0.75f),
new GradientColorKey(new Color(0.8f, 0.2f, 0.8f), 1f)
},
new GradientAlphaKey[] {
new GradientAlphaKey(1f, 0f),
new GradientAlphaKey(1f, 0.8f),
new GradientAlphaKey(0f, 1f)
}
);
return gradient;
}
private void CreateExplosionSubEmitters(GameObject parent)
{
// 煙のサブエミッター
var smokeGo = new GameObject("Smoke_SubEmitter");
smokeGo.transform.SetParent(parent.transform);
smokeGo.transform.localPosition = Vector3.zero;
var smokePs = smokeGo.AddComponent<ParticleSystem>();
var smokeMain = smokePs.main;
smokeMain.loop = false;
smokeMain.duration = 2f;
smokeMain.startLifetime = new ParticleSystem.MinMaxCurve(2f, 4f);
smokeMain.startSpeed = new ParticleSystem.MinMaxCurve(1f, 3f);
smokeMain.startSize = new ParticleSystem.MinMaxCurve(1f, 3f);
smokeMain.startColor = new Color(0.2f, 0.2f, 0.2f, 0.5f);
smokeMain.gravityModifier = -0.1f;
var smokeEmission = smokePs.emission;
smokeEmission.rateOverTime = 0;
smokeEmission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0.1f, 20) });
var smokeNoise = smokePs.noise;
smokeNoise.enabled = true;
smokeNoise.strength = 1f;
// 親のサブエミッターとして登録
var parentPs = parent.GetComponent<ParticleSystem>();
var subEmitters = parentPs.subEmitters;
subEmitters.enabled = true;
subEmitters.AddSubEmitter(smokePs, ParticleSystemSubEmitterType.Death, ParticleSystemSubEmitterProperties.InheritNothing);
}
private Material GetOrCreateParticleMaterial(string effectType)
{
// レンダリングパイプラインを検出
string pipeline = DetectRenderingPipeline();
Shader shader = null;
bool isAdditive = effectType == "fire" || effectType == "energy" || effectType == "magic" || effectType == "sparks" || effectType == "explosion";
// パイプラインに応じたシェーダーを選択
switch (pipeline)
{
case "URP":
// URP用シェーダー(優先順位順にフォールバック)
string[] urpShaders = isAdditive
? new[] {
"Universal Render Pipeline/Particles/Unlit",
"Universal Render Pipeline/Particles/Simple Lit",
"Particles/Standard Unlit",
"Unlit/Transparent"
}
: new[] {
"Universal Render Pipeline/Particles/Lit",
"Universal Render Pipeline/Particles/Simple Lit",
"Universal Render Pipeline/Particles/Unlit",
"Particles/Standard Surface",
"Particles/Standard Unlit"
};
foreach (var name in urpShaders)
{
shader = Shader.Find(name);
if (shader != null) break;
}
break;
case "HDRP":
// HDRP用シェーダー
string[] hdrpShaders = isAdditive
? new[] {
"HDRP/Unlit",
"Shader Graphs/HDRP Particles Unlit",
"Particles/Standard Unlit"
}
: new[] {
"HDRP/Lit",
"Shader Graphs/HDRP Particles Lit",
"Particles/Standard Surface"
};
foreach (var name in hdrpShaders)
{
shader = Shader.Find(name);
if (shader != null) break;
}
break;
default: // Built-in
// Built-in用シェーダー
string[] builtinShaders = isAdditive
? new[] {
"Particles/Standard Unlit",
"Legacy Shaders/Particles/Additive",
"Particles/Additive",
"Mobile/Particles/Additive"
}
: new[] {
"Particles/Standard Surface",
"Particles/Standard Unlit",
"Legacy Shaders/Particles/Alpha Blended",
"Particles/Alpha Blended"
};
foreach (var name in builtinShaders)
{
shader = Shader.Find(name);
if (shader != null) break;
}
break;
}
// 最終フォールバック
if (shader == null)
{
SynLog.Warn($"[NexusVFX] No suitable particle shader found for {pipeline}. Using fallback.");
shader = Shader.Find("Unlit/Color");
if (shader == null) shader = Shader.Find("Standard");
}
if (shader == null)
{
Debug.LogError("[NexusVFX] Failed to find any shader for particles!");
return null;
}
var mat = new Material(shader);
mat.name = $"VFX_{effectType}_Material";
// シェーダープロパティを設定
ConfigureParticleMaterial(mat, pipeline, effectType, isAdditive);
return mat;
}
private void ConfigureParticleMaterial(Material mat, string pipeline, string effectType, bool isAdditive)
{
// パイプライン共通のカラー設定
Color baseColor = GetVFXBaseColor(effectType);
switch (pipeline)
{
case "URP":
// URP Particles shader properties
if (mat.HasProperty("_BaseColor"))
mat.SetColor("_BaseColor", baseColor);
if (mat.HasProperty("_Surface"))
mat.SetFloat("_Surface", 1); // Transparent
if (mat.HasProperty("_Blend"))
mat.SetFloat("_Blend", isAdditive ? 1 : 0); // Additive or Alpha
if (mat.HasProperty("_AlphaClip"))
mat.SetFloat("_AlphaClip", 0);
if (mat.HasProperty("_SoftParticlesEnabled"))
mat.SetFloat("_SoftParticlesEnabled", 1);
if (mat.HasProperty("_CameraFadingEnabled"))
mat.SetFloat("_CameraFadingEnabled", 1);
// Render Queue
mat.renderQueue = isAdditive ? 3100 : 3000;
// キーワード設定
mat.EnableKeyword("_SURFACE_TYPE_TRANSPARENT");
if (isAdditive)
mat.EnableKeyword("_ALPHABLEND_ON");
break;
case "HDRP":
// HDRP shader properties
if (mat.HasProperty("_BaseColor"))
mat.SetColor("_BaseColor", baseColor);
if (mat.HasProperty("_UnlitColor"))
mat.SetColor("_UnlitColor", baseColor);
if (mat.HasProperty("_SurfaceType"))
mat.SetFloat("_SurfaceType", 1); // Transparent
if (mat.HasProperty("_BlendMode"))
mat.SetFloat("_BlendMode", isAdditive ? 1 : 0);
mat.renderQueue = isAdditive ? 3100 : 3000;
break;
default: // Built-in
// Built-in shader properties
if (mat.HasProperty("_Color"))
mat.SetColor("_Color", baseColor);
if (mat.HasProperty("_TintColor"))
mat.SetColor("_TintColor", baseColor);
// Legacy Particles shaders
if (mat.shader.name.Contains("Additive") || mat.shader.name.Contains("Alpha"))
{
mat.renderQueue = isAdditive ? 3100 : 3000;
}
// Standard Particles
if (mat.HasProperty("_Mode"))
{
mat.SetFloat("_Mode", isAdditive ? 4 : 2); // Additive or Fade
}
break;
}
// デフォルトテクスチャを設定(白い円形グラデーション)
Texture2D defaultParticleTex = CreateDefaultParticleTexture(effectType);
if (defaultParticleTex != null)
{
if (mat.HasProperty("_BaseMap"))
mat.SetTexture("_BaseMap", defaultParticleTex);
if (mat.HasProperty("_MainTex"))
mat.SetTexture("_MainTex", defaultParticleTex);
}
}
private Color GetVFXBaseColor(string effectType)
{
switch (effectType)
{
case "fire": return new Color(1f, 0.6f, 0.2f, 1f);
case "smoke": return new Color(0.4f, 0.4f, 0.45f, 0.7f);
case "explosion": return new Color(1f, 0.8f, 0.3f, 1f);
case "magic": return new Color(0.6f, 0.3f, 1f, 1f);
case "water":
case "splash": return new Color(0.4f, 0.7f, 1f, 0.8f);
case "energy": return new Color(0.3f, 0.8f, 1f, 1f);
case "sparks": return new Color(1f, 0.9f, 0.5f, 1f);
case "dust": return new Color(0.7f, 0.65f, 0.6f, 0.5f);
case "snow": return Color.white;
case "rain": return new Color(0.7f, 0.8f, 0.9f, 0.6f);
case "leaves": return new Color(0.4f, 0.6f, 0.2f, 1f);
case "confetti": return Color.white;
case "trail":
case "cyber": return new Color(0f, 1f, 1f, 1f); // シアン
case "heal": return new Color(0.3f, 1f, 0.5f, 1f); // 緑
case "buff": return new Color(1f, 0.85f, 0.3f, 1f); // 金
case "debuff": return new Color(0.5f, 0.1f, 0.6f, 0.8f); // 紫
case "portal": return new Color(0.4f, 0.2f, 1f, 1f); // 青紫
default: return Color.white;
}
}
private Texture2D CreateDefaultParticleTexture(string effectType)
{
int size = 64;
var tex = new Texture2D(size, size, TextureFormat.RGBA32, false);
var pixels = new Color[size * size];
float center = size / 2f;
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
float dx = x - center;
float dy = y - center;
float dist = Mathf.Sqrt(dx * dx + dy * dy) / center;
float alpha = 0f;
switch (effectType)
{
case "fire":
case "explosion":
case "energy":
case "magic":
// ソフトな円形グラデーション(中心が明るい)
alpha = Mathf.Pow(1f - Mathf.Clamp01(dist), 2f);
break;
case "smoke":
case "dust":
// ノイジーな円形(煙っぽい)
alpha = (1f - Mathf.Clamp01(dist)) * (0.7f + 0.3f * Mathf.PerlinNoise(x * 0.2f, y * 0.2f));
break;
case "sparks":
case "rain":
// シャープな点
alpha = dist < 0.3f ? 1f : Mathf.Pow(1f - Mathf.Clamp01((dist - 0.3f) / 0.7f), 3f);
break;
case "water":
case "splash":
// 水滴(中心がより明るい)
alpha = Mathf.Pow(1f - Mathf.Clamp01(dist), 1.5f);
break;
case "snow":
case "confetti":
case "leaves":
// ソリッドな円
alpha = dist < 0.8f ? 1f : 1f - (dist - 0.8f) / 0.2f;
break;
case "trail":
case "cyber":
case "portal":
// シャープなコア + ソフトな外側(トレイル用)
alpha = dist < 0.2f ? 1f : Mathf.Pow(1f - Mathf.Clamp01((dist - 0.2f) / 0.8f), 1.5f);
break;
case "heal":
case "buff":
// 柔らかい光(オーラ用)
alpha = Mathf.Pow(1f - Mathf.Clamp01(dist), 1.2f);
break;
case "debuff":
// ノイジーで不規則(毒用)
alpha = (1f - Mathf.Clamp01(dist)) * (0.6f + 0.4f * Mathf.PerlinNoise(x * 0.3f, y * 0.3f));
break;
default:
// デフォルト円形グラデーション
alpha = 1f - Mathf.Clamp01(dist);
break;
}
pixels[y * size + x] = new Color(1f, 1f, 1f, Mathf.Clamp01(alpha));
}
}
tex.SetPixels(pixels);
tex.Apply();
tex.wrapMode = TextureWrapMode.Clamp;
tex.filterMode = FilterMode.Bilinear;
return tex;
}
// Registered shader mapping - Synaptic original shaders by logical key
// Supports both URP and Built-in pipeline with fallbacks
private class RegisteredShaderInfo
{
public string URPShaderName; // Shader name for URP
public string BuiltInShaderName; // Shader name for Built-in pipeline
public string FolderPath;
public string Source;
public string License;
public bool URPOnly; // True if only available for URP
public RegisteredShaderInfo(string urpShaderName, string builtInShaderName, string folderPath, string source, string license, bool urpOnly = false)
{
URPShaderName = urpShaderName;
BuiltInShaderName = builtInShaderName;
FolderPath = folderPath;
Source = source;
License = license;
URPOnly = urpOnly;
}
// Get shader name based on current pipeline
public string GetShaderName(string pipeline)
{
if (pipeline == "URP" && !string.IsNullOrEmpty(URPShaderName))
return URPShaderName;
return BuiltInShaderName;
}
// Check if shader is available for current pipeline
public bool IsAvailable(string pipeline)
{
if (URPOnly && pipeline != "URP")
return false;
// Check if folder actually exists (not disabled with ~)
string fullPath = $"Assets/Synaptic AI Pro/Shaders/{FolderPath}";
if (!System.IO.Directory.Exists(fullPath))
return false;
return true;
}
}
private static readonly Dictionary<string, RegisteredShaderInfo> RegisteredShaders = new Dictionary<string, RegisteredShaderInfo>
{
// === Synaptic Original Shaders ===
// These are custom shaders created by Synaptic AI Pro
// Caustics - Water light effect projector (works on both pipelines)
{ "caustics", new RegisteredShaderInfo(
"Synaptic/Caustics",
"Synaptic/Caustics",
"Shaders/Caustics",
"Synaptic AI Pro (Original)",
"Included") },
// Water - Full water system with waves, reflections, foam (works on both pipelines)
{ "water", new RegisteredShaderInfo(
"Synaptic/Water",
"Synaptic/Water",
"Shaders/Water",
"Synaptic AI Pro (Original)",
"Included") },
// === Synaptic Pro Shaders (Genshin Impact Level Quality) ===
// High-quality shaders - URP/Built-in/HDRP compatible
// Water Pro - SSR, Tessellation, Caustics, Flowmap, SSS, Foam
{ "water_pro", new RegisteredShaderInfo(
"Synaptic/WaterPro",
"Synaptic/WaterPro",
"Shaders/Water",
"Synaptic AI Pro (Original)",
"Included") },
{ "waterpro", new RegisteredShaderInfo(
"Synaptic/WaterPro",
"Synaptic/WaterPro",
"Shaders/Water",
"Synaptic AI Pro (Original)",
"Included") },
{ "ocean", new RegisteredShaderInfo(
"Synaptic/WaterPro",
"Synaptic/WaterPro",
"Shaders/Water",
"Synaptic AI Pro (Original)",
"Included") },
// Toon Pro - Genshin-style SDF face shadows, ramp, anisotropic specular, outline
{ "toon_pro", new RegisteredShaderInfo(
"Synaptic/ToonPro",
"Synaptic/ToonPro",
"Shaders/Toon",
"Synaptic AI Pro (Original)",
"Included") },
{ "toonpro", new RegisteredShaderInfo(
"Synaptic/ToonPro",
"Synaptic/ToonPro",
"Shaders/Toon",
"Synaptic AI Pro (Original)",
"Included") },
{ "anime", new RegisteredShaderInfo(
"Synaptic/ToonPro",
"Synaptic/ToonPro",
"Shaders/Toon",
"Synaptic AI Pro (Original)",
"Included") },
{ "genshin", new RegisteredShaderInfo(
"Synaptic/ToonPro",
"Synaptic/ToonPro",
"Shaders/Toon",
"Synaptic AI Pro (Original)",
"Included") },
// Grass Pro - Wind animation, player interaction, SSS, distance fade
{ "grass_pro", new RegisteredShaderInfo(
"Synaptic/GrassPro",
"Synaptic/GrassPro",
"Shaders/Grass",
"Synaptic AI Pro (Original)",
"Included") },
{ "grasspro", new RegisteredShaderInfo(
"Synaptic/GrassPro",
"Synaptic/GrassPro",
"Shaders/Grass",
"Synaptic AI Pro (Original)",
"Included") },
{ "vegetation", new RegisteredShaderInfo(
"Synaptic/GrassPro",
"Synaptic/GrassPro",
"Shaders/Grass",
"Synaptic AI Pro (Original)",
"Included") },
{ "foliage", new RegisteredShaderInfo(
"Synaptic/GrassPro",
"Synaptic/GrassPro",
"Shaders/Grass",
"Synaptic AI Pro (Original)",
"Included") },
// Sky Pro - Volumetric clouds, atmospheric scattering, stars, sun
{ "sky_pro", new RegisteredShaderInfo(
"Synaptic/SkyPro",
"Synaptic/SkyPro",
"Shaders/Sky",
"Synaptic AI Pro (Original)",
"Included") },
{ "skypro", new RegisteredShaderInfo(
"Synaptic/SkyPro",
"Synaptic/SkyPro",
"Shaders/Sky",
"Synaptic AI Pro (Original)",
"Included") },
{ "skybox_pro", new RegisteredShaderInfo(
"Synaptic/SkyPro",
"Synaptic/SkyPro",
"Shaders/Sky",
"Synaptic AI Pro (Original)",
"Included") },
{ "atmosphere", new RegisteredShaderInfo(
"Synaptic/SkyPro",
"Synaptic/SkyPro",
"Shaders/Sky",
"Synaptic AI Pro (Original)",
"Included") },
{ "volumetric_clouds", new RegisteredShaderInfo(
"Synaptic/SkyPro",
"Synaptic/SkyPro",
"Shaders/Sky",
"Synaptic AI Pro (Original)",
"Included") },
// Dissolve Pro - Directional dissolve, edge glow, particles, vertex displacement
{ "dissolve_pro", new RegisteredShaderInfo(
"Synaptic/DissolvePro",
"Synaptic/DissolvePro",
"Shaders/Effects",
"Synaptic AI Pro (Original)",
"Included") },
{ "dissolvepro", new RegisteredShaderInfo(
"Synaptic/DissolvePro",
"Synaptic/DissolvePro",
"Shaders/Effects",
"Synaptic AI Pro (Original)",
"Included") },
{ "dissolve", new RegisteredShaderInfo(
"Synaptic/DissolvePro",
"Synaptic/DissolvePro",
"Shaders/Effects",
"Synaptic AI Pro (Original)",
"Included") },
{ "disintegrate", new RegisteredShaderInfo(
"Synaptic/DissolvePro",
"Synaptic/DissolvePro",
"Shaders/Effects",
"Synaptic AI Pro (Original)",
"Included") },
// Shield Pro - Hex pattern, hit effects, intersection glow, distortion
{ "shield_pro", new RegisteredShaderInfo(
"Synaptic/ShieldPro",
"Synaptic/ShieldPro",
"Shaders/Effects",
"Synaptic AI Pro (Original)",
"Included") },
{ "shieldpro", new RegisteredShaderInfo(
"Synaptic/ShieldPro",
"Synaptic/ShieldPro",
"Shaders/Effects",
"Synaptic AI Pro (Original)",
"Included") },
{ "hex_shield", new RegisteredShaderInfo(
"Synaptic/ShieldPro",
"Synaptic/ShieldPro",
"Shaders/Effects",
"Synaptic AI Pro (Original)",
"Included") },
{ "energy_shield", new RegisteredShaderInfo(
"Synaptic/ShieldPro",
"Synaptic/ShieldPro",
"Shaders/Effects",
"Synaptic AI Pro (Original)",
"Included") },
{ "barrier", new RegisteredShaderInfo(
"Synaptic/ShieldPro",
"Synaptic/ShieldPro",
"Shaders/Effects",
"Synaptic AI Pro (Original)",
"Included") },
// Hair Pro - Kajiya-Kay dual specular, anisotropic highlights, SSS
{ "hair_pro", new RegisteredShaderInfo(
"Synaptic/HairPro",
"Synaptic/HairPro",
"Shaders/Character",
"Synaptic AI Pro (Original)",
"Included") },
{ "hairpro", new RegisteredShaderInfo(
"Synaptic/HairPro",
"Synaptic/HairPro",
"Shaders/Character",
"Synaptic AI Pro (Original)",
"Included") },
{ "anime_hair", new RegisteredShaderInfo(
"Synaptic/HairPro",
"Synaptic/HairPro",
"Shaders/Character",
"Synaptic AI Pro (Original)",
"Included") },
{ "kajiya", new RegisteredShaderInfo(
"Synaptic/HairPro",
"Synaptic/HairPro",
"Shaders/Character",
"Synaptic AI Pro (Original)",
"Included") },
// Eye Pro - Parallax iris, pupil dilation, caustics, multiple highlights
{ "eye_pro", new RegisteredShaderInfo(
"Synaptic/EyePro",
"Synaptic/EyePro",
"Shaders/Character",
"Synaptic AI Pro (Original)",
"Included") },
{ "eyepro", new RegisteredShaderInfo(
"Synaptic/EyePro",
"Synaptic/EyePro",
"Shaders/Character",
"Synaptic AI Pro (Original)",
"Included") },
{ "anime_eye", new RegisteredShaderInfo(
"Synaptic/EyePro",
"Synaptic/EyePro",
"Shaders/Character",
"Synaptic AI Pro (Original)",
"Included") },
{ "iris", new RegisteredShaderInfo(
"Synaptic/EyePro",
"Synaptic/EyePro",
"Shaders/Character",
"Synaptic AI Pro (Original)",
"Included") },
{ "pupil", new RegisteredShaderInfo(
"Synaptic/EyePro",
"Synaptic/EyePro",
"Shaders/Character",
"Synaptic AI Pro (Original)",
"Included") },
};
#region External Shader Editing
/// <summary>
/// Read a .shader file content
/// </summary>
private string ReadShader(Dictionary<string, string> parameters)
{
try
{
var path = parameters.GetValueOrDefault("path", "");
if (string.IsNullOrEmpty(path))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "path parameter is required"
});
}
// Normalize path
if (!path.StartsWith("Assets/") && !path.StartsWith("Packages/"))
{
path = "Assets/" + path;
}
if (!path.EndsWith(".shader"))
{
path += ".shader";
}
var fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), path);
if (!File.Exists(fullPath))
{
// Try to find shader by name
var shaderGuids = AssetDatabase.FindAssets("t:Shader");
foreach (var guid in shaderGuids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
if (assetPath.Contains(path.Replace(".shader", "")) || assetPath.EndsWith(path))
{
fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), assetPath);
path = assetPath;
break;
}
}
}
if (!File.Exists(fullPath))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Shader file not found: {path}",
searchedPath = fullPath
});
}
var content = File.ReadAllText(fullPath);
var lines = content.Split('\n');
// Mark as read for edit protection
MarkFileAsRead(fullPath);
return JsonConvert.SerializeObject(new
{
success = true,
path = path,
fullPath = fullPath,
content = content,
lineCount = lines.Length,
fileSize = new FileInfo(fullPath).Length,
message = $"Shader file read successfully. {lines.Length} lines. File marked as read for editing."
});
}
catch (Exception e)
{
return CreateErrorResponse("ReadShader", e, parameters);
}
}
/// <summary>
/// Modify an existing .shader file
/// </summary>
private string ModifyShader(Dictionary<string, string> parameters)
{
var path = parameters.GetValueOrDefault("path", "");
var oldText = parameters.GetValueOrDefault("old_text", "");
var newText = parameters.GetValueOrDefault("new_text", "");
var replaceAll = bool.Parse(parameters.GetValueOrDefault("replace_all", "false"));
if (string.IsNullOrEmpty(path))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "path parameter is required"
});
}
// Normalize path
if (!path.StartsWith("Assets/") && !path.StartsWith("Packages/"))
{
path = "Assets/" + path;
}
if (!path.EndsWith(".shader"))
{
path += ".shader";
}
var fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), path);
if (!File.Exists(fullPath))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Shader file not found: {path}"
});
}
// Check if file was read first
if (!HasFileBeenRead(fullPath))
{
return GetFileNotReadError(fullPath);
}
var content = File.ReadAllText(fullPath);
if (string.IsNullOrEmpty(oldText))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "old_text parameter is required for modification"
});
}
if (!content.Contains(oldText))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "old_text not found in shader file",
hint = "Make sure you're using exact text including whitespace"
});
}
try
{
string newContent;
int replaceCount;
if (replaceAll)
{
replaceCount = (content.Length - content.Replace(oldText, "").Length) / oldText.Length;
newContent = content.Replace(oldText, newText);
}
else
{
var index = content.IndexOf(oldText);
newContent = content.Substring(0, index) + newText + content.Substring(index + oldText.Length);
replaceCount = 1;
}
// Backup original
var backupPath = fullPath + ".bak";
File.WriteAllText(backupPath, content);
// Write modified content
File.WriteAllText(fullPath, newContent);
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
path = path,
replacements = replaceCount,
backupPath = backupPath,
message = $"Shader modified successfully. {replaceCount} replacement(s) made. Backup saved."
});
}
catch (Exception e)
{
return CreateErrorResponse("ModifyShader", e, parameters);
}
}
/// <summary>
/// Analyze shader structure (Properties, SubShaders, Passes, etc.)
/// </summary>
private string AnalyzeShader(Dictionary<string, string> parameters)
{
try
{
var path = parameters.GetValueOrDefault("path", "");
var shaderName = parameters.GetValueOrDefault("shader_name", "");
string content = "";
string resolvedPath = "";
// Try to load by path first
if (!string.IsNullOrEmpty(path))
{
if (!path.StartsWith("Assets/") && !path.StartsWith("Packages/"))
{
path = "Assets/" + path;
}
if (!path.EndsWith(".shader"))
{
path += ".shader";
}
var fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), path);
if (File.Exists(fullPath))
{
content = File.ReadAllText(fullPath);
resolvedPath = path;
MarkFileAsRead(fullPath);
}
}
// Try to find by shader name
if (string.IsNullOrEmpty(content) && !string.IsNullOrEmpty(shaderName))
{
var shader = Shader.Find(shaderName);
if (shader != null)
{
var assetPath = AssetDatabase.GetAssetPath(shader);
if (!string.IsNullOrEmpty(assetPath) && assetPath.EndsWith(".shader"))
{
var fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), assetPath);
if (File.Exists(fullPath))
{
content = File.ReadAllText(fullPath);
resolvedPath = assetPath;
MarkFileAsRead(fullPath);
}
}
}
}
if (string.IsNullOrEmpty(content))
{
// List available shaders
var availableShaders = new List<object>();
var shaderGuids = AssetDatabase.FindAssets("t:Shader");
foreach (var guid in shaderGuids.Take(50))
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var shaderAsset = AssetDatabase.LoadAssetAtPath<Shader>(assetPath);
if (shaderAsset != null)
{
availableShaders.Add(new { name = shaderAsset.name, path = assetPath });
}
}
return JsonConvert.SerializeObject(new
{
success = false,
error = "Could not find shader. Provide valid path or shader_name.",
availableShaders = availableShaders,
hint = "Use one of the available shaders listed above"
});
}
// Parse shader structure
var analysis = ParseShaderContent(content);
analysis["path"] = resolvedPath;
analysis["success"] = true;
analysis["message"] = "Shader analyzed successfully. File marked as read for editing.";
return JsonConvert.SerializeObject(analysis);
}
catch (Exception e)
{
return CreateErrorResponse("AnalyzeShader", e, parameters);
}
}
/// <summary>
/// Parse shader content and extract structure
/// </summary>
private Dictionary<string, object> ParseShaderContent(string content)
{
var result = new Dictionary<string, object>();
var lines = content.Split('\n');
// Extract shader name
var shaderNameMatch = System.Text.RegularExpressions.Regex.Match(content, @"Shader\s+""([^""]+)""");
if (shaderNameMatch.Success)
{
result["shaderName"] = shaderNameMatch.Groups[1].Value;
}
// Extract Properties
var properties = new List<object>();
var propsMatch = System.Text.RegularExpressions.Regex.Match(content, @"Properties\s*\{([^}]+)\}", System.Text.RegularExpressions.RegexOptions.Singleline);
if (propsMatch.Success)
{
var propsContent = propsMatch.Groups[1].Value;
var propMatches = System.Text.RegularExpressions.Regex.Matches(propsContent, @"(\w+)\s*\(\s*""([^""]+)""\s*,\s*(\w+)(?:\s*\([^)]*\))?\s*\)\s*=\s*([^\n]+)");
foreach (System.Text.RegularExpressions.Match propMatch in propMatches)
{
properties.Add(new
{
name = propMatch.Groups[1].Value,
displayName = propMatch.Groups[2].Value,
type = propMatch.Groups[3].Value,
defaultValue = propMatch.Groups[4].Value.Trim()
});
}
}
result["properties"] = properties;
// Count SubShaders
var subShaderMatches = System.Text.RegularExpressions.Regex.Matches(content, @"SubShader\s*\{");
result["subShaderCount"] = subShaderMatches.Count;
// Extract SubShader details
var subShaders = new List<object>();
var subShaderRegex = new System.Text.RegularExpressions.Regex(@"SubShader\s*\{", System.Text.RegularExpressions.RegexOptions.Singleline);
var matches = subShaderRegex.Matches(content);
for (int i = 0; i < matches.Count; i++)
{
var startIndex = matches[i].Index;
var endIndex = i < matches.Count - 1 ? matches[i + 1].Index : content.Length;
var subShaderContent = content.Substring(startIndex, endIndex - startIndex);
// Extract Tags
var tagsMatch = System.Text.RegularExpressions.Regex.Match(subShaderContent, @"Tags\s*\{([^}]+)\}");
var tags = new Dictionary<string, string>();
if (tagsMatch.Success)
{
var tagMatches = System.Text.RegularExpressions.Regex.Matches(tagsMatch.Groups[1].Value, @"""(\w+)""\s*=\s*""([^""]+)""");
foreach (System.Text.RegularExpressions.Match tagMatch in tagMatches)
{
tags[tagMatch.Groups[1].Value] = tagMatch.Groups[2].Value;
}
}
// Count Passes
var passCount = System.Text.RegularExpressions.Regex.Matches(subShaderContent, @"Pass\s*\{").Count;
// Extract Pass names
var passNames = new List<string>();
var passNameMatches = System.Text.RegularExpressions.Regex.Matches(subShaderContent, @"Name\s+""([^""]+)""");
foreach (System.Text.RegularExpressions.Match passNameMatch in passNameMatches)
{
passNames.Add(passNameMatch.Groups[1].Value);
}
// Check for CGPROGRAM/HLSLPROGRAM
var hasCG = subShaderContent.Contains("CGPROGRAM");
var hasHLSL = subShaderContent.Contains("HLSLPROGRAM");
subShaders.Add(new
{
index = i,
tags = tags,
passCount = passCount,
passNames = passNames,
language = hasCG ? "CG" : (hasHLSL ? "HLSL" : "Unknown")
});
}
result["subShaders"] = subShaders;
// Extract pragmas
var pragmas = new List<string>();
var pragmaMatches = System.Text.RegularExpressions.Regex.Matches(content, @"#pragma\s+(\w+)");
foreach (System.Text.RegularExpressions.Match pragmaMatch in pragmaMatches)
{
var pragma = pragmaMatch.Groups[1].Value;
if (!pragmas.Contains(pragma))
pragmas.Add(pragma);
}
result["pragmas"] = pragmas;
// Extract includes
var includes = new List<string>();
var includeMatches = System.Text.RegularExpressions.Regex.Matches(content, @"#include\s+""([^""]+)""");
foreach (System.Text.RegularExpressions.Match includeMatch in includeMatches)
{
includes.Add(includeMatch.Groups[1].Value);
}
result["includes"] = includes;
// Check for common features
var features = new List<string>();
if (content.Contains("_MainTex")) features.Add("MainTexture");
if (content.Contains("_BumpMap") || content.Contains("_NormalMap")) features.Add("NormalMap");
if (content.Contains("_MetallicGlossMap") || content.Contains("_Metallic")) features.Add("Metallic");
if (content.Contains("_EmissionMap") || content.Contains("_EmissionColor")) features.Add("Emission");
if (content.Contains("_OcclusionMap")) features.Add("Occlusion");
if (content.Contains("_Cutoff") || content.Contains("AlphaTest")) features.Add("AlphaCutoff");
if (content.Contains("Blend ")) features.Add("Blending");
if (content.Contains("ZWrite Off")) features.Add("NoZWrite");
if (content.Contains("Cull Off")) features.Add("DoubleSided");
result["features"] = features;
// Check render pipeline compatibility
var pipelineHints = new List<string>();
if (content.Contains("UnityPerMaterial") || content.Contains("HLSLPROGRAM")) pipelineHints.Add("SRP Compatible");
if (content.Contains("CGPROGRAM") && !content.Contains("HLSLPROGRAM")) pipelineHints.Add("Built-in Pipeline");
if (content.Contains("LightweightPipeline") || content.Contains("UniversalPipeline")) pipelineHints.Add("URP");
if (content.Contains("HDRenderPipeline")) pipelineHints.Add("HDRP");
result["pipelineHints"] = pipelineHints;
result["lineCount"] = lines.Length;
return result;
}
/// <summary>
/// Read ShaderGraph asset (Unity 2018.1+)
/// </summary>
private string ReadShaderGraph(Dictionary<string, string> parameters)
{
try
{
var path = parameters.GetValueOrDefault("path", "");
if (string.IsNullOrEmpty(path))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "path parameter is required"
});
}
// Normalize path
if (!path.StartsWith("Assets/") && !path.StartsWith("Packages/"))
{
path = "Assets/" + path;
}
if (!path.EndsWith(".shadergraph") && !path.EndsWith(".shadersubgraph"))
{
// Try both extensions
var sgPath = path + ".shadergraph";
var ssgPath = path + ".shadersubgraph";
var sgFullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), sgPath);
var ssgFullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), ssgPath);
if (File.Exists(sgFullPath))
path = sgPath;
else if (File.Exists(ssgFullPath))
path = ssgPath;
else
path += ".shadergraph";
}
var fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), path);
if (!File.Exists(fullPath))
{
// List available shader graphs
var availableGraphs = new List<string>();
var sgGuids = AssetDatabase.FindAssets("t:Shader");
foreach (var guid in sgGuids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
if (assetPath.EndsWith(".shadergraph") || assetPath.EndsWith(".shadersubgraph"))
{
availableGraphs.Add(assetPath);
}
}
return JsonConvert.SerializeObject(new
{
success = false,
error = $"ShaderGraph not found: {path}",
availableGraphs = availableGraphs.Take(20).ToList()
});
}
// ShaderGraph files are JSON-like format
var content = File.ReadAllText(fullPath);
MarkFileAsRead(fullPath);
// Parse ShaderGraph JSON structure
var graphInfo = ParseShaderGraph(content, path);
return JsonConvert.SerializeObject(new
{
success = true,
path = path,
fullPath = fullPath,
graphInfo = graphInfo,
rawContent = content.Length > 50000 ? content.Substring(0, 50000) + "\n... (truncated)" : content,
message = "ShaderGraph read successfully. File marked as read for editing."
});
}
catch (Exception e)
{
return CreateErrorResponse("ReadShaderGraph", e, parameters);
}
}
/// <summary>
/// Parse ShaderGraph JSON content
/// </summary>
private Dictionary<string, object> ParseShaderGraph(string content, string path)
{
var result = new Dictionary<string, object>();
try
{
// ShaderGraph uses a custom serialization format
// Try to extract useful information
// Count nodes
var nodeCount = System.Text.RegularExpressions.Regex.Matches(content, @"""m_ObjectId""").Count;
result["nodeCount"] = nodeCount;
// Find property references
var properties = new List<object>();
var propMatches = System.Text.RegularExpressions.Regex.Matches(content, @"""m_DefaultReferenceName"":\s*""([^""]+)""");
var displayMatches = System.Text.RegularExpressions.Regex.Matches(content, @"""m_DisplayName"":\s*""([^""]+)""");
var propNames = new HashSet<string>();
foreach (System.Text.RegularExpressions.Match match in propMatches)
{
var name = match.Groups[1].Value;
if (!propNames.Contains(name) && name.StartsWith("_"))
{
propNames.Add(name);
properties.Add(new { referenceName = name });
}
}
result["properties"] = properties;
// Detect graph type
var isSubGraph = path.EndsWith(".shadersubgraph");
result["graphType"] = isSubGraph ? "SubGraph" : "ShaderGraph";
// Check for common node types
var nodeTypes = new List<string>();
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.SampleTexture2DNode\"")) nodeTypes.Add("SampleTexture2D");
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.ColorNode\"")) nodeTypes.Add("Color");
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.MultiplyNode\"")) nodeTypes.Add("Multiply");
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.AddNode\"")) nodeTypes.Add("Add");
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.LerpNode\"")) nodeTypes.Add("Lerp");
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.NormalStrengthNode\"")) nodeTypes.Add("NormalStrength");
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.FresnelNode\"")) nodeTypes.Add("Fresnel");
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.TimeNode\"")) nodeTypes.Add("Time");
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.PositionNode\"")) nodeTypes.Add("Position");
if (content.Contains("\"m_Type\":\"UnityEditor.ShaderGraph.ViewDirectionNode\"")) nodeTypes.Add("ViewDirection");
result["nodeTypesFound"] = nodeTypes;
// Check master node type (for shader output)
if (content.Contains("PBRMasterNode") || content.Contains("UniversalTarget"))
result["masterNode"] = "PBR/Lit";
else if (content.Contains("UnlitMasterNode"))
result["masterNode"] = "Unlit";
else if (content.Contains("SpriteLitMasterNode"))
result["masterNode"] = "SpriteLit";
else if (content.Contains("SpriteUnlitMasterNode"))
result["masterNode"] = "SpriteUnlit";
// Extract precision
if (content.Contains("\"m_Precision\":0")) result["precision"] = "Inherit";
else if (content.Contains("\"m_Precision\":1")) result["precision"] = "Float";
else if (content.Contains("\"m_Precision\":2")) result["precision"] = "Half";
}
catch
{
result["parseError"] = "Could not fully parse ShaderGraph structure";
}
return result;
}
// ============================================================
// VFX Graph Asset Editing (External Assets)
// ============================================================
/// <summary>
/// Track which VFX Graph files have been read (for edit protection)
/// </summary>
private static HashSet<string> _readVFXGraphFiles = new HashSet<string>();
private void MarkVFXGraphAsRead(string path)
{
_readVFXGraphFiles.Add(path.ToLower());
}
private bool HasVFXGraphBeenRead(string path)
{
return _readVFXGraphFiles.Contains(path.ToLower());
}
/// <summary>
/// Read existing VFX Graph asset
/// </summary>
private string ReadVFXGraph(Dictionary<string, string> parameters)
{
try
{
var path = parameters.GetValueOrDefault("path", "");
if (string.IsNullOrEmpty(path))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "path parameter is required"
});
}
// Normalize path
if (!path.StartsWith("Assets/") && !path.StartsWith("Packages/"))
{
path = "Assets/" + path;
}
if (!path.EndsWith(".vfx"))
{
path += ".vfx";
}
var fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), path);
if (!File.Exists(fullPath))
{
// List available VFX graphs
var availableVFX = AssetDatabase.FindAssets("t:VisualEffectAsset")
.Select(guid => AssetDatabase.GUIDToAssetPath(guid))
.Take(20)
.ToList();
return JsonConvert.SerializeObject(new
{
success = false,
error = $"VFX Graph not found: {path}",
availableVFX = availableVFX
});
}
// Read VFX Graph file content
var content = File.ReadAllText(fullPath);
MarkVFXGraphAsRead(fullPath);
// Parse VFX structure
var vfxInfo = ParseVFXGraphContent(content, path);
return JsonConvert.SerializeObject(new
{
success = true,
path = path,
fullPath = fullPath,
vfxInfo = vfxInfo,
fileSize = new FileInfo(fullPath).Length,
message = "VFX Graph read successfully. File marked as read for editing."
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse("ReadVFXGraph", e, parameters);
}
}
/// <summary>
/// Parse VFX Graph content to extract structure
/// </summary>
private Dictionary<string, object> ParseVFXGraphContent(string content, string path)
{
var result = new Dictionary<string, object>();
try
{
// VFX Graph uses YAML-like serialization
// Extract context information
var contexts = new List<object>();
var contextMatches = System.Text.RegularExpressions.Regex.Matches(content, @"m_Type:\s*(\d+).*?m_Label:\s*([^\r\n]+)", System.Text.RegularExpressions.RegexOptions.Singleline);
// Count systems
var systemCount = System.Text.RegularExpressions.Regex.Matches(content, @"VFXBasicSpawner|VFXBasicInitialize|VFXBasicUpdate|VFXBasicOutput").Count;
result["estimatedSystemCount"] = Math.Max(1, systemCount / 4);
// Extract capacity
var capacityMatch = System.Text.RegularExpressions.Regex.Match(content, @"capacity:\s*(\d+)");
if (capacityMatch.Success)
{
result["capacity"] = int.Parse(capacityMatch.Groups[1].Value);
}
// Extract spawn rate info
var spawnRateMatch = System.Text.RegularExpressions.Regex.Match(content, @"Rate:\s*(\d+\.?\d*)");
if (spawnRateMatch.Success)
{
result["spawnRate"] = float.Parse(spawnRateMatch.Groups[1].Value);
}
// Check for common blocks
var blocks = new List<string>();
if (content.Contains("SetAttribute")) blocks.Add("SetAttribute");
if (content.Contains("VFXBlockSpawnerConstant") || content.Contains("ConstantRate")) blocks.Add("ConstantSpawnRate");
if (content.Contains("VFXBlockSpawnerBurst") || content.Contains("Burst")) blocks.Add("BurstSpawn");
if (content.Contains("Position")) blocks.Add("Position");
if (content.Contains("Velocity")) blocks.Add("Velocity");
if (content.Contains("Lifetime")) blocks.Add("Lifetime");
if (content.Contains("Size")) blocks.Add("Size");
if (content.Contains("Color")) blocks.Add("Color");
if (content.Contains("Turbulence")) blocks.Add("Turbulence");
if (content.Contains("Gravity") || content.Contains("gravity")) blocks.Add("Gravity");
if (content.Contains("Collision")) blocks.Add("Collision");
if (content.Contains("Noise")) blocks.Add("Noise");
result["detectedBlocks"] = blocks;
// Extract exposed parameters
var exposedParams = new List<object>();
var paramMatches = System.Text.RegularExpressions.Regex.Matches(content, @"m_ExposedName:\s*([^\r\n]+)");
foreach (System.Text.RegularExpressions.Match match in paramMatches)
{
var paramName = match.Groups[1].Value.Trim();
if (!string.IsNullOrEmpty(paramName))
{
exposedParams.Add(new { name = paramName });
}
}
result["exposedParameters"] = exposedParams;
// Check output type
if (content.Contains("VFXQuadOutput") || content.Contains("Quad Output"))
result["outputType"] = "Quad";
else if (content.Contains("VFXMeshOutput") || content.Contains("Mesh Output"))
result["outputType"] = "Mesh";
else if (content.Contains("VFXPointOutput") || content.Contains("Point Output"))
result["outputType"] = "Point";
else if (content.Contains("VFXLineOutput") || content.Contains("Line Output"))
result["outputType"] = "Line";
else
result["outputType"] = "Unknown";
// Check blend mode
if (content.Contains("Additive"))
result["blendMode"] = "Additive";
else if (content.Contains("Alpha"))
result["blendMode"] = "Alpha";
else if (content.Contains("Opaque"))
result["blendMode"] = "Opaque";
}
catch
{
result["parseError"] = "Could not fully parse VFX Graph structure";
}
return result;
}
/// <summary>
/// Modify existing VFX Graph asset
/// </summary>
private string ModifyVFXGraph(Dictionary<string, string> parameters)
{
try
{
var path = parameters.GetValueOrDefault("path", "");
var oldText = parameters.GetValueOrDefault("old_text", "");
var newText = parameters.GetValueOrDefault("new_text", "");
var replaceAll = parameters.GetValueOrDefault("replace_all", "false") == "true";
if (string.IsNullOrEmpty(path))
{
return JsonConvert.SerializeObject(new { success = false, error = "path parameter is required" });
}
if (string.IsNullOrEmpty(oldText))
{
return JsonConvert.SerializeObject(new { success = false, error = "old_text parameter is required" });
}
// Normalize path
if (!path.StartsWith("Assets/") && !path.StartsWith("Packages/"))
{
path = "Assets/" + path;
}
if (!path.EndsWith(".vfx"))
{
path += ".vfx";
}
var fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), path);
if (!File.Exists(fullPath))
{
return JsonConvert.SerializeObject(new { success = false, error = $"VFX Graph not found: {path}" });
}
// Check if file was read first
if (!HasVFXGraphBeenRead(fullPath))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "FILE_NOT_READ: You must call READ_VFX_GRAPH before modifying. This ensures you understand the current structure.",
suggestion = $"First call: READ_VFX_GRAPH with path=\"{path}\""
});
}
var content = File.ReadAllText(fullPath);
// Check if old_text exists
if (!content.Contains(oldText))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "old_text not found in VFX Graph file",
suggestion = "Make sure the text exactly matches the content in the file"
});
}
// Create backup
var backupPath = fullPath + ".bak";
File.Copy(fullPath, backupPath, true);
// Replace text
string newContent;
int replacementCount;
if (replaceAll)
{
replacementCount = System.Text.RegularExpressions.Regex.Matches(content, System.Text.RegularExpressions.Regex.Escape(oldText)).Count;
newContent = content.Replace(oldText, newText);
}
else
{
var index = content.IndexOf(oldText);
newContent = content.Substring(0, index) + newText + content.Substring(index + oldText.Length);
replacementCount = 1;
}
File.WriteAllText(fullPath, newContent);
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
path = path,
replacementCount = replacementCount,
backupCreated = backupPath,
message = $"VFX Graph modified successfully. {replacementCount} replacement(s) made."
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse("ModifyVFXGraph", e, parameters);
}
}
/// <summary>
/// Analyze VFX Graph structure without full content
/// </summary>
private string AnalyzeVFXGraph(Dictionary<string, string> parameters)
{
try
{
var path = parameters.GetValueOrDefault("path", "");
if (string.IsNullOrEmpty(path))
{
return JsonConvert.SerializeObject(new { success = false, error = "path parameter is required" });
}
// Normalize path
if (!path.StartsWith("Assets/") && !path.StartsWith("Packages/"))
{
path = "Assets/" + path;
}
if (!path.EndsWith(".vfx"))
{
path += ".vfx";
}
var fullPath = Path.Combine(Application.dataPath.Replace("/Assets", ""), path);
if (!File.Exists(fullPath))
{
return JsonConvert.SerializeObject(new { success = false, error = $"VFX Graph not found: {path}" });
}
var content = File.ReadAllText(fullPath);
var analysis = ParseVFXGraphContent(content, path);
// Add file info
var fileInfo = new FileInfo(fullPath);
analysis["fileName"] = fileInfo.Name;
analysis["fileSize"] = fileInfo.Length;
analysis["lastModified"] = fileInfo.LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss");
// Complexity estimation
var lineCount = content.Split('\n').Length;
analysis["lineCount"] = lineCount;
analysis["complexity"] = lineCount < 500 ? "Simple" : (lineCount < 2000 ? "Medium" : "Complex");
return JsonConvert.SerializeObject(new
{
success = true,
path = path,
analysis = analysis
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse("AnalyzeVFXGraph", e, parameters);
}
}
// ============================================================
// Particle System Editing (Scene/Prefab Components)
// ============================================================
/// <summary>
/// Read existing Particle System component
/// </summary>
private string ReadParticleSystem(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target", "");
var targetObject = parameters.GetValueOrDefault("gameObject", targetName);
if (string.IsNullOrEmpty(targetObject))
{
// List all particle systems in scene
var allPS = GameObject.FindObjectsOfType<ParticleSystem>();
return JsonConvert.SerializeObject(new
{
success = false,
error = "target/gameObject parameter is required",
availableParticleSystems = allPS.Select(ps => ps.gameObject.name).ToList()
});
}
var go = GameObject.Find(targetObject);
if (go == null)
{
return JsonConvert.SerializeObject(new { success = false, error = $"GameObject not found: {targetObject}" });
}
var particleSystem = go.GetComponent<ParticleSystem>();
if (particleSystem == null)
{
return JsonConvert.SerializeObject(new { success = false, error = $"No ParticleSystem component on: {targetObject}" });
}
// Read all modules
var main = particleSystem.main;
var emission = particleSystem.emission;
var shape = particleSystem.shape;
var velocityOverLifetime = particleSystem.velocityOverLifetime;
var colorOverLifetime = particleSystem.colorOverLifetime;
var sizeOverLifetime = particleSystem.sizeOverLifetime;
var rotationOverLifetime = particleSystem.rotationOverLifetime;
var noise = particleSystem.noise;
var collision = particleSystem.collision;
var renderer = go.GetComponent<ParticleSystemRenderer>();
var psInfo = new Dictionary<string, object>
{
["gameObject"] = targetObject,
["isPlaying"] = particleSystem.isPlaying,
["particleCount"] = particleSystem.particleCount,
["main"] = new Dictionary<string, object>
{
["duration"] = main.duration,
["looping"] = main.loop,
["prewarm"] = main.prewarm,
["startDelay"] = main.startDelay.constant,
["startLifetime"] = main.startLifetime.constant,
["startSpeed"] = main.startSpeed.constant,
["startSize"] = main.startSize.constant,
["startRotation"] = main.startRotation.constant,
["startColor"] = ColorToHex(main.startColor.color),
["gravityModifier"] = main.gravityModifier.constant,
["simulationSpace"] = main.simulationSpace.ToString(),
["scalingMode"] = main.scalingMode.ToString(),
["maxParticles"] = main.maxParticles,
["playOnAwake"] = main.playOnAwake
},
["emission"] = new Dictionary<string, object>
{
["enabled"] = emission.enabled,
["rateOverTime"] = emission.rateOverTime.constant,
["rateOverDistance"] = emission.rateOverDistance.constant,
["burstCount"] = emission.burstCount
},
["shape"] = new Dictionary<string, object>
{
["enabled"] = shape.enabled,
["shapeType"] = shape.shapeType.ToString(),
["radius"] = shape.radius,
["angle"] = shape.angle,
["arc"] = shape.arc
},
["velocityOverLifetime"] = new Dictionary<string, object>
{
["enabled"] = velocityOverLifetime.enabled
},
["colorOverLifetime"] = new Dictionary<string, object>
{
["enabled"] = colorOverLifetime.enabled
},
["sizeOverLifetime"] = new Dictionary<string, object>
{
["enabled"] = sizeOverLifetime.enabled
},
["noise"] = new Dictionary<string, object>
{
["enabled"] = noise.enabled,
["strength"] = noise.strength.constant,
["frequency"] = noise.frequency,
["scrollSpeed"] = noise.scrollSpeed.constant
},
["collision"] = new Dictionary<string, object>
{
["enabled"] = collision.enabled,
["type"] = collision.type.ToString()
}
};
if (renderer != null)
{
psInfo["renderer"] = new Dictionary<string, object>
{
["renderMode"] = renderer.renderMode.ToString(),
["material"] = renderer.sharedMaterial?.name ?? "None",
["sortingOrder"] = renderer.sortingOrder,
["minParticleSize"] = renderer.minParticleSize,
["maxParticleSize"] = renderer.maxParticleSize
};
}
return JsonConvert.SerializeObject(new
{
success = true,
particleSystem = psInfo
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse("ReadParticleSystem", e, parameters);
}
}
/// <summary>
/// Modify existing Particle System component
/// </summary>
private string ModifyParticleSystem(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target", "");
var targetObject = parameters.GetValueOrDefault("gameObject", targetName);
var module = parameters.GetValueOrDefault("module", "main").ToLower();
var property = parameters.GetValueOrDefault("property", "");
var value = parameters.GetValueOrDefault("value", "");
if (string.IsNullOrEmpty(targetObject))
{
return JsonConvert.SerializeObject(new { success = false, error = "target/gameObject parameter is required" });
}
if (string.IsNullOrEmpty(property))
{
return JsonConvert.SerializeObject(new { success = false, error = "property parameter is required" });
}
var go = GameObject.Find(targetObject);
if (go == null)
{
return JsonConvert.SerializeObject(new { success = false, error = $"GameObject not found: {targetObject}" });
}
var ps = go.GetComponent<ParticleSystem>();
if (ps == null)
{
return JsonConvert.SerializeObject(new { success = false, error = $"No ParticleSystem component on: {targetObject}" });
}
// Register for undo
UnityEditor.Undo.RecordObject(ps, "Modify Particle System");
var propertyLower = property.ToLower();
var changed = false;
switch (module)
{
case "main":
var main = ps.main;
switch (propertyLower)
{
case "duration":
main.duration = float.Parse(value);
changed = true;
break;
case "looping":
case "loop":
main.loop = bool.Parse(value);
changed = true;
break;
case "prewarm":
main.prewarm = bool.Parse(value);
changed = true;
break;
case "startdelay":
main.startDelay = float.Parse(value);
changed = true;
break;
case "startlifetime":
main.startLifetime = float.Parse(value);
changed = true;
break;
case "startspeed":
main.startSpeed = float.Parse(value);
changed = true;
break;
case "startsize":
main.startSize = float.Parse(value);
changed = true;
break;
case "startrotation":
main.startRotation = float.Parse(value) * Mathf.Deg2Rad;
changed = true;
break;
case "startcolor":
if (ColorUtility.TryParseHtmlString(value, out Color color))
{
main.startColor = color;
changed = true;
}
break;
case "gravitymodifier":
case "gravity":
main.gravityModifier = float.Parse(value);
changed = true;
break;
case "maxparticles":
main.maxParticles = int.Parse(value);
changed = true;
break;
case "playonawake":
main.playOnAwake = bool.Parse(value);
changed = true;
break;
case "simulationspace":
if (Enum.TryParse<ParticleSystemSimulationSpace>(value, true, out var space))
{
main.simulationSpace = space;
changed = true;
}
break;
}
break;
case "emission":
var emission = ps.emission;
switch (propertyLower)
{
case "enabled":
emission.enabled = bool.Parse(value);
changed = true;
break;
case "rateovertime":
case "rate":
emission.rateOverTime = float.Parse(value);
changed = true;
break;
case "rateoverdistance":
emission.rateOverDistance = float.Parse(value);
changed = true;
break;
}
break;
case "shape":
var shape = ps.shape;
switch (propertyLower)
{
case "enabled":
shape.enabled = bool.Parse(value);
changed = true;
break;
case "shapetype":
case "type":
if (Enum.TryParse<ParticleSystemShapeType>(value, true, out var shapeType))
{
shape.shapeType = shapeType;
changed = true;
}
break;
case "radius":
shape.radius = float.Parse(value);
changed = true;
break;
case "angle":
shape.angle = float.Parse(value);
changed = true;
break;
case "arc":
shape.arc = float.Parse(value);
changed = true;
break;
}
break;
case "noise":
var noise = ps.noise;
switch (propertyLower)
{
case "enabled":
noise.enabled = bool.Parse(value);
changed = true;
break;
case "strength":
noise.strength = float.Parse(value);
changed = true;
break;
case "frequency":
noise.frequency = float.Parse(value);
changed = true;
break;
case "scrollspeed":
noise.scrollSpeed = float.Parse(value);
changed = true;
break;
}
break;
case "collision":
var collision = ps.collision;
switch (propertyLower)
{
case "enabled":
collision.enabled = bool.Parse(value);
changed = true;
break;
}
break;
case "velocityoverlifetime":
case "velocity":
var vol = ps.velocityOverLifetime;
switch (propertyLower)
{
case "enabled":
vol.enabled = bool.Parse(value);
changed = true;
break;
}
break;
case "coloroverlifetime":
case "color":
var col = ps.colorOverLifetime;
switch (propertyLower)
{
case "enabled":
col.enabled = bool.Parse(value);
changed = true;
break;
}
break;
case "sizeoverlifetime":
case "size":
var sol = ps.sizeOverLifetime;
switch (propertyLower)
{
case "enabled":
sol.enabled = bool.Parse(value);
changed = true;
break;
}
break;
}
if (!changed)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Unknown property '{property}' for module '{module}'",
availableModules = new[] { "main", "emission", "shape", "noise", "collision", "velocityOverLifetime", "colorOverLifetime", "sizeOverLifetime" },
mainProperties = new[] { "duration", "loop", "prewarm", "startDelay", "startLifetime", "startSpeed", "startSize", "startRotation", "startColor", "gravityModifier", "maxParticles", "playOnAwake", "simulationSpace" },
emissionProperties = new[] { "enabled", "rateOverTime", "rateOverDistance" },
shapeProperties = new[] { "enabled", "shapeType", "radius", "angle", "arc" },
noiseProperties = new[] { "enabled", "strength", "frequency", "scrollSpeed" }
});
}
EditorUtility.SetDirty(ps);
return JsonConvert.SerializeObject(new
{
success = true,
gameObject = targetObject,
module = module,
property = property,
newValue = value,
message = $"Particle System modified: {module}.{property} = {value}"
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse("ModifyParticleSystem", e, parameters);
}
}
#endregion
private string CreateShaderGraph(Dictionary<string, string> parameters)
{
try
{
var shaderType = parameters.GetValueOrDefault("shaderType", "surface").ToLower();
var requestedPipeline = parameters.GetValueOrDefault("pipeline", "auto");
var featuresStr = parameters.GetValueOrDefault("features", "normal_map");
var features = featuresStr.Split(',').Select(f => f.Trim()).ToList();
var animated = bool.Parse(parameters.GetValueOrDefault("animated", "false"));
// Detect current rendering pipeline
var currentPipeline = DetectRenderingPipeline();
var effectivePipeline = requestedPipeline == "auto" ? currentPipeline : requestedPipeline.ToUpper();
// Check if we have a registered Synaptic shader for this type
if (RegisteredShaders.TryGetValue(shaderType, out var shaderInfo))
{
// Check if shader is available for current pipeline
if (!shaderInfo.IsAvailable(currentPipeline))
{
return JsonConvert.SerializeObject(new
{
success = false,
shaderType = shaderType,
currentPipeline = currentPipeline,
message = $"Registered shader '{shaderType}' is not available for {currentPipeline} pipeline. Using generated fallback shader instead.",
fallbackAvailable = true
});
}
// Get the appropriate shader name for current pipeline
var shaderName = shaderInfo.GetShaderName(currentPipeline);
if (string.IsNullOrEmpty(shaderName))
{
return JsonConvert.SerializeObject(new
{
success = false,
shaderType = shaderType,
currentPipeline = currentPipeline,
message = $"No shader available for '{shaderType}' on {currentPipeline} pipeline.",
fallbackAvailable = true
});
}
// Use registered Synaptic shader
var shader = Shader.Find(shaderName);
if (shader != null)
{
return JsonConvert.SerializeObject(new
{
success = true,
shaderType = shaderType,
shaderName = shaderName,
currentPipeline = currentPipeline,
source = "registered",
repository = shaderInfo.Source,
license = shaderInfo.License,
folderPath = $"Assets/Synaptic AI Pro/Shaders/{shaderInfo.FolderPath}",
message = $"Using registered shader '{shaderName}' from {shaderInfo.Source} for {currentPipeline} pipeline. Shader is ready to use.",
materialPreset = GetMaterialPreset(shaderType)
});
}
else
{
return JsonConvert.SerializeObject(new
{
success = true,
shaderType = shaderType,
shaderName = shaderName,
currentPipeline = currentPipeline,
source = "registered",
repository = shaderInfo.Source,
license = shaderInfo.License,
folderPath = $"Assets/Synaptic AI Pro/Shaders/{shaderInfo.FolderPath}",
message = $"Registered shader '{shaderName}' is included in package at {shaderInfo.FolderPath}. May need Unity to refresh assets (Assets > Refresh).",
materialPreset = GetMaterialPreset(shaderType)
});
}
}
// Fallback to generated shader for basic types
var generatedShaderName = $"CustomShaderGraph_{shaderType}_{DateTime.Now.Ticks}";
var shaderController = new GameObject(generatedShaderName).AddComponent<ShaderGraphController>();
shaderController.shaderType = shaderType;
shaderController.targetPipeline = effectivePipeline;
shaderController.features = features;
shaderController.animated = animated;
string result = "";
string actualShaderName = "";
switch (shaderType)
{
case "surface":
case "lit":
result = GenerateLitShader(shaderController);
actualShaderName = "Synaptic/Lit";
break;
case "unlit":
result = GenerateUnlitShader(shaderController);
actualShaderName = "Synaptic/Unlit";
break;
case "glass":
result = GenerateGlassShader(shaderController);
actualShaderName = "Synaptic/Glass";
break;
case "metal":
result = GenerateMetalShader(shaderController);
actualShaderName = "Synaptic/Metal";
break;
case "fabric":
result = GenerateFabricShader(shaderController);
actualShaderName = "Synaptic/Fabric";
break;
case "neon":
case "neon_glow":
result = GenerateNeonGlowShader(shaderController);
actualShaderName = "Synaptic/NeonGlow";
break;
default:
result = GenerateCustomShader(shaderController);
actualShaderName = "Synaptic/Custom";
break;
}
// Clean up temporary object
if (shaderController != null && shaderController.gameObject != null)
{
GameObject.DestroyImmediate(shaderController.gameObject);
}
var featureList = string.Join(", ", features);
return JsonConvert.SerializeObject(new
{
success = true,
shaderType = shaderType,
shaderName = actualShaderName,
source = "generated",
pipeline = effectivePipeline,
features = featureList,
animated = animated,
message = $"Generated {shaderType} shader '{actualShaderName}' for {effectivePipeline} pipeline."
});
}
catch (Exception e)
{
return CreateErrorResponse("CreateShaderGraph", e, parameters);
}
}
// Get recommended material property presets for each shader type
private static object GetMaterialPreset(string shaderType)
{
switch (shaderType)
{
case "water":
return new
{
_DepthGradientShallow = new float[] { 0.325f, 0.807f, 0.971f, 0.725f },
_DepthGradientDeep = new float[] { 0.086f, 0.407f, 1f, 0.749f },
_DepthMaxDistance = 1f,
_SurfaceNoiseCutoff = 0.777f,
_FoamDistance = 0.4f,
_SurfaceNoiseScroll = new float[] { 0.03f, 0.03f },
_SurfaceDistortionAmount = 0.27f
};
case "toon":
case "cel":
return new
{
_BaseColor = new float[] { 1f, 1f, 1f, 1f },
_IndirectLightMinColor = new float[] { 0.1f, 0.1f, 0.1f, 1f },
_CelShadeMidPoint = 0f,
_CelShadeSoftness = 0.05f,
_OutlineWidth = 1f,
_OutlineColor = new float[] { 0f, 0f, 0f, 1f }
};
case "hologram":
return new
{
_Color = new float[] { 0f, 0.8f, 1f, 1f },
_RimPower = 2f,
_GlitchSpeed = 1f,
_GlitchIntensity = 0.1f,
_ScanLineSpeed = 1f,
_ScanLineDensity = 50f
};
case "forcefield":
case "force_field":
case "shield":
return new
{
_Color = new float[] { 0f, 0.5f, 1f, 0.5f },
_FresnelPower = 3f,
_PulseSpeed = 1f,
_NoiseScale = 5f,
_Distortion = 0.1f
};
case "fresnel":
case "rim":
case "rimlight":
return new
{
_BaseColor = new float[] { 0.2f, 0.2f, 0.2f, 1f },
_RimColor = new float[] { 0f, 0.8f, 1f, 1f },
_RimPower = 3f,
_RimIntensity = 1f
};
case "dissolve":
return new
{
_BaseColor = new float[] { 1f, 1f, 1f, 1f },
_DissolveAmount = 0f,
_EdgeColor = new float[] { 1f, 0.5f, 0f, 1f },
_EdgeWidth = 0.05f,
_NoiseScale = 10f
};
default:
return new { };
}
}
private string GenerateLitShader(ShaderGraphController controller)
{
var sb = new StringBuilder();
sb.AppendLine("// Generated Lit Shader");
sb.AppendLine($"Shader \"NexusGenerated/{controller.name}_Lit\"");
sb.AppendLine("{");
sb.AppendLine(" Properties");
sb.AppendLine(" {");
sb.AppendLine(" _MainTex (\"Texture\", 2D) = \"white\" {}");
sb.AppendLine(" _Color (\"Color\", Color) = (1,1,1,1)");
if (controller.features.Contains("normal_map"))
{
sb.AppendLine(" _NormalMap (\"Normal Map\", 2D) = \"bump\" {}");
sb.AppendLine(" _NormalStrength (\"Normal Strength\", Range(0,1)) = 1.0");
}
if (controller.features.Contains("metallic"))
{
sb.AppendLine(" _Metallic (\"Metallic\", Range(0,1)) = 0.0");
}
if (controller.features.Contains("roughness"))
{
sb.AppendLine(" _Roughness (\"Roughness\", Range(0,1)) = 0.5");
}
if (controller.features.Contains("emission"))
{
sb.AppendLine(" _EmissionColor (\"Emission Color\", Color) = (0,0,0,0)");
sb.AppendLine(" _EmissionMap (\"Emission Map\", 2D) = \"black\" {}");
}
sb.AppendLine(" }");
sb.AppendLine(" // Shader implementation...");
sb.AppendLine("}");
return sb.ToString();
}
private string GenerateUnlitShader(ShaderGraphController controller)
{
return "// Generated Unlit Shader";
}
private string GenerateWaterShader(ShaderGraphController controller)
{
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version - Procedural Caustics, No Texture Required
shaderCode = @"Shader ""Synaptic/URP/Water""
{
Properties
{
[Header(Water Color)]
_ShallowColor (""Shallow Water Color"", Color) = (0.4, 0.75, 0.65, 0.4)
_DeepColor (""Deep Water Color"", Color) = (0.1, 0.35, 0.3, 0.85)
_DepthMaxDistance (""Depth Maximum Distance"", Float) = 3
_DepthFalloff (""Depth Falloff"", Range(0.1, 5)) = 1.5
_FresnelPower (""Fresnel Power"", Range(0, 10)) = 3
[Header(Gerstner Waves)]
_WaveA (""Wave A (dir, steepness, wavelength)"", Vector) = (1,0,0.25,8)
_WaveB (""Wave B"", Vector) = (0,1,0.15,12)
_WaveC (""Wave C"", Vector) = (1,1,0.1,6)
_WaveD (""Wave D"", Vector) = (-1,0.5,0.08,10)
[Header(Procedural Caustics)]
_CausticsStrength (""Caustics Strength"", Range(0, 3)) = 1.2
_CausticsScale (""Caustics Scale"", Range(0.1, 10)) = 2.5
_CausticsSpeed (""Caustics Speed"", Range(0, 2)) = 0.3
_CausticsDepthFade (""Caustics Depth Fade"", Float) = 4
[Header(Procedural Foam)]
_FoamColor (""Foam Color"", Color) = (1,1,1,1)
_FoamAmount (""Foam Shore Distance"", Range(0, 5)) = 1.5
_FoamScale (""Foam Noise Scale"", Range(1, 50)) = 15
_FoamSharpness (""Foam Sharpness"", Range(0, 1)) = 0.6
[Header(Surface Waves)]
_WaveScale (""Wave Scale"", Range(0.1, 10)) = 2
_WaveSpeed (""Wave Speed"", Range(0, 2)) = 0.5
_WaveStrength (""Wave Distortion"", Range(0, 0.5)) = 0.15
[Header(Refraction)]
_RefractionStrength (""Refraction Strength"", Range(0, 0.5)) = 0.15
[Header(Specular)]
_SpecularPower (""Specular Power"", Range(1, 500)) = 200
_SpecularStrength (""Specular Strength"", Range(0, 2)) = 0.8
}
SubShader
{
Tags
{
""RenderType"" = ""Transparent""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""Transparent""
}
LOD 300
Pass
{
Name ""WaterForward""
Tags { ""LightMode"" = ""UniversalForward"" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Back
HLSLPROGRAM
#pragma vertex WaterVert
#pragma fragment WaterFrag
#pragma target 4.5
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _ShallowColor;
float4 _DeepColor;
float _DepthMaxDistance;
float _DepthFalloff;
float _FresnelPower;
float4 _WaveA, _WaveB, _WaveC, _WaveD;
float _CausticsStrength;
float _CausticsScale;
float _CausticsSpeed;
float _CausticsDepthFade;
float4 _FoamColor;
float _FoamAmount;
float _FoamScale;
float _FoamSharpness;
float _WaveScale;
float _WaveSpeed;
float _WaveStrength;
float _RefractionStrength;
float _SpecularPower;
float _SpecularStrength;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float4 screenPos : TEXCOORD3;
float3 viewDirWS : TEXCOORD4;
float3 tangentWS : TEXCOORD5;
float3 bitangentWS : TEXCOORD6;
};
// Hash functions for procedural noise
float2 hash22(float2 p)
{
float3 p3 = frac(float3(p.xyx) * float3(0.1031, 0.1030, 0.0973));
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.xx + p3.yz) * p3.zy);
}
float hash21(float2 p)
{
float3 p3 = frac(float3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return frac((p3.x + p3.y) * p3.z);
}
// Voronoi for caustics
float voronoi(float2 uv)
{
float2 g = floor(uv);
float2 f = frac(uv);
float minDist = 1.0;
float minDist2 = 1.0;
for (int y = -1; y <= 1; y++)
{
for (int x = -1; x <= 1; x++)
{
float2 neighbor = float2(x, y);
float2 point = hash22(g + neighbor);
point = 0.5 + 0.5 * sin(_Time.y * _CausticsSpeed * 2.0 + 6.2831 * point);
float2 diff = neighbor + point - f;
float dist = length(diff);
if (dist < minDist)
{
minDist2 = minDist;
minDist = dist;
}
else if (dist < minDist2)
{
minDist2 = dist;
}
}
}
return minDist2 - minDist;
}
// Procedural caustics pattern
float caustics(float2 uv, float time)
{
float2 uv1 = uv * _CausticsScale;
float2 uv2 = uv * _CausticsScale * 1.3 + float2(0.5, 0.3);
// Animate UV
uv1 += float2(time * 0.1, time * 0.07);
uv2 -= float2(time * 0.08, time * 0.12);
// Two layers of voronoi for more complex pattern
float v1 = voronoi(uv1);
float v2 = voronoi(uv2);
// Combine with min for sharp caustic lines
float c = min(v1, v2);
c = pow(c, 0.5) * 2.0;
c = saturate(c);
return c;
}
// Procedural noise for foam and waves
float noise(float2 uv)
{
float2 i = floor(uv);
float2 f = frac(uv);
float a = hash21(i);
float b = hash21(i + float2(1.0, 0.0));
float c = hash21(i + float2(0.0, 1.0));
float d = hash21(i + float2(1.0, 1.0));
float2 u = f * f * (3.0 - 2.0 * f);
return lerp(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
// FBM noise for foam
float fbm(float2 uv)
{
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < 4; i++)
{
value += amplitude * noise(uv * frequency);
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
// Gerstner Wave Function
float3 GerstnerWave(float4 wave, float3 p, inout float3 tangent, inout float3 binormal, float time)
{
float steepness = wave.z;
float wavelength = wave.w;
float k = 2.0 * PI / wavelength;
float c = sqrt(9.8 / k);
float2 d = normalize(wave.xy);
float f = k * (dot(d, p.xz) - c * time);
float a = steepness / k;
tangent += float3(
-d.x * d.x * (steepness * sin(f)),
d.x * (steepness * cos(f)),
-d.x * d.y * (steepness * sin(f))
);
binormal += float3(
-d.x * d.y * (steepness * sin(f)),
d.y * (steepness * cos(f)),
-d.y * d.y * (steepness * sin(f))
);
return float3(
d.x * (a * cos(f)),
a * sin(f),
d.y * (a * cos(f))
);
}
// Procedural normal from noise
float3 GetProceduralNormal(float2 uv, float time)
{
float2 uv1 = uv * _WaveScale + float2(time * _WaveSpeed, time * _WaveSpeed * 0.7);
float2 uv2 = uv * _WaveScale * 0.7 - float2(time * _WaveSpeed * 0.8, time * _WaveSpeed * 0.5);
float eps = 0.01;
float h = noise(uv1) + noise(uv2) * 0.5;
float hx = noise(uv1 + float2(eps, 0)) + noise(uv2 + float2(eps, 0)) * 0.5;
float hy = noise(uv1 + float2(0, eps)) + noise(uv2 + float2(0, eps)) * 0.5;
float3 normal;
normal.x = (h - hx) / eps * _WaveStrength;
normal.y = 1.0;
normal.z = (h - hy) / eps * _WaveStrength;
return normalize(normal);
}
Varyings WaterVert(Attributes input)
{
Varyings output;
float3 posOS = input.positionOS.xyz;
float3 tangent = float3(1, 0, 0);
float3 binormal = float3(0, 0, 1);
float time = _Time.y;
// Apply Gerstner waves
posOS += GerstnerWave(_WaveA, input.positionOS.xyz, tangent, binormal, time);
posOS += GerstnerWave(_WaveB, input.positionOS.xyz, tangent, binormal, time);
posOS += GerstnerWave(_WaveC, input.positionOS.xyz, tangent, binormal, time);
posOS += GerstnerWave(_WaveD, input.positionOS.xyz, tangent, binormal, time);
float3 normalOS = normalize(cross(binormal, tangent));
output.positionWS = TransformObjectToWorld(posOS);
output.normalWS = TransformObjectToWorldNormal(normalOS);
output.positionCS = TransformWorldToHClip(output.positionWS);
output.screenPos = ComputeScreenPos(output.positionCS);
output.uv = input.uv;
output.viewDirWS = GetWorldSpaceNormalizeViewDir(output.positionWS);
output.tangentWS = TransformObjectToWorldDir(input.tangentOS.xyz);
output.bitangentWS = cross(output.normalWS, output.tangentWS) * input.tangentOS.w;
return output;
}
half4 WaterFrag(Varyings input) : SV_Target
{
float time = _Time.y;
// Screen UV
float2 screenUV = input.screenPos.xy / input.screenPos.w;
// Depth calculation
float sceneDepth = LinearEyeDepth(SampleSceneDepth(screenUV), _ZBufferParams);
float surfaceDepth = input.screenPos.w;
float depthDiff = sceneDepth - surfaceDepth;
float depthFactor = saturate(depthDiff / _DepthMaxDistance);
depthFactor = pow(depthFactor, _DepthFalloff);
// Water color based on depth - more natural gradient
half3 shallowCol = _ShallowColor.rgb;
half3 deepCol = _DeepColor.rgb;
half3 waterColor = lerp(shallowCol, deepCol, depthFactor);
half waterAlpha = lerp(_ShallowColor.a, _DeepColor.a, depthFactor);
// Procedural normal
float3 normalTS = GetProceduralNormal(input.positionWS.xz, time);
// Transform normal to world space
float3x3 tangentToWorld = float3x3(input.tangentWS, input.bitangentWS, input.normalWS);
half3 normalWS = normalize(mul(normalTS, tangentToWorld));
// Refraction
float2 refractOffset = normalTS.xz * _RefractionStrength * saturate(depthDiff * 0.5);
float2 refractUV = screenUV + refractOffset;
half3 refractColor = SampleSceneColor(refractUV);
// Procedural Caustics
float causticsValue = caustics(input.positionWS.xz, time * _CausticsSpeed);
float causticsDepthMask = saturate(depthDiff / _CausticsDepthFade);
causticsDepthMask *= (1.0 - depthFactor * 0.7); // Fade in deep water
half3 causticsColor = causticsValue * _CausticsStrength * causticsDepthMask * half3(1.0, 0.95, 0.85);
// Procedural Foam
float foamMask = saturate((_FoamAmount - depthDiff) / _FoamAmount);
float foamNoise = fbm(input.positionWS.xz * _FoamScale + time * 0.5);
foamNoise += fbm(input.positionWS.xz * _FoamScale * 0.5 - time * 0.3) * 0.5;
float foam = smoothstep(_FoamSharpness, _FoamSharpness + 0.2, foamMask * foamNoise);
// Wave crest foam
float waveCrest = saturate(input.positionWS.y * 3.0);
foam = saturate(foam + waveCrest * foamNoise * 0.3);
// Combine colors
half3 baseColor = lerp(refractColor, waterColor, waterAlpha);
baseColor += causticsColor * refractColor; // Caustics multiply with background
half3 finalColor = lerp(baseColor, _FoamColor.rgb, foam);
// Fresnel
half fresnel = pow(1.0 - saturate(dot(input.viewDirWS, normalWS)), _FresnelPower);
// Lighting
Light mainLight = GetMainLight();
half3 lightDir = mainLight.direction;
half3 lightColor = mainLight.color;
// Specular - sun reflection
half3 halfDir = normalize(lightDir + input.viewDirWS);
half spec = pow(saturate(dot(normalWS, halfDir)), _SpecularPower);
half3 specColor = lightColor * spec * _SpecularStrength * fresnel;
finalColor += specColor;
// Environment reflection hint
half3 reflectDir = reflect(-input.viewDirWS, normalWS);
half skyGradient = saturate(reflectDir.y * 0.5 + 0.5);
half3 skyColor = lerp(half3(0.6, 0.7, 0.8), half3(0.3, 0.5, 0.7), skyGradient);
finalColor = lerp(finalColor, skyColor, fresnel * 0.3);
// Alpha
half alpha = lerp(waterAlpha, 1.0, fresnel * 0.4);
alpha = lerp(alpha, 1.0, foam);
alpha = saturate(alpha + depthFactor * 0.3);
return half4(finalColor, alpha);
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/Water""
{
Properties
{
// Main Color & Transparency
[Header(Water Color)]
_ShallowColor (""Shallow Water Color"", Color) = (0.325, 0.807, 0.971, 0.725)
_DeepColor (""Deep Water Color"", Color) = (0.086, 0.407, 1, 0.749)
_DepthMaxDistance (""Depth Maximum Distance"", Float) = 1
_FresnelPower (""Fresnel Power"", Range(0, 10)) = 5
// Waves (Gerstner)
[Header(Gerstner Waves)]
_WaveA (""Wave A (dir, steepness, wavelength)"", Vector) = (1,0,0.5,10)
_WaveB (""Wave B"", Vector) = (0,1,0.25,20)
_WaveC (""Wave C"", Vector) = (1,1,0.15,10)
_WaveD (""Wave D"", Vector) = (-1,0.5,0.1,15)
// Foam
[Header(Foam)]
_FoamColor (""Foam Color"", Color) = (1,1,1,1)
_FoamAmount (""Foam Amount"", Range(0, 10)) = 2.66
_FoamCutoff (""Foam Cutoff"", Range(0, 10)) = 0.777
_FoamNoiseScale (""Foam Noise Scale"", Float) = 10
_FoamNoiseSpeed (""Foam Noise Speed"", Vector) = (0.3, 0.3, 0, 0)
_FoamNoiseTex (""Foam Noise Texture"", 2D) = ""white"" {}
// Caustics
[Header(Caustics)]
_CausticsTex (""Caustics Texture"", 2D) = ""black"" {}
_CausticsStrength (""Caustics Strength"", Range(0, 5)) = 1.5
_CausticsScale (""Caustics Scale"", Float) = 1
_CausticsSpeed (""Caustics Speed"", Vector) = (0.05, 0.05, 0, 0)
_CausticsDepthFade (""Caustics Depth Fade"", Float) = 5
// Surface
[Header(Surface)]
_Smoothness (""Smoothness"", Range(0, 1)) = 0.95
_NormalMap (""Normal Map"", 2D) = ""bump"" {}
_NormalStrength (""Normal Strength"", Range(0, 2)) = 0.5
_NormalScale (""Normal Scale"", Float) = 1
_NormalSpeed (""Normal Speed"", Vector) = (0.03, 0.03, 0, 0)
// Refraction
[Header(Refraction)]
_RefractionStrength (""Refraction Strength"", Range(0, 1)) = 0.25
// Specular
[Header(Specular)]
_SpecularSmoothness (""Specular Smoothness"", Range(0, 1)) = 0.9
}
SubShader
{
Tags
{
""Queue""=""Transparent""
""RenderType""=""Transparent""
}
LOD 200
GrabPass { ""_WaterBackground"" }
CGPROGRAM
#pragma surface surf StandardSpecular alpha:fade vertex:vert
#pragma target 4.0
#include ""UnityCG.cginc""
// Properties
float4 _ShallowColor;
float4 _DeepColor;
float _DepthMaxDistance;
float _FresnelPower;
// Gerstner Waves
float4 _WaveA, _WaveB, _WaveC, _WaveD;
// Foam
float4 _FoamColor;
float _FoamAmount;
float _FoamCutoff;
float _FoamNoiseScale;
float2 _FoamNoiseSpeed;
sampler2D _FoamNoiseTex;
// Caustics
sampler2D _CausticsTex;
float _CausticsStrength;
float _CausticsScale;
float2 _CausticsSpeed;
float _CausticsDepthFade;
// Surface
float _Smoothness;
sampler2D _NormalMap;
float _NormalStrength;
float _NormalScale;
float2 _NormalSpeed;
// Refraction
sampler2D _WaterBackground;
sampler2D _CameraDepthTexture;
float _RefractionStrength;
// Specular
float _SpecularSmoothness;
struct Input
{
float2 uv_NormalMap;
float4 screenPos;
float3 worldPos;
float3 viewDir;
float eyeDepth;
};
// Gerstner Wave Function
float3 GerstnerWave(float4 wave, float3 p, inout float3 tangent, inout float3 binormal)
{
float steepness = wave.z;
float wavelength = wave.w;
float k = 2 * UNITY_PI / wavelength;
float c = sqrt(9.8 / k);
float2 d = normalize(wave.xy);
float f = k * (dot(d, p.xz) - c * _Time.y);
float a = steepness / k;
tangent += float3(
-d.x * d.x * (steepness * sin(f)),
d.x * (steepness * cos(f)),
-d.x * d.y * (steepness * sin(f))
);
binormal += float3(
-d.x * d.y * (steepness * sin(f)),
d.y * (steepness * cos(f)),
-d.y * d.y * (steepness * sin(f))
);
return float3(
d.x * (a * cos(f)),
a * sin(f),
d.y * (a * cos(f))
);
}
void vert(inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input, o);
float3 gridPoint = v.vertex.xyz;
float3 tangent = float3(1, 0, 0);
float3 binormal = float3(0, 0, 1);
float3 p = gridPoint;
// Apply 4 Gerstner waves
p += GerstnerWave(_WaveA, gridPoint, tangent, binormal);
p += GerstnerWave(_WaveB, gridPoint, tangent, binormal);
p += GerstnerWave(_WaveC, gridPoint, tangent, binormal);
p += GerstnerWave(_WaveD, gridPoint, tangent, binormal);
float3 normal = normalize(cross(binormal, tangent));
v.vertex.xyz = p;
v.normal = normal;
COMPUTE_EYEDEPTH(o.eyeDepth);
}
void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
// Depth calculation
float depth = tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(IN.screenPos)).r;
depth = LinearEyeDepth(depth);
float surfaceDepth = UNITY_Z_0_FAR_FROM_CLIPSPACE(IN.screenPos.z);
float depthDifference = depth - surfaceDepth;
// Water color based on depth
float waterDepthFactor = saturate(depthDifference / _DepthMaxDistance);
float4 waterColor = lerp(_ShallowColor, _DeepColor, waterDepthFactor);
// Normal mapping (dual layer)
float2 uv1 = IN.uv_NormalMap * _NormalScale + _NormalSpeed * _Time.y;
float2 uv2 = IN.uv_NormalMap * _NormalScale * 0.7 - _NormalSpeed * _Time.y * 0.8;
float3 normal1 = UnpackNormal(tex2D(_NormalMap, uv1));
float3 normal2 = UnpackNormal(tex2D(_NormalMap, uv2));
float3 normal = normalize(normal1 + normal2) * float3(_NormalStrength, _NormalStrength, 1);
o.Normal = normal;
// Refraction
float2 refractOffset = normal.xy * _RefractionStrength * saturate(depthDifference);
float2 refractUV = (IN.screenPos.xy + refractOffset) / IN.screenPos.w;
float3 refractColor = tex2D(_WaterBackground, refractUV).rgb;
// Caustics (dual layer triplanar)
float2 causticsUV1 = IN.worldPos.xz * _CausticsScale + _CausticsSpeed * _Time.y;
float2 causticsUV2 = IN.worldPos.xz * _CausticsScale * 1.3 - _CausticsSpeed * _Time.y * 0.7;
float caustics1 = tex2D(_CausticsTex, causticsUV1).r;
float caustics2 = tex2D(_CausticsTex, causticsUV2).r;
float caustics = min(caustics1, caustics2) * _CausticsStrength;
caustics *= saturate(depthDifference / _CausticsDepthFade);
// Foam (depth + wave crest)
float foamDepth = saturate(_FoamAmount - depthDifference);
float2 foamUV1 = IN.worldPos.xz * _FoamNoiseScale + _FoamNoiseSpeed * _Time.y;
float2 foamUV2 = IN.worldPos.xz * _FoamNoiseScale * 0.7 - _FoamNoiseSpeed * _Time.y * 0.5;
float foamNoise1 = tex2D(_FoamNoiseTex, foamUV1).r;
float foamNoise2 = tex2D(_FoamNoiseTex, foamUV2).r;
float foamNoise = foamNoise1 * foamNoise2;
float foam = step(_FoamCutoff, foamDepth * foamNoise);
// Wave crest foam
float waveCrestFoam = saturate((IN.worldPos.y - 0) * 2) * foamNoise;
foam = saturate(foam + waveCrestFoam * 0.5);
// Combine colors
float3 baseColor = lerp(refractColor, waterColor.rgb, waterColor.a);
baseColor += caustics;
o.Albedo = lerp(baseColor, _FoamColor.rgb, foam);
// Fresnel
float fresnel = pow(1.0 - saturate(dot(normalize(IN.viewDir), o.Normal)), _FresnelPower);
// Specular
o.Specular = lerp(float3(0.04, 0.04, 0.04), float3(1, 1, 1), fresnel);
o.Smoothness = lerp(_Smoothness, _SpecularSmoothness, fresnel);
// Alpha
o.Alpha = lerp(waterColor.a, 1.0, fresnel * 0.5);
}
ENDCG
}
FallBack ""Transparent/Diffuse""
}
";
}
return SaveShaderAndCreateMaterial("Water", shaderCode);
}
private string GenerateGlassShader(ShaderGraphController controller)
{
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version - Uses _CameraOpaqueTexture instead of GrabPass
shaderCode = @"Shader ""Synaptic/URP/Glass""
{
Properties
{
[Header(Glass Color)]
_Color (""Glass Tint"", Color) = (0.9, 0.95, 1.0, 0.1)
_Transparency (""Transparency"", Range(0, 1)) = 0.85
[Header(Refraction)]
_IOR (""Index of Refraction"", Range(1.0, 2.5)) = 1.52
_RefractionStrength (""Refraction Strength"", Range(0, 1)) = 0.5
_ChromaticAberration (""Chromatic Aberration"", Range(0, 0.1)) = 0.02
[Header(Thickness)]
_ThicknessMap (""Thickness Map"", 2D) = ""white"" {}
_Thickness (""Thickness"", Range(0, 1)) = 0.5
_AbsorptionColor (""Absorption Color"", Color) = (0.7, 0.9, 1.0, 1.0)
_AbsorptionStrength (""Absorption Strength"", Range(0, 5)) = 1.0
[Header(Surface)]
_BumpMap (""Normal Map"", 2D) = ""bump"" {}
_BumpScale (""Normal Strength"", Range(0, 2)) = 0.3
_Smoothness (""Smoothness"", Range(0, 1)) = 0.98
[Header(Edge Effects)]
_FresnelPower (""Fresnel Power"", Range(1, 10)) = 5
_EdgeThickness (""Edge Thickness"", Range(0, 1)) = 0.5
[Header(Details)]
_DirtTex (""Dirt Texture"", 2D) = ""black"" {}
_DirtStrength (""Dirt Strength"", Range(0, 1)) = 0.0
_FrostAmount (""Frost Amount"", Range(0, 1)) = 0.0
}
SubShader
{
Tags
{
""RenderType"" = ""Transparent""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""Transparent""
}
LOD 300
Pass
{
Name ""GlassForward""
Tags { ""LightMode"" = ""UniversalForward"" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Back
HLSLPROGRAM
#pragma vertex GlassVert
#pragma fragment GlassFrag
#pragma target 4.5
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _Color;
float _Transparency;
float _IOR;
float _RefractionStrength;
float _ChromaticAberration;
float _Thickness;
float4 _AbsorptionColor;
float _AbsorptionStrength;
float _BumpScale;
float _Smoothness;
float _FresnelPower;
float _EdgeThickness;
float _DirtStrength;
float _FrostAmount;
float4 _BumpMap_ST;
float4 _ThicknessMap_ST;
float4 _DirtTex_ST;
CBUFFER_END
TEXTURE2D(_BumpMap);
SAMPLER(sampler_BumpMap);
TEXTURE2D(_ThicknessMap);
SAMPLER(sampler_ThicknessMap);
TEXTURE2D(_DirtTex);
SAMPLER(sampler_DirtTex);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float4 screenPos : TEXCOORD3;
float3 viewDirWS : TEXCOORD4;
float3 tangentWS : TEXCOORD5;
float3 bitangentWS : TEXCOORD6;
};
// Schlick's Fresnel approximation
float FresnelSchlick(float3 viewDir, float3 normal, float ior)
{
float f0 = pow((1.0 - ior) / (1.0 + ior), 2.0);
float cosTheta = saturate(dot(viewDir, normal));
return f0 + (1.0 - f0) * pow(1.0 - cosTheta, _FresnelPower);
}
half3 UnpackNormalScale(half4 packedNormal, half scale)
{
half3 normal;
normal.xy = (packedNormal.xy * 2.0 - 1.0) * scale;
normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy)));
return normal;
}
Varyings GlassVert(Attributes input)
{
Varyings output;
output.positionWS = TransformObjectToWorld(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(output.positionWS);
output.screenPos = ComputeScreenPos(output.positionCS);
output.uv = TRANSFORM_TEX(input.uv, _BumpMap);
output.viewDirWS = GetWorldSpaceNormalizeViewDir(output.positionWS);
output.tangentWS = TransformObjectToWorldDir(input.tangentOS.xyz);
output.bitangentWS = cross(output.normalWS, output.tangentWS) * input.tangentOS.w;
return output;
}
half4 GlassFrag(Varyings input) : SV_Target
{
// Normal mapping
half3 normalTS = UnpackNormalScale(SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, input.uv), _BumpScale);
float3x3 tangentToWorld = float3x3(input.tangentWS, input.bitangentWS, input.normalWS);
half3 normalWS = normalize(mul(normalTS, tangentToWorld));
// Fresnel effect with IOR
float fresnel = FresnelSchlick(input.viewDirWS, normalWS, _IOR);
// Thickness-based absorption
float thickness = SAMPLE_TEXTURE2D(_ThicknessMap, sampler_ThicknessMap, input.uv).r * _Thickness;
half3 absorption = lerp(half3(1,1,1), _AbsorptionColor.rgb, saturate(thickness * _AbsorptionStrength));
// Screen UV
float2 screenUV = input.screenPos.xy / input.screenPos.w;
// Refraction with chromatic aberration
float2 refractOffset = normalWS.xy * _RefractionStrength * (1.0 - fresnel);
// Sample RGB channels separately for chromatic aberration
float2 uvR = screenUV + refractOffset * (1.0 + _ChromaticAberration);
float2 uvG = screenUV + refractOffset;
float2 uvB = screenUV + refractOffset * (1.0 - _ChromaticAberration);
half r = SampleSceneColor(uvR).r;
half g = SampleSceneColor(uvG).g;
half b = SampleSceneColor(uvB).b;
half3 refractColor = half3(r, g, b);
// Apply thickness absorption
refractColor *= absorption;
// Combine with glass tint
half3 glassColor = lerp(refractColor, _Color.rgb, _Color.a);
// Dirt overlay
half dirt = SAMPLE_TEXTURE2D(_DirtTex, sampler_DirtTex, input.uv).r;
glassColor = lerp(glassColor, glassColor * 0.5, dirt * _DirtStrength);
// Frost effect
if (_FrostAmount > 0.001)
{
float2 frostUV = input.uv * 10;
half frost = SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, frostUV).r;
frost = pow(frost, 2);
glassColor = lerp(glassColor, half3(1,1,1), frost * _FrostAmount);
}
// Lighting
Light mainLight = GetMainLight();
half3 halfDir = normalize(mainLight.direction + input.viewDirWS);
half spec = pow(saturate(dot(normalWS, halfDir)), _Smoothness * 128.0);
half3 specColor = mainLight.color * spec * fresnel;
half3 finalColor = glassColor + specColor;
// Edge highlighting with fresnel
float edgeGlow = pow(fresnel, _EdgeThickness + 1);
finalColor += _Color.rgb * edgeGlow * 0.1;
// Alpha with fresnel edge
half alpha = lerp(_Transparency, 1.0, fresnel * 0.3);
return half4(finalColor, alpha);
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/Glass""
{
Properties
{
[Header(Glass Color)]
_Color (""Glass Tint"", Color) = (0.9, 0.95, 1.0, 0.1)
_Transparency (""Transparency"", Range(0, 1)) = 0.85
[Header(Refraction)]
_IOR (""Index of Refraction"", Range(1.0, 2.5)) = 1.52
_RefractionStrength (""Refraction Strength"", Range(0, 1)) = 0.5
_ChromaticAberration (""Chromatic Aberration"", Range(0, 0.1)) = 0.02
[Header(Thickness)]
_ThicknessMap (""Thickness Map"", 2D) = ""white"" {}
_Thickness (""Thickness"", Range(0, 1)) = 0.5
_AbsorptionColor (""Absorption Color"", Color) = (0.7, 0.9, 1.0, 1.0)
_AbsorptionStrength (""Absorption Strength"", Range(0, 5)) = 1.0
[Header(Surface)]
_BumpMap (""Normal Map"", 2D) = ""bump"" {}
_BumpScale (""Normal Strength"", Range(0, 2)) = 0.3
_Smoothness (""Smoothness"", Range(0, 1)) = 0.98
[Header(Edge Effects)]
_FresnelPower (""Fresnel Power"", Range(1, 10)) = 5
_EdgeThickness (""Edge Thickness"", Range(0, 1)) = 0.5
[Header(Details)]
_DirtTex (""Dirt Texture"", 2D) = ""black"" {}
_DirtStrength (""Dirt Strength"", Range(0, 1)) = 0.0
_FrostAmount (""Frost Amount"", Range(0, 1)) = 0.0
}
SubShader
{
Tags
{
""Queue""=""Transparent""
""RenderType""=""Transparent""
""IgnoreProjector""=""True""
}
LOD 300
GrabPass { ""_GlassGrabTexture"" }
CGPROGRAM
#pragma surface surf StandardSpecular alpha:fade vertex:vert
#pragma target 4.0
#include ""UnityCG.cginc""
// Properties
float4 _Color;
float _Transparency;
float _IOR;
float _RefractionStrength;
float _ChromaticAberration;
sampler2D _ThicknessMap;
float _Thickness;
float4 _AbsorptionColor;
float _AbsorptionStrength;
sampler2D _BumpMap;
float _BumpScale;
float _Smoothness;
float _FresnelPower;
float _EdgeThickness;
sampler2D _DirtTex;
float _DirtStrength;
float _FrostAmount;
sampler2D _GlassGrabTexture;
sampler2D _CameraDepthTexture;
struct Input
{
float2 uv_BumpMap;
float2 uv_ThicknessMap;
float2 uv_DirtTex;
float4 screenPos;
float3 viewDir;
float3 worldNormal;
INTERNAL_DATA
};
void vert(inout appdata_full v)
{
// Vertex shader pass-through
}
// Schlick's Fresnel approximation
float FresnelSchlick(float3 viewDir, float3 normal, float ior)
{
float f0 = pow((1.0 - ior) / (1.0 + ior), 2.0);
float cosTheta = saturate(dot(viewDir, normal));
return f0 + (1.0 - f0) * pow(1.0 - cosTheta, _FresnelPower);
}
void surf(Input IN, inout SurfaceOutputStandardSpecular o)
{
// Normal mapping
float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
normal.xy *= _BumpScale;
o.Normal = normalize(normal);
float3 worldNormal = WorldNormalVector(IN, o.Normal);
float3 viewDir = normalize(IN.viewDir);
// Fresnel effect with IOR
float fresnel = FresnelSchlick(viewDir, worldNormal, _IOR);
// Thickness-based absorption
float thickness = tex2D(_ThicknessMap, IN.uv_ThicknessMap).r * _Thickness;
float3 absorption = lerp(float3(1,1,1), _AbsorptionColor.rgb,
saturate(thickness * _AbsorptionStrength));
// Refraction with chromatic aberration
float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
float2 refractOffset = worldNormal.xy * _RefractionStrength * (1.0 - fresnel);
// Sample RGB channels separately for chromatic aberration
float2 uvR = screenUV + refractOffset * (1.0 + _ChromaticAberration);
float2 uvG = screenUV + refractOffset;
float2 uvB = screenUV + refractOffset * (1.0 - _ChromaticAberration);
float r = tex2D(_GlassGrabTexture, uvR).r;
float g = tex2D(_GlassGrabTexture, uvG).g;
float b = tex2D(_GlassGrabTexture, uvB).b;
float3 refractColor = float3(r, g, b);
// Apply thickness absorption
refractColor *= absorption;
// Combine with glass tint
float3 glassColor = lerp(refractColor, _Color.rgb, _Color.a);
// Dirt overlay
float dirt = tex2D(_DirtTex, IN.uv_DirtTex).r;
glassColor = lerp(glassColor, glassColor * 0.5, dirt * _DirtStrength);
// Frost effect
if (_FrostAmount > 0.001)
{
float2 frostUV = IN.uv_BumpMap * 10;
float frost = tex2D(_BumpMap, frostUV).r;
frost = pow(frost, 2);
glassColor = lerp(glassColor, float3(1,1,1), frost * _FrostAmount);
}
o.Albedo = glassColor;
// Edge highlighting with fresnel
float edgeGlow = pow(fresnel, _EdgeThickness + 1);
o.Emission = _Color.rgb * edgeGlow * 0.1;
// Specular
o.Specular = lerp(float3(0.04, 0.04, 0.04), float3(1, 1, 1), fresnel);
o.Smoothness = _Smoothness;
// Alpha with fresnel edge
o.Alpha = lerp(_Transparency, 1.0, fresnel * 0.3);
}
ENDCG
}
FallBack ""Transparent/Diffuse""
}
";
}
return SaveShaderAndCreateMaterial("Glass", shaderCode);
}
private string GenerateMetalShader(ShaderGraphController controller)
{
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version with Anisotropic Reflection and PBR
shaderCode = @"Shader ""Synaptic/URP/Metal""
{
Properties
{
[Header(Base Metal)]
_Color (""Base Color"", Color) = (0.8, 0.8, 0.8, 1.0)
_MainTex (""Albedo (RGB)"", 2D) = ""white"" {}
_Metallic (""Metallic"", Range(0, 1)) = 1.0
_Smoothness (""Smoothness"", Range(0, 1)) = 0.9
[Header(PBR Maps)]
_MetallicGlossMap (""Metallic"", 2D) = ""white"" {}
_BumpMap (""Normal Map"", 2D) = ""bump"" {}
_BumpScale (""Normal Strength"", Range(0, 2)) = 1.0
_OcclusionMap (""Occlusion"", 2D) = ""white"" {}
_OcclusionStrength (""Occlusion Strength"", Range(0, 1)) = 1.0
[Header(Anisotropic Reflection)]
_Anisotropy (""Anisotropy"", Range(-1, 1)) = 0.8
_AnisotropyAngle (""Anisotropy Rotation"", Range(0, 360)) = 0
[Header(Clearcoat Layer)]
_ClearCoat (""Clearcoat Strength"", Range(0, 1)) = 0.0
_ClearCoatSmoothness (""Clearcoat Smoothness"", Range(0, 1)) = 0.95
_ClearCoatMask (""Clearcoat Mask"", 2D) = ""white"" {}
[Header(Reflection)]
_ReflectionStrength (""Reflection Strength"", Range(0, 1)) = 1.0
}
SubShader
{
Tags
{
""RenderType"" = ""Opaque""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""Geometry""
}
LOD 300
Pass
{
Name ""MetalForward""
Tags { ""LightMode"" = ""UniversalForward"" }
HLSLPROGRAM
#pragma vertex MetalVert
#pragma fragment MetalFrag
#pragma target 4.5
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS
#pragma multi_compile_fragment _ _SHADOWS_SOFT
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _Color;
float _Metallic;
float _Smoothness;
float _BumpScale;
float _OcclusionStrength;
float _Anisotropy;
float _AnisotropyAngle;
float _ClearCoat;
float _ClearCoatSmoothness;
float _ReflectionStrength;
float4 _MainTex_ST;
float4 _BumpMap_ST;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
TEXTURE2D(_MetallicGlossMap);
SAMPLER(sampler_MetallicGlossMap);
TEXTURE2D(_BumpMap);
SAMPLER(sampler_BumpMap);
TEXTURE2D(_OcclusionMap);
SAMPLER(sampler_OcclusionMap);
TEXTURE2D(_ClearCoatMask);
SAMPLER(sampler_ClearCoatMask);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float3 tangentWS : TEXCOORD3;
float3 bitangentWS : TEXCOORD4;
float3 viewDirWS : TEXCOORD5;
};
half3 UnpackNormalScale(half4 packedNormal, half scale)
{
half3 normal;
normal.xy = (packedNormal.xy * 2.0 - 1.0) * scale;
normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy)));
return normal;
}
// Anisotropic GGX Distribution
float D_GGX_Anisotropic(float NoH, float ToH, float BoH, float roughnessT, float roughnessB)
{
float a2 = roughnessT * roughnessB;
float3 v = float3(roughnessB * ToH, roughnessT * BoH, a2 * NoH);
float v2 = dot(v, v);
float w2 = a2 / v2;
return a2 * w2 * w2 * (1.0 / PI);
}
Varyings MetalVert(Attributes input)
{
Varyings output;
output.positionWS = TransformObjectToWorld(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(output.positionWS);
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
output.viewDirWS = GetWorldSpaceNormalizeViewDir(output.positionWS);
output.tangentWS = TransformObjectToWorldDir(input.tangentOS.xyz);
output.bitangentWS = cross(output.normalWS, output.tangentWS) * input.tangentOS.w;
return output;
}
half4 MetalFrag(Varyings input) : SV_Target
{
// Albedo
half4 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv) * _Color;
// Metallic & Smoothness
half4 metalGloss = SAMPLE_TEXTURE2D(_MetallicGlossMap, sampler_MetallicGlossMap, input.uv);
half metallic = metalGloss.r * _Metallic;
half smoothness = metalGloss.a * _Smoothness;
half roughness = 1.0 - smoothness;
// Normal mapping
half3 normalTS = UnpackNormalScale(SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, input.uv), _BumpScale);
float3x3 tangentToWorld = float3x3(input.tangentWS, input.bitangentWS, input.normalWS);
half3 normalWS = normalize(mul(normalTS, tangentToWorld));
// Occlusion
half occlusion = lerp(1.0, SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, input.uv).g, _OcclusionStrength);
// Anisotropic tangent rotation
float angle = _AnisotropyAngle * PI / 180.0;
float cosAngle = cos(angle);
float sinAngle = sin(angle);
float3 tangentWS = input.tangentWS * cosAngle + input.bitangentWS * sinAngle;
float3 bitangentWS = cross(normalWS, tangentWS);
// Main light
Light mainLight = GetMainLight();
half3 lightDir = mainLight.direction;
half3 lightColor = mainLight.color;
half3 halfDir = normalize(lightDir + input.viewDirWS);
// Anisotropic specular
float NoH = saturate(dot(normalWS, halfDir));
float NoV = saturate(dot(normalWS, input.viewDirWS));
float NoL = saturate(dot(normalWS, lightDir));
float ToH = dot(tangentWS, halfDir);
float BoH = dot(bitangentWS, halfDir);
float roughnessT = roughness * (1.0 + abs(_Anisotropy));
float roughnessB = roughness * (1.0 - abs(_Anisotropy));
if (_Anisotropy < 0)
{
float temp = roughnessT;
roughnessT = roughnessB;
roughnessB = temp;
}
float D = D_GGX_Anisotropic(NoH, ToH, BoH, roughnessT, roughnessB);
// F0 for metals
half3 f0 = lerp(half3(0.04, 0.04, 0.04), albedo.rgb, metallic);
// Fresnel
half fresnel = pow(1.0 - NoV, 5.0);
half3 F = f0 + (1.0 - f0) * fresnel;
// Diffuse
half3 diffuse = albedo.rgb * (1.0 - metallic) * lightColor * NoL;
// Specular
half3 specular = D * F * lightColor * NoL;
// Clearcoat
half clearcoatMask = SAMPLE_TEXTURE2D(_ClearCoatMask, sampler_ClearCoatMask, input.uv).r;
half clearcoat = _ClearCoat * clearcoatMask;
if (clearcoat > 0.01)
{
float clearcoatRoughness = 1.0 - _ClearCoatSmoothness;
float clearcoatD = (clearcoatRoughness * clearcoatRoughness) / (PI * pow(NoH * NoH * (clearcoatRoughness * clearcoatRoughness - 1.0) + 1.0, 2.0));
half3 clearcoatSpec = clearcoatD * lightColor * NoL * clearcoat * 0.04;
specular += clearcoatSpec;
}
// Environment reflection
half3 reflectDir = reflect(-input.viewDirWS, normalWS);
half3 envReflection = GlossyEnvironmentReflection(reflectDir, roughness, occlusion);
half3 indirectSpec = envReflection * F * _ReflectionStrength;
// Final color
half3 finalColor = (diffuse + specular) * occlusion + indirectSpec;
return half4(finalColor, 1.0);
}
ENDHLSL
}
// Shadow caster pass
Pass
{
Name ""ShadowCaster""
Tags { ""LightMode"" = ""ShadowCaster"" }
ZWrite On
ZTest LEqual
ColorMask 0
HLSLPROGRAM
#pragma vertex ShadowVert
#pragma fragment ShadowFrag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
};
float3 _LightDirection;
Varyings ShadowVert(Attributes input)
{
Varyings output;
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
return output;
}
half4 ShadowFrag(Varyings input) : SV_Target
{
return 0;
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/Metal""
{
Properties
{
[Header(Base Metal)]
_Color (""Base Color"", Color) = (0.8, 0.8, 0.8, 1.0)
_MainTex (""Albedo (RGB)"", 2D) = ""white"" {}
_Metallic (""Metallic"", Range(0, 1)) = 1.0
_Smoothness (""Smoothness"", Range(0, 1)) = 0.9
[Header(PBR Maps)]
_MetallicGlossMap (""Metallic"", 2D) = ""white"" {}
_BumpMap (""Normal Map"", 2D) = ""bump"" {}
_BumpScale (""Normal Strength"", Range(0, 2)) = 1.0
_OcclusionMap (""Occlusion"", 2D) = ""white"" {}
_OcclusionStrength (""Occlusion Strength"", Range(0, 1)) = 1.0
[Header(Anisotropic Reflection)]
_Anisotropy (""Anisotropy"", Range(-1, 1)) = 0.8
_AnisotropyAngle (""Anisotropy Rotation"", Range(0, 360)) = 0
_AnisotropicTangentMap (""Tangent Map"", 2D) = ""bump"" {}
[Header(Clearcoat Layer)]
_ClearCoat (""Clearcoat Strength"", Range(0, 1)) = 0.0
_ClearCoatSmoothness (""Clearcoat Smoothness"", Range(0, 1)) = 0.95
_ClearCoatMask (""Clearcoat Mask"", 2D) = ""white"" {}
[Header(Detail Maps)]
_DetailMask (""Detail Mask"", 2D) = ""white"" {}
_DetailAlbedoMap (""Detail Albedo"", 2D) = ""grey"" {}
_DetailNormalMap (""Detail Normal Map"", 2D) = ""bump"" {}
_DetailNormalMapScale (""Detail Normal Scale"", Range(0, 2)) = 1.0
[Header(Reflection)]
_ReflectionCube (""Reflection Cubemap"", Cube) = ""_Skybox"" {}
_ReflectionStrength (""Reflection Strength"", Range(0, 1)) = 1.0
}
SubShader
{
Tags {""RenderType""=""Opaque""}
LOD 300
CGPROGRAM
#pragma surface surf StandardAniso fullforwardshadows addshadow
#pragma target 4.0
#include ""UnityPBSLighting.cginc""
// Properties
sampler2D _MainTex;
sampler2D _MetallicGlossMap;
sampler2D _BumpMap;
sampler2D _OcclusionMap;
sampler2D _AnisotropicTangentMap;
sampler2D _ClearCoatMask;
sampler2D _DetailMask;
sampler2D _DetailAlbedoMap;
sampler2D _DetailNormalMap;
samplerCUBE _ReflectionCube;
fixed4 _Color;
half _Metallic;
half _Smoothness;
float _BumpScale;
float _OcclusionStrength;
float _Anisotropy;
float _AnisotropyAngle;
half _ClearCoat;
half _ClearCoatSmoothness;
float _DetailNormalMapScale;
half _ReflectionStrength;
struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
float2 uv_DetailAlbedoMap;
float3 worldRefl;
float3 worldNormal;
float3 viewDir;
INTERNAL_DATA
};
struct SurfaceOutputStandardAniso
{
fixed3 Albedo;
fixed3 Normal;
half3 Emission;
half Metallic;
half Smoothness;
half Occlusion;
fixed Alpha;
half Anisotropy;
float3 AnisotropicDirection;
half ClearCoat;
half ClearCoatSmoothness;
};
// Custom lighting model for anisotropic reflection
inline half4 LightingStandardAniso(SurfaceOutputStandardAniso s, half3 viewDir, UnityGI gi)
{
// Base PBR lighting
s.Normal = normalize(s.Normal);
half oneMinusReflectivity;
half3 specColor;
s.Albedo = DiffuseAndSpecularFromMetallic(s.Albedo, s.Metallic, specColor, oneMinusReflectivity);
half4 c = UNITY_BRDF_PBS(s.Albedo, specColor, oneMinusReflectivity, s.Smoothness, s.Normal, viewDir, gi.light, gi.indirect);
// Anisotropic specular
if (abs(s.Anisotropy) > 0.01)
{
float3 tangent = s.AnisotropicDirection;
float3 bitangent = cross(s.Normal, tangent);
float3 halfDir = normalize(gi.light.dir + viewDir);
float dotTH = dot(tangent, halfDir);
float dotBH = dot(bitangent, halfDir);
float dotNH = dot(s.Normal, halfDir);
float dotNV = dot(s.Normal, viewDir);
float dotNL = dot(s.Normal, gi.light.dir);
float roughnessT = lerp(1 - s.Smoothness, 0.001, abs(s.Anisotropy));
float roughnessB = 1 - s.Smoothness;
float D = dotTH * dotTH / (roughnessT * roughnessT) + dotBH * dotBH / (roughnessB * roughnessB) + dotNH * dotNH;
D = 1 / (UNITY_PI * roughnessT * roughnessB * D * D);
float anisoSpec = D * gi.light.color * saturate(dotNL);
c.rgb += specColor * anisoSpec * s.Anisotropy * s.Anisotropy;
}
// Clearcoat layer
if (s.ClearCoat > 0.01)
{
half3 clearcoatSpec = specColor * s.ClearCoat;
half clearcoatRoughness = 1.0 - s.ClearCoatSmoothness;
half3 clearcoatRefl = UNITY_BRDF_PBS(half3(0,0,0), clearcoatSpec, 0, s.ClearCoatSmoothness, s.Normal, viewDir, gi.light, gi.indirect);
c.rgb += clearcoatRefl.rgb * s.ClearCoat;
}
return c;
}
inline void LightingStandardAniso_GI(SurfaceOutputStandardAniso s, UnityGIInput data, inout UnityGI gi)
{
Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(s.Smoothness, data.worldViewDir, s.Normal, lerp(unity_ColorSpaceDielectricSpec.rgb, s.Albedo, s.Metallic));
gi = UnityGlobalIllumination(data, s.Occlusion, s.Normal, g);
}
void surf(Input IN, inout SurfaceOutputStandardAniso o)
{
// Albedo
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
half detailMask = tex2D(_DetailMask, IN.uv_MainTex).a;
half3 detailAlbedo = tex2D(_DetailAlbedoMap, IN.uv_DetailAlbedoMap).rgb;
c.rgb = lerp(c.rgb, c.rgb * detailAlbedo * 2, detailMask);
o.Albedo = c.rgb;
// Metallic & Smoothness
half4 metalGloss = tex2D(_MetallicGlossMap, IN.uv_MainTex);
o.Metallic = metalGloss.r * _Metallic;
o.Smoothness = metalGloss.a * _Smoothness;
// Normal mapping with detail
half3 normal = UnpackScaleNormal(tex2D(_BumpMap, IN.uv_BumpMap), _BumpScale);
half3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, IN.uv_DetailAlbedoMap), _DetailNormalMapScale);
normal = lerp(normal, BlendNormals(normal, detailNormal), detailMask);
o.Normal = normal;
// Occlusion
half occ = tex2D(_OcclusionMap, IN.uv_MainTex).g;
o.Occlusion = lerp(1.0, occ, _OcclusionStrength);
// Anisotropic setup
o.Anisotropy = _Anisotropy;
float angle = _AnisotropyAngle * UNITY_PI / 180.0;
float3 tangentWS = normalize(cross(IN.worldNormal, float3(0,0,1)));
float3x3 rotation = float3x3(
cos(angle), -sin(angle), 0,
sin(angle), cos(angle), 0,
0, 0, 1
);
o.AnisotropicDirection = mul(rotation, tangentWS);
// Clearcoat
half clearcoatMask = tex2D(_ClearCoatMask, IN.uv_MainTex).r;
o.ClearCoat = _ClearCoat * clearcoatMask;
o.ClearCoatSmoothness = _ClearCoatSmoothness;
// Reflection
float3 worldRefl = WorldReflectionVector(IN, o.Normal);
half3 reflection = texCUBE(_ReflectionCube, worldRefl).rgb;
o.Emission = reflection * o.Metallic * _ReflectionStrength * 0.1;
o.Alpha = c.a;
}
ENDCG
}
FallBack ""Standard""
}
";
}
return SaveShaderAndCreateMaterial("Metal", shaderCode);
}
private string GenerateFabricShader(ShaderGraphController controller)
{
return "// Generated Fabric Shader with subsurface scattering";
}
private string GenerateCustomShader(ShaderGraphController controller)
{
return "// Generated Custom Shader";
}
// Phase 1 Shaders (Priority 4-10)
private string GenerateHologramShader(ShaderGraphController controller)
{
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version with Glitch Effects, Vertex Displacement, RGB Separation
shaderCode = @"Shader ""Synaptic/URP/Hologram""
{
Properties
{
[Header(Base Hologram)]
_Color (""Hologram Color"", Color) = (0, 1, 1, 0.5)
_MainTex (""Base Texture"", 2D) = ""white"" {}
_Brightness (""Brightness"", Range(0, 5)) = 1.5
[Header(Scanlines)]
_ScanSpeed (""Scan Speed"", Range(0, 10)) = 2.0
_ScanlineWidth (""Scanline Width"", Range(0, 1)) = 0.1
_ScanlineDirection (""Scanline Direction"", Vector) = (0, 1, 0, 0)
_ScanlineFrequency (""Scanline Frequency"", Range(1, 50)) = 10
[Header(Glitch Effects)]
_GlitchIntensity (""Glitch Intensity"", Range(0, 1)) = 0.5
_GlitchFrequency (""Glitch Frequency"", Range(0, 10)) = 2.0
_GlitchBlockSize (""Glitch Block Size"", Range(0.01, 0.5)) = 0.1
_RGBSplitAmount (""RGB Split Amount"", Range(0, 0.1)) = 0.02
[Header(Vertex Glitch)]
_VertexGlitchAmount (""Vertex Glitch Amount"", Range(0, 1)) = 0.3
_VertexGlitchSpeed (""Vertex Glitch Speed"", Range(0, 10)) = 3.0
[Header(Flicker)]
_FlickerSpeed (""Flicker Speed"", Range(0, 20)) = 5.0
_FlickerAmount (""Flicker Amount"", Range(0, 1)) = 0.2
[Header(Fresnel Rim)]
_RimPower (""Rim Power"", Range(0.5, 8.0)) = 3.0
_RimIntensity (""Rim Intensity"", Range(0, 5)) = 2.0
[Header(Pixelation Voxelization)]
_PixelSize (""Pixel Size"", Range(1, 100)) = 1
_VoxelSize (""Voxel Size"", Range(0, 0.5)) = 0.0
[Header(Dot Matrix)]
_DotMatrixSize (""Dot Matrix Size"", Range(0, 50)) = 20
_DotMatrixStrength (""Dot Matrix Strength"", Range(0, 1)) = 0.3
}
SubShader
{
Tags
{
""RenderType"" = ""Transparent""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""Transparent""
}
LOD 300
Pass
{
Name ""HologramForward""
Tags { ""LightMode"" = ""UniversalForward"" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Back
HLSLPROGRAM
#pragma vertex HologramVert
#pragma fragment HologramFrag
#pragma target 4.5
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _Color;
float _Brightness;
float _ScanSpeed;
float _ScanlineWidth;
float4 _ScanlineDirection;
float _ScanlineFrequency;
float _GlitchIntensity;
float _GlitchFrequency;
float _GlitchBlockSize;
float _RGBSplitAmount;
float _VertexGlitchAmount;
float _VertexGlitchSpeed;
float _FlickerSpeed;
float _FlickerAmount;
float _RimPower;
float _RimIntensity;
float _PixelSize;
float _VoxelSize;
float _DotMatrixSize;
float _DotMatrixStrength;
float4 _MainTex_ST;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float3 viewDirWS : TEXCOORD3;
float4 screenPos : TEXCOORD4;
};
// Random function
float rand(float2 co)
{
return frac(sin(dot(co, float2(12.9898, 78.233))) * 43758.5453);
}
// Glitch noise
float glitchNoise(float2 uv, float time, float glitchFreq, float blockSize)
{
float block = floor(time * glitchFreq);
float2 blockUV = floor(uv / blockSize) * blockSize;
return rand(blockUV + block);
}
Varyings HologramVert(Attributes input)
{
Varyings output;
float time = _Time.y * _VertexGlitchSpeed;
float3 posOS = input.positionOS.xyz;
// Vertex glitch
float glitch = glitchNoise(input.uv, time, _GlitchFrequency, _GlitchBlockSize);
if (glitch > 0.9)
{
float3 offset = input.normalOS * _VertexGlitchAmount * (glitch - 0.9) * 10;
posOS += offset * sin(time * 10);
}
output.positionWS = TransformObjectToWorld(posOS);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(output.positionWS);
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
output.viewDirWS = GetWorldSpaceNormalizeViewDir(output.positionWS);
output.screenPos = ComputeScreenPos(output.positionCS);
return output;
}
half4 HologramFrag(Varyings input) : SV_Target
{
float time = _Time.y;
// Pixelation
float2 pixelUV = input.uv;
if (_PixelSize > 1)
{
pixelUV = floor(input.uv * _PixelSize) / _PixelSize;
}
// Screen UV for glitch
float2 screenUV = input.screenPos.xy / input.screenPos.w;
float glitchValue = glitchNoise(screenUV, time, _GlitchFrequency, _GlitchBlockSize);
float2 uvOffset = float2(0, 0);
if (glitchValue > (1.0 - _GlitchIntensity))
{
float glitchAmount = (glitchValue - (1.0 - _GlitchIntensity)) * 10;
uvOffset.x = (rand(floor(time * _GlitchFrequency).xx) - 0.5) * glitchAmount * 0.1;
}
// RGB Split (Chromatic Aberration for glitch)
float splitAmount = _RGBSplitAmount * glitchValue * _GlitchIntensity;
half3 texColor;
texColor.r = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, pixelUV + uvOffset + float2(splitAmount, 0)).r;
texColor.g = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, pixelUV + uvOffset).g;
texColor.b = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, pixelUV + uvOffset - float2(splitAmount, 0)).b;
half4 baseColor = half4(texColor, 1) * _Color;
// Scanline effect
float3 scanDir = normalize(_ScanlineDirection.xyz);
float scanPos = dot(input.positionWS, scanDir);
float scan = frac(scanPos * _ScanlineFrequency - time * _ScanSpeed);
float scanline = step(1.0 - _ScanlineWidth, scan);
// Dot Matrix pattern
float2 dotUV = input.uv * _DotMatrixSize;
float2 dotCenter = frac(dotUV) - 0.5;
float dotDist = length(dotCenter);
float dotPattern = 1.0 - smoothstep(0.3, 0.5, dotDist);
float dotMatrix = lerp(1.0, dotPattern, _DotMatrixStrength);
// Flicker effect
float flicker = 1.0;
flicker *= sin(time * _FlickerSpeed) * _FlickerAmount * 0.5 + (1.0 - _FlickerAmount * 0.5);
flicker *= rand(floor(time * _FlickerSpeed * 0.5).xx) * _FlickerAmount * 0.3 + (1.0 - _FlickerAmount * 0.3);
// Rim light (Fresnel)
half3 normalWS = normalize(input.normalWS);
float rim = 1.0 - saturate(dot(input.viewDirWS, normalWS));
rim = pow(rim, _RimPower) * _RimIntensity;
// Voxelization effect
if (_VoxelSize > 0)
{
float3 voxelPos = floor(input.positionWS / _VoxelSize) * _VoxelSize;
float3 voxelDist = abs(input.positionWS - voxelPos);
float voxelEdge = step(max(max(voxelDist.x, voxelDist.y), voxelDist.z), _VoxelSize * 0.1);
rim += voxelEdge * 0.5;
}
half3 finalColor = baseColor.rgb * dotMatrix;
half3 emission = baseColor.rgb * _Brightness * (scanline * 2 + rim) * flicker * dotMatrix;
// Glitch color shift
if (glitchValue > (1.0 - _GlitchIntensity * 0.5))
{
emission += half3(rand(glitchValue.xx), rand(glitchValue.xx + 0.1), rand(glitchValue.xx + 0.2)) * _GlitchIntensity * 0.5;
}
finalColor += emission;
half alpha = (baseColor.a + rim * 0.5) * flicker;
return half4(finalColor, alpha);
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/Hologram""
{
Properties
{
[Header(Base Hologram)]
_Color (""Hologram Color"", Color) = (0, 1, 1, 0.5)
_MainTex (""Base Texture"", 2D) = ""white"" {}
_Brightness (""Brightness"", Range(0, 5)) = 1.5
[Header(Scanlines)]
_ScanSpeed (""Scan Speed"", Range(0, 10)) = 2.0
_ScanlineWidth (""Scanline Width"", Range(0, 1)) = 0.1
_ScanlineDirection (""Scanline Direction"", Vector) = (0, 1, 0, 0)
_ScanlineFrequency (""Scanline Frequency"", Range(1, 50)) = 10
[Header(Glitch Effects)]
_GlitchIntensity (""Glitch Intensity"", Range(0, 1)) = 0.5
_GlitchFrequency (""Glitch Frequency"", Range(0, 10)) = 2.0
_GlitchBlockSize (""Glitch Block Size"", Range(0.01, 0.5)) = 0.1
_RGBSplitAmount (""RGB Split Amount"", Range(0, 0.1)) = 0.02
[Header(Vertex Glitch)]
_VertexGlitchAmount (""Vertex Glitch Amount"", Range(0, 1)) = 0.3
_VertexGlitchSpeed (""Vertex Glitch Speed"", Range(0, 10)) = 3.0
[Header(Flicker)]
_FlickerSpeed (""Flicker Speed"", Range(0, 20)) = 5.0
_FlickerAmount (""Flicker Amount"", Range(0, 1)) = 0.2
[Header(Fresnel Rim)]
_RimPower (""Rim Power"", Range(0.5, 8.0)) = 3.0
_RimIntensity (""Rim Intensity"", Range(0, 5)) = 2.0
[Header(Pixelation Voxelization)]
_PixelSize (""Pixel Size"", Range(1, 100)) = 1
_VoxelSize (""Voxel Size"", Range(0, 0.5)) = 0.0
[Header(Dot Matrix)]
_DotMatrixSize (""Dot Matrix Size"", Range(0, 50)) = 20
_DotMatrixStrength (""Dot Matrix Strength"", Range(0, 1)) = 0.3
}
SubShader
{
Tags {""Queue""=""Transparent"" ""RenderType""=""Transparent""}
LOD 300
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
CGPROGRAM
#pragma surface surf Standard alpha:fade vertex:vert
#pragma target 4.0
sampler2D _MainTex;
fixed4 _Color;
float _Brightness;
float _ScanSpeed;
float _ScanlineWidth;
float4 _ScanlineDirection;
float _ScanlineFrequency;
float _GlitchIntensity;
float _GlitchFrequency;
float _GlitchBlockSize;
float _RGBSplitAmount;
float _VertexGlitchAmount;
float _VertexGlitchSpeed;
float _FlickerSpeed;
float _FlickerAmount;
float _RimPower;
float _RimIntensity;
float _PixelSize;
float _VoxelSize;
float _DotMatrixSize;
float _DotMatrixStrength;
struct Input
{
float2 uv_MainTex;
float3 worldPos;
float3 viewDir;
float4 screenPos;
};
// Random function
float rand(float2 co)
{
return frac(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453);
}
// Glitch noise
float glitchNoise(float2 uv, float time)
{
float block = floor(time * _GlitchFrequency);
float2 blockUV = floor(uv / _GlitchBlockSize) * _GlitchBlockSize;
return rand(blockUV + block);
}
// Vertex glitch
void vert(inout appdata_full v)
{
float time = _Time.y * _VertexGlitchSpeed;
float glitch = glitchNoise(v.texcoord.xy, time);
if (glitch > 0.9)
{
float3 offset = v.normal * _VertexGlitchAmount * (glitch - 0.9) * 10;
v.vertex.xyz += offset * sin(time * 10);
}
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Pixelation/Voxelization
float2 pixelUV = IN.uv_MainTex;
if (_PixelSize > 1)
{
pixelUV = floor(IN.uv_MainTex * _PixelSize) / _PixelSize;
}
// Screen-space glitch
float glitchValue = glitchNoise(IN.screenPos.xy / IN.screenPos.w, _Time.y);
float2 uvOffset = float2(0, 0);
if (glitchValue > (1.0 - _GlitchIntensity))
{
float glitchAmount = (glitchValue - (1.0 - _GlitchIntensity)) * 10;
uvOffset.x = (rand(floor(_Time.y * _GlitchFrequency).xx) - 0.5) * glitchAmount * 0.1;
}
// RGB Split (Chromatic Aberration for glitch)
float splitAmount = _RGBSplitAmount * glitchValue * _GlitchIntensity;
float3 texColor;
texColor.r = tex2D(_MainTex, pixelUV + uvOffset + float2(splitAmount, 0)).r;
texColor.g = tex2D(_MainTex, pixelUV + uvOffset).g;
texColor.b = tex2D(_MainTex, pixelUV + uvOffset - float2(splitAmount, 0)).b;
fixed4 baseColor = fixed4(texColor, 1) * _Color;
// Scanline effect
float3 scanDir = normalize(_ScanlineDirection.xyz);
float scanPos = dot(IN.worldPos, scanDir);
float scan = frac(scanPos * _ScanlineFrequency - _Time.y * _ScanSpeed);
float scanline = step(1.0 - _ScanlineWidth, scan);
// Dot Matrix pattern
float2 dotUV = IN.uv_MainTex * _DotMatrixSize;
float2 dotCenter = frac(dotUV) - 0.5;
float dotDist = length(dotCenter);
float dotPattern = 1.0 - smoothstep(0.3, 0.5, dotDist);
float dotMatrix = lerp(1.0, dotPattern, _DotMatrixStrength);
// Flicker effect (multiple modes)
float flicker = 1.0;
flicker *= sin(_Time.y * _FlickerSpeed) * _FlickerAmount * 0.5 + (1.0 - _FlickerAmount * 0.5);
flicker *= rand(floor(_Time.y * _FlickerSpeed * 0.5).xx) * _FlickerAmount * 0.3 + (1.0 - _FlickerAmount * 0.3);
// Rim light (Fresnel)
float rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));
rim = pow(rim, _RimPower) * _RimIntensity;
// Voxelization effect
float3 voxelPos = IN.worldPos;
if (_VoxelSize > 0)
{
voxelPos = floor(IN.worldPos / _VoxelSize) * _VoxelSize;
float3 voxelDist = abs(IN.worldPos - voxelPos);
float voxelEdge = step(max(max(voxelDist.x, voxelDist.y), voxelDist.z), _VoxelSize * 0.1);
rim += voxelEdge * 0.5;
}
o.Albedo = baseColor.rgb * dotMatrix;
o.Emission = baseColor.rgb * _Brightness * (scanline * 2 + rim) * flicker * dotMatrix;
o.Alpha = (baseColor.a + rim * 0.5) * flicker;
// Glitch color shift
if (glitchValue > (1.0 - _GlitchIntensity * 0.5))
{
o.Emission += float3(rand(glitchValue.xx), rand(glitchValue.xx + 0.1), rand(glitchValue.xx + 0.2)) * _GlitchIntensity * 0.5;
}
}
ENDCG
}
FallBack ""Transparent/Diffuse""
}
";
}
return SaveShaderAndCreateMaterial("Hologram", shaderCode);
}
private string GenerateToonShader(ShaderGraphController controller)
{
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version with Multiple Shade Layers, MatCap, Advanced Outline
shaderCode = @"Shader ""Synaptic/URP/Toon""
{
Properties
{
[Header(Base)]
_MainTex (""Texture"", 2D) = ""white"" {}
_Color (""Base Color"", Color) = (1, 1, 1, 1)
[Header(Shadow Layers)]
_Shadow1Color (""Shadow 1 Color"", Color) = (0.7, 0.7, 0.7, 1)
_Shadow1Step (""Shadow 1 Step"", Range(0, 1)) = 0.6
_Shadow2Color (""Shadow 2 Color"", Color) = (0.4, 0.4, 0.4, 1)
_Shadow2Step (""Shadow 2 Step"", Range(0, 1)) = 0.3
_ShadowSmoothness (""Shadow Smoothness"", Range(0, 0.5)) = 0.05
[Header(Specular)]
_SpecularColor (""Specular Color"", Color) = (1, 1, 1, 1)
_SpecularSize (""Specular Size"", Range(0, 1)) = 0.1
_SpecularSmoothness (""Specular Smoothness"", Range(0, 0.5)) = 0.05
_SpecularIntensity (""Specular Intensity"", Range(0, 5)) = 1.0
[Header(Rim Light)]
_RimColor (""Rim Color"", Color) = (1, 1, 1, 1)
_RimPower (""Rim Power"", Range(0.5, 8.0)) = 3.0
_RimIntensity (""Rim Intensity"", Range(0, 5)) = 1.0
_RimSmoothness (""Rim Smoothness"", Range(0, 1)) = 0.1
[Header(MatCap)]
_MatCap (""MatCap Texture"", 2D) = ""white"" {}
_MatCapIntensity (""MatCap Intensity"", Range(0, 1)) = 0
_MatCapBlend (""MatCap Blend Mode"", Range(0, 2)) = 0
[Header(Outline)]
_OutlineWidth (""Outline Width"", Range(0, 0.2)) = 0.01
_OutlineColor (""Outline Color"", Color) = (0, 0, 0, 1)
_OutlineMinDist (""Outline Min Distance"", Float) = 1
_OutlineMaxDist (""Outline Max Distance"", Float) = 10
_OutlineDistanceScale (""Distance Scale"", Range(0, 1)) = 0.5
}
SubShader
{
Tags
{
""RenderType"" = ""Opaque""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""Geometry""
}
LOD 300
// Outline Pass
Pass
{
Name ""Outline""
Tags { ""LightMode"" = ""SRPDefaultUnlit"" }
Cull Front
ZWrite On
HLSLPROGRAM
#pragma vertex OutlineVert
#pragma fragment OutlineFrag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
CBUFFER_START(UnityPerMaterial)
float _OutlineWidth;
float4 _OutlineColor;
float _OutlineMinDist;
float _OutlineMaxDist;
float _OutlineDistanceScale;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
};
Varyings OutlineVert(Attributes input)
{
Varyings output;
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float dist = distance(GetCameraPositionWS(), positionWS);
float distFactor = saturate((dist - _OutlineMinDist) / (_OutlineMaxDist - _OutlineMinDist));
float dynamicWidth = _OutlineWidth * lerp(1.0, _OutlineDistanceScale, distFactor);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
float3 outlinePos = positionWS + normalWS * dynamicWidth;
output.positionCS = TransformWorldToHClip(outlinePos);
return output;
}
half4 OutlineFrag(Varyings input) : SV_Target
{
return _OutlineColor;
}
ENDHLSL
}
// Main Toon Shading Pass
Pass
{
Name ""ToonForward""
Tags { ""LightMode"" = ""UniversalForward"" }
HLSLPROGRAM
#pragma vertex ToonVert
#pragma fragment ToonFrag
#pragma target 4.5
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE
#pragma multi_compile _ _ADDITIONAL_LIGHTS
#pragma multi_compile_fragment _ _SHADOWS_SOFT
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _Color;
float4 _Shadow1Color;
float4 _Shadow2Color;
float _Shadow1Step;
float _Shadow2Step;
float _ShadowSmoothness;
float4 _SpecularColor;
float _SpecularSize;
float _SpecularSmoothness;
float _SpecularIntensity;
float4 _RimColor;
float _RimPower;
float _RimIntensity;
float _RimSmoothness;
float _MatCapIntensity;
float _MatCapBlend;
float4 _MainTex_ST;
float4 _MatCap_ST;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
TEXTURE2D(_MatCap);
SAMPLER(sampler_MatCap);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float3 viewDirWS : TEXCOORD3;
};
Varyings ToonVert(Attributes input)
{
Varyings output;
output.positionWS = TransformObjectToWorld(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(output.positionWS);
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
output.viewDirWS = GetWorldSpaceNormalizeViewDir(output.positionWS);
return output;
}
half4 ToonFrag(Varyings input) : SV_Target
{
// Base texture
half4 baseColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv) * _Color;
half3 normalWS = normalize(input.normalWS);
// MatCap (Sphere mapping)
half3 albedo = baseColor.rgb;
if (_MatCapIntensity > 0)
{
half3 viewNorm = mul((float3x3)UNITY_MATRIX_V, normalWS);
half2 matcapUV = viewNorm.xy * 0.5 + 0.5;
half3 matcap = SAMPLE_TEXTURE2D(_MatCap, sampler_MatCap, matcapUV).rgb;
// Blend modes: 0=Multiply, 1=Add, 2=Overlay
if (_MatCapBlend < 0.5)
{
albedo = lerp(albedo, albedo * matcap, _MatCapIntensity);
}
else if (_MatCapBlend < 1.5)
{
albedo += matcap * _MatCapIntensity;
}
else
{
half3 overlay = lerp(albedo * matcap * 2, 1 - 2 * (1 - albedo) * (1 - matcap), step(0.5, albedo));
albedo = lerp(albedo, overlay, _MatCapIntensity);
}
}
// Main light
Light mainLight = GetMainLight();
half3 lightDir = mainLight.direction;
half3 lightColor = mainLight.color;
// Diffuse lighting with multiple steps
half NdotL = dot(normalWS, lightDir);
half shadow = NdotL * 0.5 + 0.5; // Half Lambert
// Multiple shadow layers with smooth transitions
half3 toonColor;
if (shadow > _Shadow1Step)
{
half blend = smoothstep(_Shadow1Step - _ShadowSmoothness, _Shadow1Step + _ShadowSmoothness, shadow);
toonColor = lerp(_Shadow1Color.rgb * albedo, albedo, blend);
}
else if (shadow > _Shadow2Step)
{
half blend = smoothstep(_Shadow2Step - _ShadowSmoothness, _Shadow2Step + _ShadowSmoothness, shadow);
toonColor = lerp(_Shadow2Color.rgb * albedo, _Shadow1Color.rgb * albedo, blend);
}
else
{
toonColor = _Shadow2Color.rgb * albedo;
}
// Toon specular highlight
half3 halfDir = normalize(lightDir + input.viewDirWS);
half NdotH = dot(normalWS, halfDir);
half specular = smoothstep(_SpecularSize - _SpecularSmoothness, _SpecularSize + _SpecularSmoothness, NdotH);
half3 specularColor = _SpecularColor.rgb * specular * _SpecularIntensity;
// Rim lighting
half rim = 1.0 - saturate(dot(input.viewDirWS, normalWS));
rim = smoothstep(1.0 - _RimSmoothness, 1.0, pow(rim, _RimPower));
half3 rimColor = _RimColor.rgb * rim * _RimIntensity;
// Final color
half3 finalColor = toonColor * lightColor + specularColor * lightColor + rimColor;
return half4(finalColor, baseColor.a);
}
ENDHLSL
}
// Shadow caster pass
Pass
{
Name ""ShadowCaster""
Tags { ""LightMode"" = ""ShadowCaster"" }
ZWrite On
ZTest LEqual
ColorMask 0
HLSLPROGRAM
#pragma vertex ShadowVert
#pragma fragment ShadowFrag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
};
float3 _LightDirection;
Varyings ShadowVert(Attributes input)
{
Varyings output;
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
return output;
}
half4 ShadowFrag(Varyings input) : SV_Target
{
return 0;
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/Toon""
{
Properties
{
[Header(Base)]
_MainTex (""Texture"", 2D) = ""white"" {}
_Color (""Base Color"", Color) = (1, 1, 1, 1)
[Header(Shadow Layers)]
_Shadow1Color (""Shadow 1 Color"", Color) = (0.7, 0.7, 0.7, 1)
_Shadow1Step (""Shadow 1 Step"", Range(0, 1)) = 0.6
_Shadow2Color (""Shadow 2 Color"", Color) = (0.4, 0.4, 0.4, 1)
_Shadow2Step (""Shadow 2 Step"", Range(0, 1)) = 0.3
_ShadowSmoothness (""Shadow Smoothness"", Range(0, 0.5)) = 0.05
[Header(Specular)]
_SpecularColor (""Specular Color"", Color) = (1, 1, 1, 1)
_SpecularSize (""Specular Size"", Range(0, 1)) = 0.1
_SpecularSmoothness (""Specular Smoothness"", Range(0, 0.5)) = 0.05
_SpecularIntensity (""Specular Intensity"", Range(0, 5)) = 1.0
[Header(Rim Light)]
_RimColor (""Rim Color"", Color) = (1, 1, 1, 1)
_RimPower (""Rim Power"", Range(0.5, 8.0)) = 3.0
_RimIntensity (""Rim Intensity"", Range(0, 5)) = 1.0
_RimSmoothness (""Rim Smoothness"", Range(0, 1)) = 0.1
[Header(MatCap)]
_MatCap (""MatCap Texture"", 2D) = ""white"" {}
_MatCapIntensity (""MatCap Intensity"", Range(0, 1)) = 0
_MatCapBlend (""MatCap Blend Mode"", Range(0, 2)) = 0
[Header(Outline)]
_OutlineWidth (""Outline Width"", Range(0, 0.2)) = 0.01
_OutlineColor (""Outline Color"", Color) = (0, 0, 0, 1)
_OutlineMinDist (""Outline Min Distance"", Float) = 1
_OutlineMaxDist (""Outline Max Distance"", Float) = 10
_OutlineDistanceScale (""Distance Scale"", Range(0, 1)) = 0.5
}
SubShader
{
Tags {""RenderType""=""Opaque""}
LOD 300
// Advanced Outline Pass (Distance-based width)
Pass
{
Name ""OUTLINE""
Cull Front
ZWrite On
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include ""UnityCG.cginc""
float _OutlineWidth;
fixed4 _OutlineColor;
float _OutlineMinDist;
float _OutlineMaxDist;
float _OutlineDistanceScale;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
// Calculate distance-based width
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
float dist = distance(_WorldSpaceCameraPos, worldPos.xyz);
float distFactor = saturate((dist - _OutlineMinDist) / (_OutlineMaxDist - _OutlineMinDist));
float dynamicWidth = _OutlineWidth * lerp(1.0, _OutlineDistanceScale, distFactor);
// Use smoothed normals for better outline
float3 norm = normalize(v.normal);
float3 outlinePos = v.vertex.xyz + norm * dynamicWidth;
o.pos = UnityObjectToClipPos(float4(outlinePos, 1));
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _OutlineColor;
}
ENDCG
}
// Toon shading pass with multiple lights
CGPROGRAM
#pragma surface surf ToonMulti fullforwardshadows addshadow
#pragma target 4.0
sampler2D _MainTex;
sampler2D _MatCap;
fixed4 _Color;
fixed4 _Shadow1Color;
fixed4 _Shadow2Color;
half _Shadow1Step;
half _Shadow2Step;
half _ShadowSmoothness;
fixed4 _SpecularColor;
half _SpecularSize;
half _SpecularSmoothness;
half _SpecularIntensity;
fixed4 _RimColor;
float _RimPower;
half _RimIntensity;
half _RimSmoothness;
half _MatCapIntensity;
half _MatCapBlend;
struct Input
{
float2 uv_MainTex;
float3 viewDir;
float3 worldNormal;
INTERNAL_DATA
};
struct SurfaceOutputToon
{
fixed3 Albedo;
fixed3 Normal;
half3 Emission;
fixed Alpha;
fixed3 Specular;
half SpecularSize;
};
// Custom lighting for multiple shade layers
half4 LightingToonMulti(SurfaceOutputToon s, half3 viewDir, UnityGI gi)
{
half3 lightDir = gi.light.dir;
half3 normal = s.Normal;
// Diffuse lighting with multiple steps
half NdotL = dot(normal, lightDir);
half atten = gi.light.ndotl;
// Multiple shadow layers with smooth transitions
half shadow = NdotL * atten;
fixed3 toonColor;
if (shadow > _Shadow1Step)
{
half blend = smoothstep(_Shadow1Step - _ShadowSmoothness, _Shadow1Step + _ShadowSmoothness, shadow);
toonColor = lerp(_Shadow1Color.rgb, s.Albedo, blend);
}
else if (shadow > _Shadow2Step)
{
half blend = smoothstep(_Shadow2Step - _ShadowSmoothness, _Shadow2Step + _ShadowSmoothness, shadow);
toonColor = lerp(_Shadow2Color.rgb, _Shadow1Color.rgb, blend);
}
else
{
toonColor = _Shadow2Color.rgb;
}
// Toon specular highlight
half3 halfDir = normalize(lightDir + viewDir);
half NdotH = dot(normal, halfDir);
half specular = smoothstep(s.SpecularSize - _SpecularSmoothness, s.SpecularSize + _SpecularSmoothness, NdotH);
half4 c;
c.rgb = toonColor * _LightColor0.rgb;
c.rgb += s.Specular * specular * _SpecularIntensity * _LightColor0.rgb;
c.rgb += s.Emission;
c.a = s.Alpha;
return c;
}
void LightingToonMulti_GI(SurfaceOutputToon s, UnityGIInput data, inout UnityGI gi)
{
gi = UnityGlobalIllumination(data, 1.0, s.Normal);
}
void surf (Input IN, inout SurfaceOutputToon o)
{
// Base texture
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// MatCap (Sphere mapping)
if (_MatCapIntensity > 0)
{
half3 worldNorm = WorldNormalVector(IN, o.Normal);
half3 viewNorm = mul((float3x3)UNITY_MATRIX_V, worldNorm);
half2 matcapUV = viewNorm.xy * 0.5 + 0.5;
half3 matcap = tex2D(_MatCap, matcapUV).rgb;
// Blend modes: 0=Multiply, 1=Add, 2=Overlay
if (_MatCapBlend < 0.5)
{
o.Albedo = lerp(o.Albedo, o.Albedo * matcap, _MatCapIntensity);
}
else if (_MatCapBlend < 1.5)
{
o.Albedo += matcap * _MatCapIntensity;
}
else
{
half3 overlay = lerp(o.Albedo * matcap * 2, 1 - 2 * (1 - o.Albedo) * (1 - matcap), step(0.5, o.Albedo));
o.Albedo = lerp(o.Albedo, overlay, _MatCapIntensity);
}
}
// Specular setup
o.Specular = _SpecularColor.rgb;
o.SpecularSize = _SpecularSize;
// Rim lighting
half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));
rim = smoothstep(1.0 - _RimSmoothness, 1.0, pow(rim, _RimPower));
o.Emission = _RimColor.rgb * rim * _RimIntensity;
o.Alpha = c.a;
}
ENDCG
}
FallBack ""Diffuse""
}
";
}
return SaveShaderAndCreateMaterial("Toon", shaderCode);
}
private string GenerateDissolveShader(ShaderGraphController controller)
{
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version with HDR Edge Glow, Triplanar Mapping, Burning Edge
shaderCode = @"Shader ""Synaptic/URP/Dissolve""
{
Properties
{
[Header(Base)]
_Color (""Base Color"", Color) = (1, 1, 1, 1)
_MainTex (""Albedo"", 2D) = ""white"" {}
[Header(Dissolve)]
_DissolveAmount (""Dissolve Amount"", Range(0, 1)) = 0.5
_DissolveDirection (""Dissolve Direction"", Vector) = (0, 1, 0, 0)
_DirectionInfluence (""Direction Influence"", Range(0, 1)) = 0.5
[Header(Primary Noise)]
_NoiseTex (""Noise Texture"", 2D) = ""white"" {}
_NoiseScale (""Noise Scale"", Range(1, 50)) = 10
_NoiseStrength (""Noise Strength"", Range(0, 1)) = 1.0
[Header(Detail Noise)]
_DetailNoiseTex (""Detail Noise Texture"", 2D) = ""white"" {}
_DetailNoiseScale (""Detail Noise Scale"", Range(1, 100)) = 25
_DetailNoiseStrength (""Detail Noise Strength"", Range(0, 1)) = 0.3
[Header(Triplanar)]
[Toggle] _UseTriplanar (""Use Triplanar Mapping"", Float) = 1
_TriplanarBlend (""Triplanar Blend Sharpness"", Range(1, 16)) = 4
[Header(HDR Edge Glow)]
[HDR] _EdgeColor1 (""Inner Edge Color (HDR)"", Color) = (10, 5, 0, 1)
[HDR] _EdgeColor2 (""Outer Edge Color (HDR)"", Color) = (5, 1.5, 0, 1)
_EdgeWidth1 (""Inner Edge Width"", Range(0, 0.3)) = 0.03
_EdgeWidth2 (""Outer Edge Width"", Range(0, 0.5)) = 0.1
_EdgeIntensity (""Edge Glow Intensity"", Range(0, 10)) = 5.0
[Header(Burning Edge)]
_BurnColor (""Burn Color"", Color) = (0.1, 0.05, 0, 1)
_BurnWidth (""Burn Width"", Range(0, 0.2)) = 0.05
_BurnNoiseScale (""Burn Noise Scale"", Float) = 50
}
SubShader
{
Tags
{
""RenderType"" = ""TransparentCutout""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""AlphaTest""
}
LOD 300
Pass
{
Name ""DissolveForward""
Tags { ""LightMode"" = ""UniversalForward"" }
HLSLPROGRAM
#pragma vertex DissolveVert
#pragma fragment DissolveFrag
#pragma target 4.5
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _Color;
float _DissolveAmount;
float4 _DissolveDirection;
float _DirectionInfluence;
float _NoiseScale;
float _NoiseStrength;
float _DetailNoiseScale;
float _DetailNoiseStrength;
float _UseTriplanar;
float _TriplanarBlend;
float4 _EdgeColor1;
float4 _EdgeColor2;
float _EdgeWidth1;
float _EdgeWidth2;
float _EdgeIntensity;
float4 _BurnColor;
float _BurnWidth;
float _BurnNoiseScale;
float4 _MainTex_ST;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
TEXTURE2D(_NoiseTex);
SAMPLER(sampler_NoiseTex);
TEXTURE2D(_DetailNoiseTex);
SAMPLER(sampler_DetailNoiseTex);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
};
// Triplanar sampling
float4 TriplanarSample(TEXTURE2D_PARAM(tex, samp), float3 worldPos, float3 normal, float scale, float blend)
{
float3 blendWeights = abs(normal);
blendWeights = pow(blendWeights, blend);
blendWeights /= (blendWeights.x + blendWeights.y + blendWeights.z);
float4 xSample = SAMPLE_TEXTURE2D(tex, samp, worldPos.yz * scale);
float4 ySample = SAMPLE_TEXTURE2D(tex, samp, worldPos.xz * scale);
float4 zSample = SAMPLE_TEXTURE2D(tex, samp, worldPos.xy * scale);
return xSample * blendWeights.x + ySample * blendWeights.y + zSample * blendWeights.z;
}
Varyings DissolveVert(Attributes input)
{
Varyings output;
output.positionWS = TransformObjectToWorld(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(output.positionWS);
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
return output;
}
half4 DissolveFrag(Varyings input) : SV_Target
{
// Base color
half4 baseColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv) * _Color;
half3 normalWS = normalize(input.normalWS);
// Noise sampling (Triplanar or UV)
float noise1, noise2, burnNoise;
if (_UseTriplanar > 0.5)
{
noise1 = TriplanarSample(TEXTURE2D_ARGS(_NoiseTex, sampler_NoiseTex), input.positionWS, normalWS, _NoiseScale * 0.01, _TriplanarBlend).r;
noise2 = TriplanarSample(TEXTURE2D_ARGS(_DetailNoiseTex, sampler_DetailNoiseTex), input.positionWS, normalWS, _DetailNoiseScale * 0.01, _TriplanarBlend).r;
burnNoise = TriplanarSample(TEXTURE2D_ARGS(_NoiseTex, sampler_NoiseTex), input.positionWS, normalWS, _BurnNoiseScale * 0.01, _TriplanarBlend).r;
}
else
{
noise1 = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, input.uv * _NoiseScale).r;
noise2 = SAMPLE_TEXTURE2D(_DetailNoiseTex, sampler_DetailNoiseTex, input.uv * _DetailNoiseScale).r;
burnNoise = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, input.uv * _BurnNoiseScale).r;
}
// Combine noise layers
float combinedNoise = lerp(noise1, noise1 * noise2, _DetailNoiseStrength);
combinedNoise = lerp(0.5, combinedNoise, _NoiseStrength);
// Direction-based dissolve
float directionGradient = dot(input.positionWS, normalize(_DissolveDirection.xyz));
directionGradient = (directionGradient + 1.0) * 0.5;
// Combine direction and noise
float dissolveMask = lerp(combinedNoise, combinedNoise * directionGradient, _DirectionInfluence);
// Dissolve cutoff
float cutoff = dissolveMask - _DissolveAmount;
clip(cutoff);
// Multi-layer edge glow (HDR)
float edge1 = smoothstep(0, _EdgeWidth1, cutoff);
float edge2 = smoothstep(0, _EdgeWidth2, cutoff);
half3 edgeGlow = _EdgeColor1.rgb * (1.0 - edge1) + _EdgeColor2.rgb * (edge2 - edge1);
edgeGlow *= _EdgeIntensity;
// Burning edge effect
float burnMask = smoothstep(0, _BurnWidth, cutoff);
float burnPattern = burnNoise * (1.0 - burnMask);
half3 burnColor = _BurnColor.rgb * burnPattern * 2.0;
// Lighting
Light mainLight = GetMainLight();
half NdotL = saturate(dot(normalWS, mainLight.direction));
half3 diffuse = baseColor.rgb * mainLight.color * NdotL;
// Darken albedo near edge (burning)
float albedoDarken = smoothstep(0, _BurnWidth * 2, cutoff);
half3 finalColor = diffuse * albedoDarken + edgeGlow + burnColor;
return half4(finalColor, 1.0);
}
ENDHLSL
}
// Shadow caster pass
Pass
{
Name ""ShadowCaster""
Tags { ""LightMode"" = ""ShadowCaster"" }
ZWrite On
ZTest LEqual
ColorMask 0
HLSLPROGRAM
#pragma vertex ShadowVert
#pragma fragment ShadowFrag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl""
CBUFFER_START(UnityPerMaterial)
float _DissolveAmount;
float _NoiseScale;
float _NoiseStrength;
float4 _MainTex_ST;
CBUFFER_END
TEXTURE2D(_NoiseTex);
SAMPLER(sampler_NoiseTex);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
float3 _LightDirection;
Varyings ShadowVert(Attributes input)
{
Varyings output;
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
return output;
}
half4 ShadowFrag(Varyings input) : SV_Target
{
float noise = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, input.uv * _NoiseScale).r;
noise = lerp(0.5, noise, _NoiseStrength);
clip(noise - _DissolveAmount);
return 0;
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/Dissolve""
{
Properties
{
// Base
[Header(Base)]
_Color (""Base Color"", Color) = (1, 1, 1, 1)
_MainTex (""Albedo"", 2D) = ""white"" {}
// Dissolve
[Header(Dissolve)]
_DissolveAmount (""Dissolve Amount"", Range(0, 1)) = 0.5
_DissolveDirection (""Dissolve Direction"", Vector) = (0, 1, 0, 0)
_DirectionInfluence (""Direction Influence"", Range(0, 1)) = 0.5
// Noise (Primary)
[Header(Primary Noise)]
_NoiseTex (""Noise Texture"", 2D) = ""white"" {}
_NoiseScale (""Noise Scale"", Range(1, 50)) = 10
_NoiseStrength (""Noise Strength"", Range(0, 1)) = 1.0
// Noise (Detail)
[Header(Detail Noise)]
_DetailNoiseTex (""Detail Noise Texture"", 2D) = ""white"" {}
_DetailNoiseScale (""Detail Noise Scale"", Range(1, 100)) = 25
_DetailNoiseStrength (""Detail Noise Strength"", Range(0, 1)) = 0.3
// Triplanar
[Header(Triplanar)]
[Toggle] _UseTriplanar (""Use Triplanar Mapping"", Float) = 1
_TriplanarBlend (""Triplanar Blend Sharpness"", Range(1, 16)) = 4
// HDR Edge Glow
[Header(HDR Edge Glow)]
[HDR] _EdgeColor1 (""Inner Edge Color (HDR)"", Color) = (10, 5, 0, 1)
[HDR] _EdgeColor2 (""Outer Edge Color (HDR)"", Color) = (5, 1.5, 0, 1)
_EdgeWidth1 (""Inner Edge Width"", Range(0, 0.3)) = 0.03
_EdgeWidth2 (""Outer Edge Width"", Range(0, 0.5)) = 0.1
_EdgeIntensity (""Edge Glow Intensity"", Range(0, 10)) = 5.0
// Burning Edge
[Header(Burning Edge)]
_BurnColor (""Burn Color"", Color) = (0.1, 0.05, 0, 1)
_BurnWidth (""Burn Width"", Range(0, 0.2)) = 0.05
_BurnNoiseScale (""Burn Noise Scale"", Float) = 50
}
SubShader
{
Tags {""RenderType""=""TransparentCutout"" ""Queue""=""AlphaTest""}
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.5
// Base
sampler2D _MainTex;
fixed4 _Color;
// Dissolve
float _DissolveAmount;
float3 _DissolveDirection;
float _DirectionInfluence;
// Noise
sampler2D _NoiseTex;
float _NoiseScale;
float _NoiseStrength;
sampler2D _DetailNoiseTex;
float _DetailNoiseScale;
float _DetailNoiseStrength;
// Triplanar
float _UseTriplanar;
float _TriplanarBlend;
// Edge
float4 _EdgeColor1;
float4 _EdgeColor2;
float _EdgeWidth1;
float _EdgeWidth2;
float _EdgeIntensity;
// Burn
float4 _BurnColor;
float _BurnWidth;
float _BurnNoiseScale;
struct Input
{
float2 uv_MainTex;
float3 worldPos;
float3 worldNormal;
};
// Triplanar sampling
float4 TriplanarSample(sampler2D tex, float3 worldPos, float3 normal, float scale)
{
// Blend weights
float3 blendWeights = abs(normal);
blendWeights = pow(blendWeights, _TriplanarBlend);
blendWeights /= (blendWeights.x + blendWeights.y + blendWeights.z);
// Sample three planes
float4 xSample = tex2D(tex, worldPos.yz * scale);
float4 ySample = tex2D(tex, worldPos.xz * scale);
float4 zSample = tex2D(tex, worldPos.xy * scale);
// Blend
return xSample * blendWeights.x + ySample * blendWeights.y + zSample * blendWeights.z;
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Base color
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
// Noise sampling (Triplanar or UV)
float noise1, noise2, burnNoise;
if (_UseTriplanar > 0.5)
{
noise1 = TriplanarSample(_NoiseTex, IN.worldPos, IN.worldNormal, _NoiseScale * 0.01).r;
noise2 = TriplanarSample(_DetailNoiseTex, IN.worldPos, IN.worldNormal, _DetailNoiseScale * 0.01).r;
burnNoise = TriplanarSample(_NoiseTex, IN.worldPos, IN.worldNormal, _BurnNoiseScale * 0.01).r;
}
else
{
noise1 = tex2D(_NoiseTex, IN.uv_MainTex * _NoiseScale).r;
noise2 = tex2D(_DetailNoiseTex, IN.uv_MainTex * _DetailNoiseScale).r;
burnNoise = tex2D(_NoiseTex, IN.uv_MainTex * _BurnNoiseScale).r;
}
// Combine noise layers
float combinedNoise = lerp(noise1, noise1 * noise2, _DetailNoiseStrength);
combinedNoise = lerp(0.5, combinedNoise, _NoiseStrength);
// Direction-based dissolve
float directionGradient = dot(IN.worldPos, normalize(_DissolveDirection));
directionGradient = (directionGradient + 1.0) * 0.5; // Remap to 0-1
// Combine direction and noise
float dissolveMask = lerp(combinedNoise, combinedNoise * directionGradient, _DirectionInfluence);
// Dissolve cutoff
float cutoff = dissolveMask - _DissolveAmount;
clip(cutoff);
// Multi-layer edge glow (HDR)
float edge1 = smoothstep(0, _EdgeWidth1, cutoff);
float edge2 = smoothstep(0, _EdgeWidth2, cutoff);
float3 edgeGlow = _EdgeColor1.rgb * (1.0 - edge1) + _EdgeColor2.rgb * (edge2 - edge1);
edgeGlow *= _EdgeIntensity;
// Burning edge effect
float burnMask = smoothstep(0, _BurnWidth, cutoff);
float burnPattern = burnNoise * (1.0 - burnMask);
float3 burnColor = _BurnColor.rgb * burnPattern * 2.0;
// Combine emission
o.Emission = edgeGlow + burnColor;
// Darken albedo near edge (burning)
float albedoDarken = smoothstep(0, _BurnWidth * 2, cutoff);
o.Albedo = c.rgb * albedoDarken;
o.Alpha = c.a;
o.Smoothness = 0.2;
}
ENDCG
}
FallBack ""Standard""
}
";
}
return SaveShaderAndCreateMaterial("Dissolve", shaderCode);
}
private string GenerateForceFieldShader(ShaderGraphController controller)
{
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version with Impact System, Vertex Displacement, Hex Layers
shaderCode = @"Shader ""Synaptic/URP/ForceField""
{
Properties
{
[Header(Base)]
[HDR] _Color (""Shield Color (HDR)"", Color) = (0, 2, 4, 0.5)
_Transparency (""Base Transparency"", Range(0, 1)) = 0.3
[Header(Hexagon Grid)]
_HexSize (""Hex Size"", Range(1, 100)) = 20
_HexLineWidth (""Hex Line Width"", Range(0, 1)) = 0.1
[HDR] _HexColor (""Hex Line Color (HDR)"", Color) = (0, 3, 6, 1)
_HexSize2 (""Hex Layer 2 Size"", Range(1, 100)) = 40
_HexRotation2 (""Hex Layer 2 Rotation Speed"", Range(-5, 5)) = 0.5
_HexLayer2Strength (""Hex Layer 2 Strength"", Range(0, 1)) = 0.5
[Header(Fresnel)]
_FresnelPower (""Fresnel Power"", Range(0.5, 10)) = 4
_FresnelIntensity (""Fresnel Intensity"", Range(0, 5)) = 2.5
[HDR] _FresnelColor (""Fresnel Color (HDR)"", Color) = (0, 4, 8, 1)
[Header(Impact System)]
_ImpactPos1 (""Impact Position 1"", Vector) = (0, 0, 0, 0)
_ImpactPos2 (""Impact Position 2"", Vector) = (0, 0, 0, 0)
_ImpactPos3 (""Impact Position 3"", Vector) = (0, 0, 0, 0)
_ImpactPos4 (""Impact Position 4"", Vector) = (0, 0, 0, 0)
_ImpactStrength1 (""Impact 1 Strength"", Range(0, 2)) = 0
_ImpactStrength2 (""Impact 2 Strength"", Range(0, 2)) = 0
_ImpactStrength3 (""Impact 3 Strength"", Range(0, 2)) = 0
_ImpactStrength4 (""Impact 4 Strength"", Range(0, 2)) = 0
_ImpactRadius (""Impact Radius"", Range(0.1, 5)) = 1.5
_ImpactFalloff (""Impact Falloff"", Range(0.1, 5)) = 2.0
[HDR] _ImpactColor (""Impact Color (HDR)"", Color) = (4, 2, 0, 1)
[Header(Vertex Displacement)]
_DisplacementAmount (""Displacement Amount"", Range(0, 1)) = 0.2
_RippleSpeed (""Ripple Speed"", Range(0, 10)) = 3.0
[Header(Pulse Animation)]
_PulseSpeed (""Pulse Speed"", Range(0, 10)) = 2
_PulseAmount (""Pulse Amount"", Range(0, 1)) = 0.3
[Header(Energy Flow)]
_FlowSpeed (""Flow Speed"", Range(0, 10)) = 1.5
_FlowTiling (""Flow Tiling"", Range(1, 50)) = 10
_FlowStrength (""Flow Strength"", Range(0, 1)) = 0.3
}
SubShader
{
Tags
{
""RenderType"" = ""Transparent""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""Transparent""
}
LOD 300
Pass
{
Name ""ForceFieldForward""
Tags { ""LightMode"" = ""UniversalForward"" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Back
HLSLPROGRAM
#pragma vertex ForceFieldVert
#pragma fragment ForceFieldFrag
#pragma target 4.5
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _Color;
float _Transparency;
float _HexSize;
float _HexSize2;
float _HexLineWidth;
float4 _HexColor;
float _HexRotation2;
float _HexLayer2Strength;
float _FresnelPower;
float _FresnelIntensity;
float4 _FresnelColor;
float4 _ImpactPos1;
float4 _ImpactPos2;
float4 _ImpactPos3;
float4 _ImpactPos4;
float _ImpactStrength1;
float _ImpactStrength2;
float _ImpactStrength3;
float _ImpactStrength4;
float _ImpactRadius;
float _ImpactFalloff;
float4 _ImpactColor;
float _DisplacementAmount;
float _RippleSpeed;
float _PulseSpeed;
float _PulseAmount;
float _FlowSpeed;
float _FlowTiling;
float _FlowStrength;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float3 positionWS : TEXCOORD0;
float3 normalWS : TEXCOORD1;
float3 viewDirWS : TEXCOORD2;
};
// Rotate 2D
float2 rotate2D(float2 p, float angle)
{
float s = sin(angle);
float c = cos(angle);
return float2(p.x * c - p.y * s, p.x * s + p.y * c);
}
// Hexagon pattern
float hexPattern(float2 p, float size)
{
p *= size;
const float3 k = float3(-0.866025404, 0.5, 0.577350269);
p = abs(p);
p -= 2.0 * min(dot(k.xy, p), 0.0) * k.xy;
p -= float2(clamp(p.x, -k.z, k.z), 1.0);
return length(p) * sign(p.y);
}
// Impact ripple
float impactRipple(float3 worldPos, float3 impactPos, float strength, float time, float rippleSpeed, float impactRadius, float impactFalloff)
{
if (strength <= 0.001) return 0;
float dist = distance(worldPos, impactPos);
float wave = sin((dist - time * rippleSpeed) / impactRadius * 6.28318) * 0.5 + 0.5;
float falloff = 1.0 - saturate(pow(dist / (impactRadius * 2), impactFalloff));
return wave * falloff * strength;
}
Varyings ForceFieldVert(Attributes input)
{
Varyings output;
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
float time = _Time.y;
// Calculate total impact displacement
float impact = 0;
impact += impactRipple(positionWS, _ImpactPos1.xyz, _ImpactStrength1, time, _RippleSpeed, _ImpactRadius, _ImpactFalloff);
impact += impactRipple(positionWS, _ImpactPos2.xyz, _ImpactStrength2, time, _RippleSpeed, _ImpactRadius, _ImpactFalloff);
impact += impactRipple(positionWS, _ImpactPos3.xyz, _ImpactStrength3, time, _RippleSpeed, _ImpactRadius, _ImpactFalloff);
impact += impactRipple(positionWS, _ImpactPos4.xyz, _ImpactStrength4, time, _RippleSpeed, _ImpactRadius, _ImpactFalloff);
// Apply displacement along normal
positionWS += normalWS * impact * _DisplacementAmount;
output.positionWS = positionWS;
output.normalWS = normalWS;
output.positionCS = TransformWorldToHClip(positionWS);
output.viewDirWS = GetWorldSpaceNormalizeViewDir(positionWS);
return output;
}
half4 ForceFieldFrag(Varyings input) : SV_Target
{
float time = _Time.y;
half3 normalWS = normalize(input.normalWS);
// Hexagon grid layer 1
float hex1 = hexPattern(input.positionWS.xz, _HexSize);
float hexLine1 = smoothstep(_HexLineWidth, _HexLineWidth * 0.5, abs(hex1));
// Hexagon grid layer 2 (rotated, animated)
float2 rotatedPos = rotate2D(input.positionWS.xz, time * _HexRotation2);
float hex2 = hexPattern(rotatedPos, _HexSize2);
float hexLine2 = smoothstep(_HexLineWidth, _HexLineWidth * 0.5, abs(hex2));
hexLine2 *= _HexLayer2Strength;
// Combine hex layers
float hexCombined = max(hexLine1, hexLine2);
// Pulse animation
float pulse = 1.0;
if (_PulseSpeed > 0)
{
pulse = sin(time * _PulseSpeed) * _PulseAmount + (1.0 - _PulseAmount);
}
// Fresnel effect
float fresnel = pow(1.0 - saturate(dot(input.viewDirWS, normalWS)), _FresnelPower);
half3 fresnelGlow = _FresnelColor.rgb * fresnel * _FresnelIntensity;
// Energy flow lines
float flow = frac(input.positionWS.y * _FlowTiling - time * _FlowSpeed);
flow = smoothstep(0.0, 0.1, flow) * smoothstep(0.2, 0.1, flow);
half3 flowGlow = _Color.rgb * flow * _FlowStrength;
// Impact effects
float impactGlow = 0;
impactGlow += impactRipple(input.positionWS, _ImpactPos1.xyz, _ImpactStrength1, time, _RippleSpeed, _ImpactRadius, _ImpactFalloff);
impactGlow += impactRipple(input.positionWS, _ImpactPos2.xyz, _ImpactStrength2, time, _RippleSpeed, _ImpactRadius, _ImpactFalloff);
impactGlow += impactRipple(input.positionWS, _ImpactPos3.xyz, _ImpactStrength3, time, _RippleSpeed, _ImpactRadius, _ImpactFalloff);
impactGlow += impactRipple(input.positionWS, _ImpactPos4.xyz, _ImpactStrength4, time, _RippleSpeed, _ImpactRadius, _ImpactFalloff);
half3 impactEmission = _ImpactColor.rgb * impactGlow * 2.0;
// Combine all effects
half3 finalColor = (_Color.rgb * hexCombined * _HexColor.rgb + fresnelGlow + flowGlow + impactEmission) * pulse;
half alpha = (_Transparency + fresnel * 0.3 + hexCombined * 0.2 + impactGlow * 0.4) * pulse;
return half4(finalColor, alpha);
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/ForceField""
{
Properties
{
// Base
[Header(Base)]
[HDR] _Color (""Shield Color (HDR)"", Color) = (0, 2, 4, 0.5)
_Transparency (""Base Transparency"", Range(0, 1)) = 0.3
// Hexagon Grid
[Header(Hexagon Grid)]
_HexSize (""Hex Size"", Range(1, 100)) = 20
_HexLineWidth (""Hex Line Width"", Range(0, 1)) = 0.1
[HDR] _HexColor (""Hex Line Color (HDR)"", Color) = (0, 3, 6, 1)
// Secondary Hex Layer
_HexSize2 (""Hex Layer 2 Size"", Range(1, 100)) = 40
_HexRotation2 (""Hex Layer 2 Rotation Speed"", Range(-5, 5)) = 0.5
_HexLayer2Strength (""Hex Layer 2 Strength"", Range(0, 1)) = 0.5
// Fresnel
[Header(Fresnel)]
_FresnelPower (""Fresnel Power"", Range(0.5, 10)) = 4
_FresnelIntensity (""Fresnel Intensity"", Range(0, 5)) = 2.5
[HDR] _FresnelColor (""Fresnel Color (HDR)"", Color) = (0, 4, 8, 1)
// Impact System
[Header(Impact System)]
_ImpactPos1 (""Impact Position 1"", Vector) = (0, 0, 0, 0)
_ImpactPos2 (""Impact Position 2"", Vector) = (0, 0, 0, 0)
_ImpactPos3 (""Impact Position 3"", Vector) = (0, 0, 0, 0)
_ImpactPos4 (""Impact Position 4"", Vector) = (0, 0, 0, 0)
_ImpactStrength1 (""Impact 1 Strength"", Range(0, 2)) = 0
_ImpactStrength2 (""Impact 2 Strength"", Range(0, 2)) = 0
_ImpactStrength3 (""Impact 3 Strength"", Range(0, 2)) = 0
_ImpactStrength4 (""Impact 4 Strength"", Range(0, 2)) = 0
_ImpactRadius (""Impact Radius"", Range(0.1, 5)) = 1.5
_ImpactFalloff (""Impact Falloff"", Range(0.1, 5)) = 2.0
[HDR] _ImpactColor (""Impact Color (HDR)"", Color) = (4, 2, 0, 1)
// Vertex Displacement
[Header(Vertex Displacement)]
_DisplacementAmount (""Displacement Amount"", Range(0, 1)) = 0.2
_RippleSpeed (""Ripple Speed"", Range(0, 10)) = 3.0
// Pulse
[Header(Pulse Animation)]
_PulseSpeed (""Pulse Speed"", Range(0, 10)) = 2
_PulseAmount (""Pulse Amount"", Range(0, 1)) = 0.3
// Energy Flow
[Header(Energy Flow)]
_FlowSpeed (""Flow Speed"", Range(0, 10)) = 1.5
_FlowTiling (""Flow Tiling"", Range(1, 50)) = 10
_FlowStrength (""Flow Strength"", Range(0, 1)) = 0.3
}
SubShader
{
Tags {""Queue""=""Transparent"" ""RenderType""=""Transparent""}
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Back
CGPROGRAM
#pragma surface surf Standard alpha:fade vertex:vert
#pragma target 4.0
// Base
float4 _Color;
float _Transparency;
// Hex
float _HexSize, _HexSize2;
float _HexLineWidth;
float4 _HexColor;
float _HexRotation2;
float _HexLayer2Strength;
// Fresnel
float _FresnelPower;
float _FresnelIntensity;
float4 _FresnelColor;
// Impact
float3 _ImpactPos1, _ImpactPos2, _ImpactPos3, _ImpactPos4;
float _ImpactStrength1, _ImpactStrength2, _ImpactStrength3, _ImpactStrength4;
float _ImpactRadius;
float _ImpactFalloff;
float4 _ImpactColor;
// Vertex Displacement
float _DisplacementAmount;
float _RippleSpeed;
// Pulse
float _PulseSpeed;
float _PulseAmount;
// Flow
float _FlowSpeed;
float _FlowTiling;
float _FlowStrength;
struct Input
{
float3 worldPos;
float3 viewDir;
float3 worldNormal;
};
// Rotate 2D
float2 rotate2D(float2 p, float angle)
{
float s = sin(angle);
float c = cos(angle);
return float2(p.x * c - p.y * s, p.x * s + p.y * c);
}
// Hexagon pattern (improved)
float hexPattern(float2 p, float size)
{
p *= size;
// Hexagon math
const float3 k = float3(-0.866025404, 0.5, 0.577350269);
p = abs(p);
p -= 2.0 * min(dot(k.xy, p), 0.0) * k.xy;
p -= float2(clamp(p.x, -k.z, k.z), 1.0);
return length(p) * sign(p.y);
}
// Impact ripple
float impactRipple(float3 worldPos, float3 impactPos, float strength, float time)
{
if (strength <= 0.001) return 0;
float dist = distance(worldPos, impactPos);
float wave = sin((dist - time * _RippleSpeed) / _ImpactRadius * 6.28318) * 0.5 + 0.5;
float falloff = 1.0 - saturate(pow(dist / (_ImpactRadius * 2), _ImpactFalloff));
return wave * falloff * strength;
}
// Vertex displacement
void vert(inout appdata_full v)
{
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// Calculate total impact displacement
float impact = 0;
impact += impactRipple(worldPos, _ImpactPos1, _ImpactStrength1, _Time.y);
impact += impactRipple(worldPos, _ImpactPos2, _ImpactStrength2, _Time.y);
impact += impactRipple(worldPos, _ImpactPos3, _ImpactStrength3, _Time.y);
impact += impactRipple(worldPos, _ImpactPos4, _ImpactStrength4, _Time.y);
// Apply displacement along normal
v.vertex.xyz += v.normal * impact * _DisplacementAmount;
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Hexagon grid layer 1
float hex1 = hexPattern(IN.worldPos.xz, _HexSize);
float hexLine1 = smoothstep(_HexLineWidth, _HexLineWidth * 0.5, abs(hex1));
// Hexagon grid layer 2 (rotated, animated)
float2 rotatedPos = rotate2D(IN.worldPos.xz, _Time.y * _HexRotation2);
float hex2 = hexPattern(rotatedPos, _HexSize2);
float hexLine2 = smoothstep(_HexLineWidth, _HexLineWidth * 0.5, abs(hex2));
hexLine2 *= _HexLayer2Strength;
// Combine hex layers
float hexCombined = max(hexLine1, hexLine2);
// Pulse animation
float pulse = 1.0;
if (_PulseSpeed > 0)
{
pulse = sin(_Time.y * _PulseSpeed) * _PulseAmount + (1.0 - _PulseAmount);
}
// Fresnel effect
float fresnel = pow(1.0 - saturate(dot(normalize(IN.viewDir), IN.worldNormal)), _FresnelPower);
float3 fresnelGlow = _FresnelColor.rgb * fresnel * _FresnelIntensity;
// Energy flow lines
float flow = frac(IN.worldPos.y * _FlowTiling - _Time.y * _FlowSpeed);
flow = smoothstep(0.0, 0.1, flow) * smoothstep(0.2, 0.1, flow);
float3 flowGlow = _Color.rgb * flow * _FlowStrength;
// Impact effects
float impactGlow = 0;
impactGlow += impactRipple(IN.worldPos, _ImpactPos1, _ImpactStrength1, _Time.y);
impactGlow += impactRipple(IN.worldPos, _ImpactPos2, _ImpactStrength2, _Time.y);
impactGlow += impactRipple(IN.worldPos, _ImpactPos3, _ImpactStrength3, _Time.y);
impactGlow += impactRipple(IN.worldPos, _ImpactPos4, _ImpactStrength4, _Time.y);
float3 impactEmission = _ImpactColor.rgb * impactGlow * 2.0;
// Combine all effects
o.Albedo = _Color.rgb * 0.1;
o.Emission = (_Color.rgb * hexCombined * _HexColor.rgb + fresnelGlow + flowGlow + impactEmission) * pulse;
o.Alpha = (_Transparency + fresnel * 0.3 + hexCombined * 0.2 + impactGlow * 0.4) * pulse;
o.Smoothness = 0.9;
}
ENDCG
}
FallBack ""Transparent/Diffuse""
}
";
}
return SaveShaderAndCreateMaterial("ForceField", shaderCode);
}
private string GenerateNeonGlowShader(ShaderGraphController controller)
{
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version with Tube Simulation, HDR Emission, Flicker System
shaderCode = @"Shader ""Synaptic/URP/NeonGlow""
{
Properties
{
[Header(Core Color)]
[HDR] _CoreColor (""Core Color (HDR)"", Color) = (10, 10, 20, 1)
_CoreIntensity (""Core Intensity"", Range(0, 20)) = 10
_CoreFalloff (""Core Falloff"", Range(0.1, 5)) = 1.5
[Header(Inner Glow)]
[HDR] _InnerGlowColor (""Inner Glow Color (HDR)"", Color) = (5, 5, 15, 1)
_InnerGlowIntensity (""Inner Glow Intensity"", Range(0, 20)) = 8
_InnerGlowSize (""Inner Glow Size"", Range(0, 2)) = 0.3
[Header(Outer Glow)]
[HDR] _OuterGlowColor (""Outer Glow Color (HDR)"", Color) = (2, 2, 8, 1)
_OuterGlowIntensity (""Outer Glow Intensity"", Range(0, 20)) = 5
_OuterGlowSize (""Outer Glow Size"", Range(0, 3)) = 0.8
[Header(Tube Simulation)]
[Toggle] _TubeMode (""Tube Mode"", Float) = 1
_TubeDepth (""Tube Depth"", Range(0, 1)) = 0.5
_TubeContrast (""Tube Contrast"", Range(1, 10)) = 3
[Header(Flicker)]
_FlickerMode (""Flicker Mode (0=Off, 1=Smooth, 2=Random, 3=Harsh)"", Range(0, 3)) = 0
_FlickerSpeed (""Flicker Speed"", Range(0, 20)) = 5
_FlickerAmount (""Flicker Amount"", Range(0, 1)) = 0.3
_FlickerSeed (""Flicker Seed"", Range(0, 100)) = 0
[Header(Pulse)]
_PulseSpeed (""Pulse Speed"", Range(0, 10)) = 0
_PulseAmount (""Pulse Amount"", Range(0, 1)) = 0.2
[Header(HDR Optimization)]
_BloomThreshold (""Bloom Threshold"", Range(0, 10)) = 1
_ExposureBoost (""Exposure Boost"", Range(1, 5)) = 1.5
}
SubShader
{
Tags
{
""RenderType"" = ""Opaque""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""Geometry""
}
LOD 200
Pass
{
Name ""NeonGlowForward""
Tags { ""LightMode"" = ""UniversalForward"" }
HLSLPROGRAM
#pragma vertex NeonVert
#pragma fragment NeonFrag
#pragma target 4.5
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _CoreColor;
float _CoreIntensity;
float _CoreFalloff;
float4 _InnerGlowColor;
float _InnerGlowIntensity;
float _InnerGlowSize;
float4 _OuterGlowColor;
float _OuterGlowIntensity;
float _OuterGlowSize;
float _TubeMode;
float _TubeDepth;
float _TubeContrast;
float _FlickerMode;
float _FlickerSpeed;
float _FlickerAmount;
float _FlickerSeed;
float _PulseSpeed;
float _PulseAmount;
float _BloomThreshold;
float _ExposureBoost;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
// Random function
float random(float2 st, float seed)
{
return frac(sin(dot(st, float2(12.9898, 78.233)) + seed) * 43758.5453123);
}
// Noise function
float noise(float t, float seed)
{
float i = floor(t);
float f = frac(t);
float u = f * f * (3.0 - 2.0 * f);
return lerp(random(float2(i, seed), seed), random(float2(i + 1.0, seed), seed), u);
}
Varyings NeonVert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 NeonFrag(Varyings input) : SV_Target
{
float time = _Time.y;
// Tube simulation (radial gradient from center)
float tubeGradient = 1.0;
if (_TubeMode > 0.5)
{
float2 uv = input.uv * 2.0 - 1.0;
float dist = length(uv);
tubeGradient = 1.0 - saturate(dist);
tubeGradient = pow(tubeGradient, _TubeContrast);
tubeGradient = lerp(1.0, tubeGradient, _TubeDepth);
}
// Core emission
half3 core = _CoreColor.rgb * _CoreIntensity;
core *= pow(tubeGradient, _CoreFalloff);
// Inner glow layer
float innerFalloff = saturate(tubeGradient + _InnerGlowSize);
half3 innerGlow = _InnerGlowColor.rgb * _InnerGlowIntensity;
innerGlow *= pow(innerFalloff, 2);
// Outer glow layer
float outerFalloff = saturate(tubeGradient + _OuterGlowSize);
half3 outerGlow = _OuterGlowColor.rgb * _OuterGlowIntensity;
outerGlow *= pow(outerFalloff, 1.5);
// Combine layers (additive)
half3 emission = core + innerGlow + outerGlow;
// Pulse effect
float pulse = 1.0;
if (_PulseSpeed > 0)
{
pulse = sin(time * _PulseSpeed) * _PulseAmount + (1.0 - _PulseAmount);
}
emission *= pulse;
// Flicker effect
float flicker = 1.0;
if (_FlickerMode >= 0.5)
{
float t = time * _FlickerSpeed;
if (_FlickerMode < 1.5) // Smooth
{
flicker = sin(t) * 0.5 + 0.5;
flicker = lerp(1.0, flicker, _FlickerAmount);
}
else if (_FlickerMode < 2.5) // Random
{
flicker = noise(t, _FlickerSeed);
flicker = lerp(1.0, flicker, _FlickerAmount);
}
else // Harsh
{
flicker = step(0.5, random(float2(floor(t * 10), _FlickerSeed), _FlickerSeed));
flicker = lerp(1.0, flicker, _FlickerAmount);
}
}
emission *= flicker;
// HDR & Bloom optimization
emission *= _ExposureBoost;
emission = max(emission, _BloomThreshold);
return half4(emission, 1.0);
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/NeonGlow""
{
Properties
{
// Core Color
[Header(Core Color)]
[HDR] _CoreColor (""Core Color (HDR)"", Color) = (10, 10, 20, 1)
_CoreIntensity (""Core Intensity"", Range(0, 20)) = 10
_CoreFalloff (""Core Falloff"", Range(0.1, 5)) = 1.5
// Inner Glow
[Header(Inner Glow)]
[HDR] _InnerGlowColor (""Inner Glow Color (HDR)"", Color) = (5, 5, 15, 1)
_InnerGlowIntensity (""Inner Glow Intensity"", Range(0, 20)) = 8
_InnerGlowSize (""Inner Glow Size"", Range(0, 2)) = 0.3
// Outer Glow
[Header(Outer Glow)]
[HDR] _OuterGlowColor (""Outer Glow Color (HDR)"", Color) = (2, 2, 8, 1)
_OuterGlowIntensity (""Outer Glow Intensity"", Range(0, 20)) = 5
_OuterGlowSize (""Outer Glow Size"", Range(0, 3)) = 0.8
// Tube Simulation
[Header(Tube Simulation)]
[Toggle] _TubeMode (""Tube Mode"", Float) = 1
_TubeDepth (""Tube Depth"", Range(0, 1)) = 0.5
_TubeContrast (""Tube Contrast"", Range(1, 10)) = 3
// Flicker System
[Header(Flicker)]
_FlickerMode (""Flicker Mode (0=Off, 1=Smooth, 2=Random, 3=Harsh)"", Range(0, 3)) = 0
_FlickerSpeed (""Flicker Speed"", Range(0, 20)) = 5
_FlickerAmount (""Flicker Amount"", Range(0, 1)) = 0.3
_FlickerSeed (""Flicker Seed"", Range(0, 100)) = 0
// Pulse
[Header(Pulse)]
_PulseSpeed (""Pulse Speed"", Range(0, 10)) = 0
_PulseAmount (""Pulse Amount"", Range(0, 1)) = 0.2
// HDR & Bloom
[Header(HDR Optimization)]
_BloomThreshold (""Bloom Threshold"", Range(0, 10)) = 1
_ExposureBoost (""Exposure Boost"", Range(1, 5)) = 1.5
}
SubShader
{
Tags {""RenderType""=""Opaque"" ""Queue""=""Geometry""}
LOD 200
CGPROGRAM
#pragma surface surf Unlit fullforwardshadows
#pragma target 3.5
// Core
float4 _CoreColor;
float _CoreIntensity;
float _CoreFalloff;
// Inner Glow
float4 _InnerGlowColor;
float _InnerGlowIntensity;
float _InnerGlowSize;
// Outer Glow
float4 _OuterGlowColor;
float _OuterGlowIntensity;
float _OuterGlowSize;
// Tube
float _TubeMode;
float _TubeDepth;
float _TubeContrast;
// Flicker
float _FlickerMode;
float _FlickerSpeed;
float _FlickerAmount;
float _FlickerSeed;
// Pulse
float _PulseSpeed;
float _PulseAmount;
// HDR
float _BloomThreshold;
float _ExposureBoost;
struct Input
{
float2 uv_MainTex;
float3 viewDir;
float3 worldPos;
};
half4 LightingUnlit(SurfaceOutput s, half3 lightDir, half atten)
{
return half4(s.Emission, 1);
}
// Random function
float random(float2 st)
{
return frac(sin(dot(st.xy, float2(12.9898, 78.233)) + _FlickerSeed) * 43758.5453123);
}
// Noise function
float noise(float t)
{
float i = floor(t);
float f = frac(t);
float u = f * f * (3.0 - 2.0 * f);
return lerp(random(float2(i, _FlickerSeed)), random(float2(i + 1.0, _FlickerSeed)), u);
}
// Flicker calculation
float GetFlicker()
{
if (_FlickerMode < 0.5) return 1.0; // Off
float t = _Time.y * _FlickerSpeed;
float flicker = 1.0;
if (_FlickerMode < 1.5) // Smooth
{
flicker = sin(t) * 0.5 + 0.5;
flicker = lerp(1.0, flicker, _FlickerAmount);
}
else if (_FlickerMode < 2.5) // Random
{
flicker = noise(t);
flicker = lerp(1.0, flicker, _FlickerAmount);
}
else // Harsh
{
flicker = step(0.5, random(float2(floor(t * 10), _FlickerSeed)));
flicker = lerp(1.0, flicker, _FlickerAmount);
}
return flicker;
}
void surf (Input IN, inout SurfaceOutput o)
{
// Tube simulation (radial gradient from center)
float tubeGradient = 1.0;
if (_TubeMode > 0.5)
{
float2 uv = IN.uv_MainTex * 2.0 - 1.0; // Center UVs
float dist = length(uv);
tubeGradient = 1.0 - saturate(dist);
tubeGradient = pow(tubeGradient, _TubeContrast);
tubeGradient = lerp(1.0, tubeGradient, _TubeDepth);
}
// Core emission
float3 core = _CoreColor.rgb * _CoreIntensity;
core *= pow(tubeGradient, _CoreFalloff);
// Inner glow layer
float innerFalloff = saturate(tubeGradient + _InnerGlowSize);
float3 innerGlow = _InnerGlowColor.rgb * _InnerGlowIntensity;
innerGlow *= pow(innerFalloff, 2);
// Outer glow layer
float outerFalloff = saturate(tubeGradient + _OuterGlowSize);
float3 outerGlow = _OuterGlowColor.rgb * _OuterGlowIntensity;
outerGlow *= pow(outerFalloff, 1.5);
// Combine layers (additive)
float3 emission = core + innerGlow + outerGlow;
// Pulse effect
float pulse = 1.0;
if (_PulseSpeed > 0)
{
pulse = sin(_Time.y * _PulseSpeed) * _PulseAmount + (1.0 - _PulseAmount);
}
emission *= pulse;
// Flicker effect
float flicker = GetFlicker();
emission *= flicker;
// HDR & Bloom optimization
emission *= _ExposureBoost;
emission = max(emission, _BloomThreshold);
o.Emission = emission;
o.Albedo = float3(0, 0, 0); // Pure emission, no albedo
o.Alpha = 1;
}
ENDCG
}
FallBack ""Unlit/Color""
}
";
}
return SaveShaderAndCreateMaterial("NeonGlow", shaderCode);
}
private string GenerateFresnelRimShader(ShaderGraphController controller)
{
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version with Dual Rim Layers, Texture Masking, Animation
shaderCode = @"Shader ""Synaptic/URP/FresnelRim""
{
Properties
{
[Header(Base)]
_Color (""Base Color"", Color) = (0.2, 0.2, 0.2, 1)
_MainTex (""Main Texture"", 2D) = ""white"" {}
[Header(Inner Rim)]
[HDR] _InnerRimColor (""Inner Rim Color (HDR)"", Color) = (5, 5, 10, 1)
_InnerRimPower (""Inner Rim Power"", Range(0.1, 10.0)) = 2.0
_InnerRimIntensity (""Inner Rim Intensity"", Range(0, 10)) = 3.0
[Header(Outer Rim)]
[HDR] _OuterRimColor (""Outer Rim Color (HDR)"", Color) = (1, 1, 3, 1)
_OuterRimPower (""Outer Rim Power"", Range(0.1, 10.0)) = 5.0
_OuterRimIntensity (""Outer Rim Intensity"", Range(0, 10)) = 1.5
[Header(Texture Masking)]
[Toggle] _UseMask (""Use Rim Mask"", Float) = 0
_RimMask (""Rim Mask Texture"", 2D) = ""white"" {}
_MaskStrength (""Mask Strength"", Range(0, 1)) = 1.0
[Header(Directional Masking)]
[Toggle] _UseDirectional (""Use Directional Mask"", Float) = 0
_DirectionalAxis (""Directional Axis"", Vector) = (0, 1, 0, 0)
_DirectionalSharpness (""Directional Sharpness"", Range(0.1, 10)) = 1.0
[Header(Animation)]
_PulseSpeed (""Pulse Speed"", Range(0, 10)) = 0
_PulseAmount (""Pulse Amount"", Range(0, 1)) = 0.3
_FlowSpeed (""Flow Speed"", Range(0, 5)) = 0
_FlowDirection (""Flow Direction"", Vector) = (1, 0, 0, 0)
_ColorShiftSpeed (""Color Shift Speed"", Range(0, 5)) = 0
_ColorShiftAmount (""Color Shift Amount"", Range(0, 1)) = 0.5
}
SubShader
{
Tags
{
""RenderType"" = ""Opaque""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""Geometry""
}
LOD 200
Pass
{
Name ""FresnelRimForward""
Tags { ""LightMode"" = ""UniversalForward"" }
HLSLPROGRAM
#pragma vertex FresnelVert
#pragma fragment FresnelFrag
#pragma target 4.5
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _Color;
float4 _InnerRimColor;
float _InnerRimPower;
float _InnerRimIntensity;
float4 _OuterRimColor;
float _OuterRimPower;
float _OuterRimIntensity;
float _UseMask;
float _MaskStrength;
float _UseDirectional;
float4 _DirectionalAxis;
float _DirectionalSharpness;
float _PulseSpeed;
float _PulseAmount;
float _FlowSpeed;
float4 _FlowDirection;
float _ColorShiftSpeed;
float _ColorShiftAmount;
float4 _MainTex_ST;
float4 _RimMask_ST;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
TEXTURE2D(_RimMask);
SAMPLER(sampler_RimMask);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float3 viewDirWS : TEXCOORD3;
};
// HSV to RGB conversion for color shift
float3 hsv2rgb(float3 c)
{
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * lerp(K.xxx, saturate(p - K.xxx), c.y);
}
float3 rgb2hsv(float3 c)
{
float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), step(c.b, c.g));
float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
Varyings FresnelVert(Attributes input)
{
Varyings output;
output.positionWS = TransformObjectToWorld(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(output.positionWS);
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
output.viewDirWS = GetWorldSpaceNormalizeViewDir(output.positionWS);
return output;
}
half4 FresnelFrag(Varyings input) : SV_Target
{
float time = _Time.y;
half3 normalWS = normalize(input.normalWS);
// Base color
half4 baseColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv) * _Color;
// Fresnel calculation
half fresnel = 1.0 - saturate(dot(input.viewDirWS, normalWS));
// Dual rim layers
half3 innerRim = _InnerRimColor.rgb * pow(fresnel, _InnerRimPower) * _InnerRimIntensity;
half3 outerRim = _OuterRimColor.rgb * pow(fresnel, _OuterRimPower) * _OuterRimIntensity;
// Combine rims (additive)
half3 rimEmission = innerRim + outerRim;
// Texture masking
float mask = 1.0;
if (_UseMask > 0.5)
{
float2 flowUV = input.uv;
if (_FlowSpeed > 0)
{
flowUV += normalize(_FlowDirection.xy) * time * _FlowSpeed;
}
float maskSample = SAMPLE_TEXTURE2D(_RimMask, sampler_RimMask, flowUV).r;
mask = lerp(1.0, maskSample, _MaskStrength);
}
// Directional masking
if (_UseDirectional > 0.5)
{
float3 dirAxis = normalize(_DirectionalAxis.xyz);
float dirDot = dot(normalWS, dirAxis);
float dirMask = saturate(pow(abs(dirDot), _DirectionalSharpness));
mask *= (1.0 - dirMask);
}
rimEmission *= mask;
// Pulse animation
float pulse = 1.0;
if (_PulseSpeed > 0)
{
pulse = sin(time * _PulseSpeed) * _PulseAmount + (1.0 - _PulseAmount);
}
rimEmission *= pulse;
// Color shift animation
if (_ColorShiftSpeed > 0)
{
float3 hsv = rgb2hsv(rimEmission);
hsv.x = frac(hsv.x + time * _ColorShiftSpeed * 0.1);
float3 originalHsv = rgb2hsv(rimEmission);
hsv.x = lerp(originalHsv.x, hsv.x, _ColorShiftAmount);
rimEmission = hsv2rgb(hsv);
}
// Lighting
Light mainLight = GetMainLight();
half NdotL = saturate(dot(normalWS, mainLight.direction));
half3 diffuse = baseColor.rgb * mainLight.color * (NdotL * 0.5 + 0.5);
// Final color
half3 finalColor = diffuse + rimEmission;
return half4(finalColor, 1.0);
}
ENDHLSL
}
// Shadow caster pass
Pass
{
Name ""ShadowCaster""
Tags { ""LightMode"" = ""ShadowCaster"" }
ZWrite On
ZTest LEqual
ColorMask 0
HLSLPROGRAM
#pragma vertex ShadowVert
#pragma fragment ShadowFrag
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl""
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
};
float3 _LightDirection;
Varyings ShadowVert(Attributes input)
{
Varyings output;
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
output.positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection));
return output;
}
half4 ShadowFrag(Varyings input) : SV_Target
{
return 0;
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/FresnelRim""
{
Properties
{
// Base
[Header(Base)]
_Color (""Base Color"", Color) = (0.2, 0.2, 0.2, 1)
_MainTex (""Main Texture"", 2D) = ""white"" {}
// Inner Rim
[Header(Inner Rim)]
[HDR] _InnerRimColor (""Inner Rim Color (HDR)"", Color) = (5, 5, 10, 1)
_InnerRimPower (""Inner Rim Power"", Range(0.1, 10.0)) = 2.0
_InnerRimIntensity (""Inner Rim Intensity"", Range(0, 10)) = 3.0
// Outer Rim
[Header(Outer Rim)]
[HDR] _OuterRimColor (""Outer Rim Color (HDR)"", Color) = (1, 1, 3, 1)
_OuterRimPower (""Outer Rim Power"", Range(0.1, 10.0)) = 5.0
_OuterRimIntensity (""Outer Rim Intensity"", Range(0, 10)) = 1.5
// Texture Masking
[Header(Texture Masking)]
[Toggle] _UseMask (""Use Rim Mask"", Float) = 0
_RimMask (""Rim Mask Texture"", 2D) = ""white"" {}
_MaskStrength (""Mask Strength"", Range(0, 1)) = 1.0
// Directional Masking
[Header(Directional Masking)]
[Toggle] _UseDirectional (""Use Directional Mask"", Float) = 0
_DirectionalAxis (""Directional Axis"", Vector) = (0, 1, 0, 0)
_DirectionalSharpness (""Directional Sharpness"", Range(0.1, 10)) = 1.0
// Animation
[Header(Animation)]
_PulseSpeed (""Pulse Speed"", Range(0, 10)) = 0
_PulseAmount (""Pulse Amount"", Range(0, 1)) = 0.3
_FlowSpeed (""Flow Speed"", Range(0, 5)) = 0
_FlowDirection (""Flow Direction"", Vector) = (1, 0, 0, 0)
_ColorShiftSpeed (""Color Shift Speed"", Range(0, 5)) = 0
_ColorShiftAmount (""Color Shift Amount"", Range(0, 1)) = 0.5
}
SubShader
{
Tags {""RenderType""=""Opaque""}
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.5
// Base
sampler2D _MainTex;
fixed4 _Color;
// Inner Rim
float4 _InnerRimColor;
float _InnerRimPower;
float _InnerRimIntensity;
// Outer Rim
float4 _OuterRimColor;
float _OuterRimPower;
float _OuterRimIntensity;
// Mask
float _UseMask;
sampler2D _RimMask;
float _MaskStrength;
// Directional
float _UseDirectional;
float3 _DirectionalAxis;
float _DirectionalSharpness;
// Animation
float _PulseSpeed;
float _PulseAmount;
float _FlowSpeed;
float2 _FlowDirection;
float _ColorShiftSpeed;
float _ColorShiftAmount;
struct Input
{
float2 uv_MainTex;
float2 uv_RimMask;
float3 viewDir;
float3 worldNormal;
float3 worldPos;
};
// HSV to RGB conversion for color shift
float3 hsv2rgb(float3 c)
{
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * lerp(K.xxx, saturate(p - K.xxx), c.y);
}
float3 rgb2hsv(float3 c)
{
float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), step(c.b, c.g));
float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Base color
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Fresnel calculation
half fresnel = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));
// Dual rim layers
float3 innerRim = _InnerRimColor.rgb * pow(fresnel, _InnerRimPower) * _InnerRimIntensity;
float3 outerRim = _OuterRimColor.rgb * pow(fresnel, _OuterRimPower) * _OuterRimIntensity;
// Combine rims (additive)
float3 rimEmission = innerRim + outerRim;
// Texture masking
float mask = 1.0;
if (_UseMask > 0.5)
{
// Animated UV for flow
float2 flowUV = IN.uv_RimMask;
if (_FlowSpeed > 0)
{
flowUV += normalize(_FlowDirection) * _Time.y * _FlowSpeed;
}
float maskSample = tex2D(_RimMask, flowUV).r;
mask = lerp(1.0, maskSample, _MaskStrength);
}
// Directional masking
if (_UseDirectional > 0.5)
{
float3 worldNorm = normalize(IN.worldNormal);
float3 dirAxis = normalize(_DirectionalAxis);
float dirDot = dot(worldNorm, dirAxis);
float dirMask = saturate(pow(abs(dirDot), _DirectionalSharpness));
mask *= (1.0 - dirMask);
}
rimEmission *= mask;
// Pulse animation
float pulse = 1.0;
if (_PulseSpeed > 0)
{
pulse = sin(_Time.y * _PulseSpeed) * _PulseAmount + (1.0 - _PulseAmount);
}
rimEmission *= pulse;
// Color shift animation
if (_ColorShiftSpeed > 0)
{
float3 hsv = rgb2hsv(rimEmission);
hsv.x = frac(hsv.x + _Time.y * _ColorShiftSpeed * 0.1);
hsv.x = lerp(rgb2hsv(rimEmission).x, hsv.x, _ColorShiftAmount);
rimEmission = hsv2rgb(hsv);
}
o.Emission = rimEmission;
o.Alpha = c.a;
}
ENDCG
}
FallBack ""Standard""
}
";
}
return SaveShaderAndCreateMaterial("FresnelRim", shaderCode);
}
private string GenerateOutlineShader(ShaderGraphController controller)
{
// Check pipeline
bool isURP = controller.targetPipeline.ToLower() == "urp";
string shaderCode;
if (isURP)
{
// URP Version
shaderCode = @"Shader ""Synaptic/URP/Outline""
{
Properties
{
[Header(Base)]
_BaseColor (""Base Color"", Color) = (1, 1, 1, 1)
_BaseMap (""Base Map"", 2D) = ""white"" {}
[Header(Outer Outline)]
_OutlineColor (""Outline Color"", Color) = (0, 0, 0, 1)
_OutlineWidth (""Outline Width"", Range(0, 0.2)) = 0.01
_OutlineMinWidth (""Min Width (Distance)"", Range(0, 0.1)) = 0.005
_OutlineMaxWidth (""Max Width (Distance)"", Range(0, 0.5)) = 0.05
_OutlineDistanceMin (""Distance Min"", Float) = 5
_OutlineDistanceMax (""Distance Max"", Float) = 50
[Header(Inner Outline)]
[Toggle(USE_INNER_OUTLINE)] _UseInnerOutline (""Use Inner Outline"", Float) = 0
_InnerOutlineColor (""Inner Outline Color"", Color) = (0.2, 0.2, 0.2, 1)
_InnerOutlineWidth (""Inner Outline Width"", Range(0, 0.1)) = 0.005
[Header(Depth Settings)]
[Toggle(DEPTH_AWARE)] _DepthAware (""Depth-Aware Outline"", Float) = 1
_DepthSensitivity (""Depth Sensitivity"", Range(0, 10)) = 1
[Header(Normal Settings)]
[Toggle(NORMAL_SMOOTH)] _NormalSmooth (""Use Smooth Normals"", Float) = 1
_NormalSmoothness (""Normal Smoothness"", Range(0, 1)) = 0.5
}
SubShader
{
Tags
{
""RenderType"" = ""Opaque""
""RenderPipeline"" = ""UniversalPipeline""
""Queue"" = ""Geometry""
}
// ===== Outer Outline Pass =====
Pass
{
Name ""Outline""
Tags { ""LightMode"" = ""SRPDefaultUnlit"" }
Cull Front
ZWrite On
ZTest LEqual
HLSLPROGRAM
#pragma vertex OutlineVert
#pragma fragment OutlineFrag
#pragma multi_compile _ DEPTH_AWARE
#pragma multi_compile _ NORMAL_SMOOTH
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _OutlineColor;
float _OutlineWidth;
float _OutlineMinWidth;
float _OutlineMaxWidth;
float _OutlineDistanceMin;
float _OutlineDistanceMax;
float _DepthSensitivity;
float _NormalSmoothness;
CBUFFER_END
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float4 color : COLOR;
};
Varyings OutlineVert(Attributes input)
{
Varyings output;
// World position
float3 positionWS = TransformObjectToWorld(input.positionOS.xyz);
float3 cameraWS = GetCameraPositionWS();
float dist = distance(cameraWS, positionWS);
// Distance-based width
float distanceT = saturate((dist - _OutlineDistanceMin) / (_OutlineDistanceMax - _OutlineDistanceMin));
float dynamicWidth = lerp(_OutlineMinWidth, _OutlineMaxWidth, distanceT);
float finalWidth = _OutlineWidth * dynamicWidth;
// Normal
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
#ifdef NORMAL_SMOOTH
float3 tangentWS = TransformObjectToWorldDir(input.tangentOS.xyz);
float3 bitangentWS = cross(normalWS, tangentWS) * input.tangentOS.w;
float3 smoothNormal = normalize(normalWS + tangentWS * _NormalSmoothness + bitangentWS * _NormalSmoothness);
normalWS = smoothNormal;
#endif
// Extrude
float3 outlinePos = positionWS + normalWS * finalWidth;
#ifdef DEPTH_AWARE
float4 clipPos = TransformWorldToHClip(outlinePos);
float depthFactor = 1.0 + (_DepthSensitivity * 0.001 * clipPos.z);
outlinePos = positionWS + normalWS * finalWidth * depthFactor;
#endif
output.positionCS = TransformWorldToHClip(outlinePos);
output.color = _OutlineColor;
return output;
}
half4 OutlineFrag(Varyings input) : SV_Target
{
return input.color;
}
ENDHLSL
}
// ===== Base Pass =====
Pass
{
Name ""ForwardLit""
Tags { ""LightMode"" = ""UniversalForward"" }
HLSLPROGRAM
#pragma vertex LitPassVertex
#pragma fragment LitPassFragment
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl""
#include ""Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl""
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
float4 _BaseMap_ST;
CBUFFER_END
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normalWS : TEXCOORD1;
};
Varyings LitPassVertex(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
return output;
}
half4 LitPassFragment(Varyings input) : SV_Target
{
half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv);
half4 color = baseMap * _BaseColor;
return color;
}
ENDHLSL
}
}
FallBack ""Hidden/Universal Render Pipeline/FallbackError""
}
";
}
else
{
// Built-in RP Version
shaderCode = @"Shader ""Synaptic/Outline""
{
Properties
{
[Header(Base)]
_Color (""Base Color"", Color) = (1, 1, 1, 1)
_MainTex (""Main Texture"", 2D) = ""white"" {}
[Header(Outer Outline)]
_OutlineColor (""Outline Color"", Color) = (0, 0, 0, 1)
_OutlineWidth (""Outline Width"", Range(0, 0.2)) = 0.01
_OutlineMinWidth (""Min Width (Distance)"", Range(0, 0.1)) = 0.005
_OutlineMaxWidth (""Max Width (Distance)"", Range(0, 0.5)) = 0.05
_OutlineDistanceMin (""Distance Min"", Float) = 5
_OutlineDistanceMax (""Distance Max"", Float) = 50
[Header(Inner Outline)]
[Toggle(USE_INNER_OUTLINE)] _UseInnerOutline (""Use Inner Outline"", Float) = 0
_InnerOutlineColor (""Inner Outline Color"", Color) = (0.2, 0.2, 0.2, 1)
_InnerOutlineWidth (""Inner Outline Width"", Range(0, 0.1)) = 0.005
[Header(Depth Settings)]
[Toggle(DEPTH_AWARE)] _DepthAware (""Depth-Aware Outline"", Float) = 1
_DepthSensitivity (""Depth Sensitivity"", Range(0, 10)) = 1
[Header(Normal Settings)]
[Toggle(NORMAL_SMOOTH)] _NormalSmooth (""Use Smooth Normals"", Float) = 1
_NormalSmoothness (""Normal Smoothness"", Range(0, 1)) = 0.5
[Header(Blend)]
_OutlineBlend (""Outline Blend"", Range(0, 1)) = 1
}
SubShader
{
Tags {""RenderType""=""Opaque"" ""Queue""=""Geometry""}
// ===== Outer Outline Pass =====
Pass
{
Name ""OUTLINE_OUTER""
Cull Front
ZWrite On
ZTest LEqual
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ DEPTH_AWARE
#pragma multi_compile _ NORMAL_SMOOTH
#include ""UnityCG.cginc""
float _OutlineWidth;
float _OutlineMinWidth;
float _OutlineMaxWidth;
float _OutlineDistanceMin;
float _OutlineDistanceMax;
fixed4 _OutlineColor;
float _OutlineBlend;
float _DepthSensitivity;
float _NormalSmoothness;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 color : COLOR;
float depth : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
// Camera distance calculation
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float dist = distance(_WorldSpaceCameraPos, worldPos);
// Distance-based width
float distanceT = saturate((dist - _OutlineDistanceMin) / (_OutlineDistanceMax - _OutlineDistanceMin));
float dynamicWidth = lerp(_OutlineMinWidth, _OutlineMaxWidth, distanceT);
float finalWidth = _OutlineWidth * dynamicWidth;
// Normal calculation
float3 norm = normalize(v.normal);
#ifdef NORMAL_SMOOTH
// Smooth normals using tangent-based calculation
float3 bitangent = cross(v.normal, v.tangent.xyz) * v.tangent.w;
float3 smoothNormal = normalize(v.normal + v.tangent.xyz * _NormalSmoothness + bitangent * _NormalSmoothness);
norm = smoothNormal;
#endif
// Outline extrusion
float3 outlinePos = v.vertex.xyz + norm * finalWidth;
#ifdef DEPTH_AWARE
// Depth-aware adjustment
float4 clipPos = UnityObjectToClipPos(float4(outlinePos, 1));
float depthFactor = 1.0 + (_DepthSensitivity * 0.001 * clipPos.z);
outlinePos = v.vertex.xyz + norm * finalWidth * depthFactor;
#endif
o.pos = UnityObjectToClipPos(float4(outlinePos, 1));
o.color = _OutlineColor;
o.depth = o.pos.z;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return i.color * _OutlineBlend;
}
ENDCG
}
// ===== Inner Outline Pass =====
Pass
{
Name ""OUTLINE_INNER""
Cull Front
ZWrite On
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ USE_INNER_OUTLINE
#include ""UnityCG.cginc""
#ifdef USE_INNER_OUTLINE
float _InnerOutlineWidth;
fixed4 _InnerOutlineColor;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
float3 norm = normalize(v.normal);
float3 outlinePos = v.vertex.xyz + norm * _InnerOutlineWidth;
o.pos = UnityObjectToClipPos(float4(outlinePos, 1));
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _InnerOutlineColor;
}
#else
struct appdata { float4 vertex : POSITION; };
struct v2f { float4 pos : SV_POSITION; };
v2f vert(appdata v) { v2f o; o.pos = float4(0,0,0,1); return o; }
fixed4 frag(v2f i) : SV_Target { discard; return 0; }
#endif
ENDCG
}
// ===== Base Pass =====
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
sampler2D _MainTex;
fixed4 _Color;
struct Input
{
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack ""Standard""
}
";
}
return SaveShaderAndCreateMaterial("Outline", shaderCode);
}
private string SetupLightingScenarios(Dictionary<string, string> parameters)
{
try
{
var scenario = parameters.GetValueOrDefault("scenario", "noon");
var ambientIntensity = float.Parse(parameters.GetValueOrDefault("ambientIntensity", "1.0"));
var shadowStrength = float.Parse(parameters.GetValueOrDefault("shadowStrength", "1.0"));
var colorTemperature = float.Parse(parameters.GetValueOrDefault("colorTemperature", "6500"));
var foggy = bool.Parse(parameters.GetValueOrDefault("foggy", "false"));
var lightingController = new GameObject($"LightingScenario_{scenario}").AddComponent<LightingScenarioController>();
lightingController.scenario = scenario;
lightingController.ambientIntensity = ambientIntensity;
lightingController.shadowStrength = shadowStrength;
lightingController.colorTemperature = colorTemperature;
lightingController.fogEnabled = foggy;
// Apply scenario preset
switch (scenario.ToLower())
{
case "dawn":
ApplyDawnLighting(lightingController);
break;
case "morning":
ApplyMorningLighting(lightingController);
break;
case "noon":
ApplyNoonLighting(lightingController);
break;
case "afternoon":
ApplyAfternoonLighting(lightingController);
break;
case "sunset":
ApplySunsetLighting(lightingController);
break;
case "dusk":
ApplyDuskLighting(lightingController);
break;
case "night":
ApplyNightLighting(lightingController);
break;
case "midnight":
ApplyMidnightLighting(lightingController);
break;
case "studio":
ApplyStudioLighting(lightingController);
break;
case "dramatic":
ApplyDramaticLighting(lightingController);
break;
}
// Apply fog if requested
if (foggy)
{
ApplyAtmosphericFog(lightingController);
}
return $"[NexusLighting] Applied {scenario} lighting scenario. Ambient: {ambientIntensity}, Shadows: {shadowStrength}, Temperature: {colorTemperature}K";
}
catch (Exception e)
{
return CreateErrorResponse("SetupLightingScenarios", e, parameters);
}
}
private void ApplyDawnLighting(LightingScenarioController controller)
{
RenderSettings.ambientSkyColor = new Color(0.4f, 0.5f, 0.7f);
RenderSettings.ambientEquatorColor = new Color(0.5f, 0.4f, 0.3f);
RenderSettings.ambientGroundColor = new Color(0.2f, 0.2f, 0.3f);
RenderSettings.ambientIntensity = controller.ambientIntensity * 0.6f;
var sun = GetOrCreateDirectionalLight("Sun");
sun.color = ColorTemperatureToRGB(4000); // Warm dawn light
sun.intensity = 0.3f;
sun.transform.rotation = Quaternion.Euler(5, -30, 0);
}
private void ApplyMorningLighting(LightingScenarioController controller)
{
RenderSettings.ambientSkyColor = new Color(0.6f, 0.7f, 0.9f);
RenderSettings.ambientEquatorColor = new Color(0.7f, 0.6f, 0.5f);
RenderSettings.ambientGroundColor = new Color(0.3f, 0.3f, 0.4f);
RenderSettings.ambientIntensity = controller.ambientIntensity * 0.8f;
var sun = GetOrCreateDirectionalLight("Sun");
sun.color = ColorTemperatureToRGB(5000);
sun.intensity = 0.7f;
sun.transform.rotation = Quaternion.Euler(30, -45, 0);
}
private void ApplyNoonLighting(LightingScenarioController controller)
{
RenderSettings.ambientSkyColor = new Color(0.5f, 0.6f, 0.8f);
RenderSettings.ambientEquatorColor = new Color(0.6f, 0.6f, 0.6f);
RenderSettings.ambientGroundColor = new Color(0.4f, 0.4f, 0.5f);
RenderSettings.ambientIntensity = controller.ambientIntensity;
var sun = GetOrCreateDirectionalLight("Sun");
sun.color = ColorTemperatureToRGB(controller.colorTemperature);
sun.intensity = 1.0f;
sun.transform.rotation = Quaternion.Euler(50, 0, 0);
}
private void ApplyAfternoonLighting(LightingScenarioController controller)
{
RenderSettings.ambientSkyColor = new Color(0.6f, 0.7f, 0.85f);
RenderSettings.ambientEquatorColor = new Color(0.7f, 0.65f, 0.5f);
RenderSettings.ambientGroundColor = new Color(0.35f, 0.35f, 0.45f);
RenderSettings.ambientIntensity = controller.ambientIntensity * 0.9f;
var sun = GetOrCreateDirectionalLight("Sun");
sun.color = ColorTemperatureToRGB(5500);
sun.intensity = 0.85f;
sun.transform.rotation = Quaternion.Euler(35, 45, 0);
}
private void ApplySunsetLighting(LightingScenarioController controller)
{
RenderSettings.ambientSkyColor = new Color(0.7f, 0.4f, 0.3f);
RenderSettings.ambientEquatorColor = new Color(0.9f, 0.5f, 0.3f);
RenderSettings.ambientGroundColor = new Color(0.2f, 0.1f, 0.1f);
RenderSettings.ambientIntensity = controller.ambientIntensity * 0.7f;
var sun = GetOrCreateDirectionalLight("Sun");
sun.color = ColorTemperatureToRGB(3000); // Very warm sunset
sun.intensity = 0.6f;
sun.transform.rotation = Quaternion.Euler(5, 90, 0);
}
private void ApplyDuskLighting(LightingScenarioController controller)
{
RenderSettings.ambientSkyColor = new Color(0.3f, 0.3f, 0.5f);
RenderSettings.ambientEquatorColor = new Color(0.4f, 0.3f, 0.4f);
RenderSettings.ambientGroundColor = new Color(0.1f, 0.1f, 0.2f);
RenderSettings.ambientIntensity = controller.ambientIntensity * 0.4f;
var sun = GetOrCreateDirectionalLight("Sun");
sun.color = ColorTemperatureToRGB(3500);
sun.intensity = 0.2f;
sun.transform.rotation = Quaternion.Euler(-5, 110, 0);
}
private void ApplyNightLighting(LightingScenarioController controller)
{
RenderSettings.ambientSkyColor = new Color(0.05f, 0.05f, 0.1f);
RenderSettings.ambientEquatorColor = new Color(0.05f, 0.05f, 0.08f);
RenderSettings.ambientGroundColor = new Color(0.02f, 0.02f, 0.05f);
RenderSettings.ambientIntensity = controller.ambientIntensity * 0.2f;
// Disable sun, enable moon
var sun = GameObject.Find("Sun");
if (sun) sun.SetActive(false);
var moon = GetOrCreateDirectionalLight("Moon");
moon.color = ColorTemperatureToRGB(7000); // Cool moonlight
moon.intensity = 0.1f;
moon.transform.rotation = Quaternion.Euler(45, -30, 0);
}
private void ApplyMidnightLighting(LightingScenarioController controller)
{
RenderSettings.ambientSkyColor = new Color(0.02f, 0.02f, 0.05f);
RenderSettings.ambientEquatorColor = new Color(0.02f, 0.02f, 0.04f);
RenderSettings.ambientGroundColor = new Color(0.01f, 0.01f, 0.02f);
RenderSettings.ambientIntensity = controller.ambientIntensity * 0.1f;
var sun = GameObject.Find("Sun");
if (sun) sun.SetActive(false);
var moon = GetOrCreateDirectionalLight("Moon");
moon.color = ColorTemperatureToRGB(8000);
moon.intensity = 0.05f;
moon.transform.rotation = Quaternion.Euler(70, 0, 0);
}
private void ApplyStudioLighting(LightingScenarioController controller)
{
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = Color.gray;
RenderSettings.ambientIntensity = controller.ambientIntensity * 0.5f;
// Three-point lighting setup
var keyLight = GetOrCreateDirectionalLight("KeyLight");
keyLight.color = ColorTemperatureToRGB(5600);
keyLight.intensity = 1.0f;
keyLight.transform.rotation = Quaternion.Euler(45, -45, 0);
var fillLight = GetOrCreateDirectionalLight("FillLight");
fillLight.color = ColorTemperatureToRGB(6500);
fillLight.intensity = 0.5f;
fillLight.transform.rotation = Quaternion.Euler(30, 45, 0);
var backLight = GetOrCreateDirectionalLight("BackLight");
backLight.color = ColorTemperatureToRGB(7000);
backLight.intensity = 0.7f;
backLight.transform.rotation = Quaternion.Euler(30, 180, 0);
}
private void ApplyDramaticLighting(LightingScenarioController controller)
{
RenderSettings.ambientSkyColor = new Color(0.1f, 0.05f, 0.05f);
RenderSettings.ambientEquatorColor = new Color(0.15f, 0.05f, 0.05f);
RenderSettings.ambientGroundColor = Color.black;
RenderSettings.ambientIntensity = controller.ambientIntensity * 0.3f;
var keyLight = GetOrCreateDirectionalLight("DramaticKey");
keyLight.color = ColorTemperatureToRGB(3200); // Warm dramatic light
keyLight.intensity = 1.5f;
keyLight.transform.rotation = Quaternion.Euler(15, -60, 0);
keyLight.shadows = LightShadows.Hard;
}
private void ApplyAtmosphericFog(LightingScenarioController controller)
{
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.Exponential;
RenderSettings.fogDensity = 0.02f * controller.fogDensity;
// Fog color based on scenario
switch (controller.scenario.ToLower())
{
case "dawn":
case "morning":
RenderSettings.fogColor = new Color(0.7f, 0.8f, 0.9f, 0.8f);
break;
case "sunset":
case "dusk":
RenderSettings.fogColor = new Color(0.9f, 0.7f, 0.6f, 0.8f);
break;
case "night":
case "midnight":
RenderSettings.fogColor = new Color(0.1f, 0.1f, 0.15f, 0.9f);
break;
default:
RenderSettings.fogColor = new Color(0.8f, 0.8f, 0.8f, 0.7f);
break;
}
}
private Light GetOrCreateDirectionalLight(string name)
{
var lightGO = GameObject.Find(name);
if (lightGO == null)
{
lightGO = new GameObject(name);
}
var light = lightGO.GetComponent<Light>();
if (light == null)
{
light = lightGO.AddComponent<Light>();
light.type = LightType.Directional;
}
return light;
}
private Color ColorTemperatureToRGB(float kelvin)
{
float temp = kelvin / 100;
float red, green, blue;
if (temp <= 66)
{
red = 255;
green = Mathf.Clamp(99.4708025861f * Mathf.Log(temp) - 161.1195681661f, 0, 255);
}
else
{
red = Mathf.Clamp(329.698727446f * Mathf.Pow(temp - 60, -0.1332047592f), 0, 255);
green = Mathf.Clamp(288.1221695283f * Mathf.Pow(temp - 60, -0.0755148492f), 0, 255);
}
if (temp >= 66)
blue = 255;
else if (temp <= 19)
blue = 0;
else
blue = Mathf.Clamp(138.5177312231f * Mathf.Log(temp - 10) - 305.0447927307f, 0, 255);
return new Color(red / 255f, green / 255f, blue / 255f);
}
// ===== Time of Day System Implementation =====
private string CreateTimeOfDay(Dictionary<string, string> parameters)
{
try
{
var startTime = float.Parse(parameters.GetValueOrDefault("startTime", "12"));
var dayDuration = float.Parse(parameters.GetValueOrDefault("dayDuration", "60"));
var latitude = float.Parse(parameters.GetValueOrDefault("latitude", "35"));
var longitude = float.Parse(parameters.GetValueOrDefault("longitude", "0"));
var enableClouds = bool.Parse(parameters.GetValueOrDefault("enableClouds", "true"));
var enableStars = bool.Parse(parameters.GetValueOrDefault("enableStars", "true"));
var timeSystem = new GameObject("TimeOfDaySystem");
var controller = timeSystem.AddComponent<TimeOfDayController>();
controller.currentTime = startTime;
controller.dayDuration = dayDuration;
controller.latitude = latitude;
controller.longitude = longitude;
controller.enableClouds = enableClouds;
controller.enableStars = enableStars;
// Create sun light
var sunGO = new GameObject("Sun");
sunGO.transform.SetParent(timeSystem.transform);
var sunLight = sunGO.AddComponent<Light>();
sunLight.type = LightType.Directional;
sunLight.intensity = 1;
sunLight.color = Color.white;
sunLight.shadows = LightShadows.Soft;
controller.sunLight = sunLight;
// Create moon light
var moonGO = new GameObject("Moon");
moonGO.transform.SetParent(timeSystem.transform);
var moonLight = moonGO.AddComponent<Light>();
moonLight.type = LightType.Directional;
moonLight.intensity = 0.2f;
moonLight.color = new Color(0.7f, 0.8f, 1f);
moonLight.shadows = LightShadows.Soft;
controller.moonLight = moonLight;
return $"[NexusVisual] Time of Day System created successfully!\n" +
$" Start Time: {startTime}:00\n" +
$" Day Duration: {dayDuration} seconds\n" +
$" Location: Lat {latitude}°, Long {longitude}°\n" +
$" Clouds: {enableClouds}\n" +
$" Stars: {enableStars}\n" +
$" Tip: Adjust dayDuration for faster/slower day-night cycles";
}
catch (Exception e)
{
return CreateErrorResponse("CreateTimeOfDay", e, parameters);
}
}
private string SetTimeOfDay(Dictionary<string, string> parameters)
{
try
{
var targetTime = float.Parse(parameters.GetValueOrDefault("time", "12"));
var instant = bool.Parse(parameters.GetValueOrDefault("instant", "false"));
var transitionDuration = float.Parse(parameters.GetValueOrDefault("transitionDuration", "5"));
var timeSystem = GameObject.Find("TimeOfDaySystem");
if (timeSystem == null)
{
return "Time of Day System not found. Create it first using CreateTimeOfDay";
}
var controller = timeSystem.GetComponent<TimeOfDayController>();
if (controller == null)
{
return "TimeOfDayController component not found";
}
if (instant)
{
controller.currentTime = targetTime;
controller.UpdateTimeOfDay();
}
else
{
controller.TransitionToTime(targetTime, transitionDuration);
}
return $"[NexusVisual] Time set to {targetTime}:00!\n" +
$" Transition: {(instant ? "Instant" : transitionDuration + " seconds")}\n" +
$" Current lighting conditions updated\n" +
$" Tip: Use 6 for sunrise, 12 for noon, 18 for sunset, 0 for midnight";
}
catch (Exception e)
{
return CreateErrorResponse("SetTimeOfDay", e, parameters);
}
}
private string CreateDayNightPreset(Dictionary<string, string> parameters)
{
try
{
var presetName = parameters.GetValueOrDefault("preset", "Default");
var sunriseTime = float.Parse(parameters.GetValueOrDefault("sunriseTime", "6"));
var sunsetTime = float.Parse(parameters.GetValueOrDefault("sunsetTime", "18"));
var sunriseColor = parameters.GetValueOrDefault("sunriseColor", "#FF8C42");
var noonColor = parameters.GetValueOrDefault("noonColor", "#FFFFFF");
var sunsetColor = parameters.GetValueOrDefault("sunsetColor", "#FF6B35");
var nightColor = parameters.GetValueOrDefault("nightColor", "#1E3A8A");
var timeSystem = GameObject.Find("TimeOfDaySystem");
if (timeSystem == null)
{
return "Time of Day System not found. Create it first using CreateTimeOfDay";
}
var controller = timeSystem.GetComponent<TimeOfDayController>();
if (controller == null)
{
return "TimeOfDayController component not found";
}
// Configure preset
controller.sunriseTime = sunriseTime;
controller.sunsetTime = sunsetTime;
if (ColorUtility.TryParseHtmlString(sunriseColor, out Color sunrise))
controller.sunriseColor = sunrise;
if (ColorUtility.TryParseHtmlString(noonColor, out Color noon))
controller.noonColor = noon;
if (ColorUtility.TryParseHtmlString(sunsetColor, out Color sunset))
controller.sunsetColor = sunset;
if (ColorUtility.TryParseHtmlString(nightColor, out Color night))
controller.nightColor = night;
// Apply fog settings based on preset
switch (presetName.ToLower())
{
case "tropical":
controller.fogDensityDay = 0.005f;
controller.fogDensityNight = 0.01f;
break;
case "desert":
controller.fogDensityDay = 0.001f;
controller.fogDensityNight = 0.002f;
break;
case "arctic":
controller.fogDensityDay = 0.02f;
controller.fogDensityNight = 0.015f;
break;
case "urban":
controller.fogDensityDay = 0.01f;
controller.fogDensityNight = 0.02f;
break;
}
return $"[NexusVisual] Day/Night Preset '{presetName}' applied!\n" +
$" Sunrise: {sunriseTime}:00 ({sunriseColor})\n" +
$" Sunset: {sunsetTime}:00 ({sunsetColor})\n" +
$" Noon Color: {noonColor}\n" +
$" Night Color: {nightColor}\n" +
$" Tip: Presets include tropical, desert, arctic, and urban";
}
catch (Exception e)
{
return CreateErrorResponse("CreateDayNightPreset", e, parameters);
}
}
private string CreateSkyboxBlend(Dictionary<string, string> parameters)
{
try
{
var daySkybox = parameters.GetValueOrDefault("daySkybox", "Default-Skybox");
var nightSkybox = parameters.GetValueOrDefault("nightSkybox", "");
var blendCurve = parameters.GetValueOrDefault("blendCurve", "Smooth");
var cloudSpeed = float.Parse(parameters.GetValueOrDefault("cloudSpeed", "0.1"));
var cloudOpacity = float.Parse(parameters.GetValueOrDefault("cloudOpacity", "0.5"));
var skyboxController = new GameObject("SkyboxBlendController");
var controller = skyboxController.AddComponent<SkyboxBlendController>();
controller.cloudSpeed = cloudSpeed;
controller.cloudOpacity = cloudOpacity;
controller.blendCurve = blendCurve;
// Load skybox materials
var dayMaterial = Resources.Load<Material>(daySkybox);
if (dayMaterial != null)
{
controller.daySkybox = dayMaterial;
}
if (!string.IsNullOrEmpty(nightSkybox))
{
var nightMaterial = Resources.Load<Material>(nightSkybox);
if (nightMaterial != null)
{
controller.nightSkybox = nightMaterial;
}
}
// Link to time of day system if exists
var timeSystem = GameObject.Find("TimeOfDaySystem");
if (timeSystem != null)
{
var timeController = timeSystem.GetComponent<TimeOfDayController>();
if (timeController != null)
{
timeController.skyboxController = controller;
}
}
return $"[NexusVisual] Skybox Blend Controller created!\n" +
$" Day Skybox: {daySkybox}\n" +
$" Night Skybox: {(string.IsNullOrEmpty(nightSkybox) ? "Procedural" : nightSkybox)}\n" +
$" Blend Curve: {blendCurve}\n" +
$" Cloud Speed: {cloudSpeed}\n" +
$" Cloud Opacity: {cloudOpacity}\n" +
$" Tip: Links automatically with Time of Day system";
}
catch (Exception e)
{
return CreateErrorResponse("CreateSkyboxBlend", e, parameters);
}
}
/// <summary>
/// Create Skybox material from image(s)
/// </summary>
private string CreateSkyboxFromImage(Dictionary<string, string> parameters)
{
try
{
var type = parameters.GetValueOrDefault("type", "panoramic").ToLower();
var materialName = parameters.GetValueOrDefault("materialName", "CustomSkybox");
var exposure = float.Parse(parameters.GetValueOrDefault("exposure", "1.0"));
var rotation = float.Parse(parameters.GetValueOrDefault("rotation", "0"));
var applyToScene = bool.Parse(parameters.GetValueOrDefault("applyToScene", "true"));
Material skyboxMaterial = null;
string resultInfo = "";
if (type == "panoramic" || type == "hdri" || type == "equirectangular")
{
// Single panorama/HDRI image
var imagePath = parameters.GetValueOrDefault("imagePath", "");
if (string.IsNullOrEmpty(imagePath))
{
return "[Error] imagePath is required for panoramic skybox";
}
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(imagePath);
if (texture == null)
{
return $"[Error] Could not load texture at: {imagePath}\nMake sure the path is relative to Assets folder (e.g., 'Assets/Textures/sky.hdr')";
}
// Create Panoramic Skybox material
var shader = Shader.Find("Skybox/Panoramic");
if (shader == null)
{
return "[Error] Skybox/Panoramic shader not found";
}
skyboxMaterial = new Material(shader);
skyboxMaterial.SetTexture("_MainTex", texture);
skyboxMaterial.SetFloat("_Exposure", exposure);
skyboxMaterial.SetFloat("_Rotation", rotation);
// Set mapping mode (0 = 6 Frames Layout, 1 = Latitude Longitude Layout)
skyboxMaterial.SetFloat("_Mapping", 1); // Lat/Long for panorama
skyboxMaterial.SetFloat("_ImageType", 0); // 0 = 360, 1 = 180
resultInfo = $" Type: Panoramic\n Image: {imagePath}\n";
}
else if (type == "6sided" || type == "cubemap" || type == "6面")
{
// 6 sided skybox
var frontPath = parameters.GetValueOrDefault("front", "");
var backPath = parameters.GetValueOrDefault("back", "");
var leftPath = parameters.GetValueOrDefault("left", "");
var rightPath = parameters.GetValueOrDefault("right", "");
var upPath = parameters.GetValueOrDefault("up", "");
var downPath = parameters.GetValueOrDefault("down", "");
// Check if at least one texture is provided
if (string.IsNullOrEmpty(frontPath) && string.IsNullOrEmpty(backPath) &&
string.IsNullOrEmpty(leftPath) && string.IsNullOrEmpty(rightPath) &&
string.IsNullOrEmpty(upPath) && string.IsNullOrEmpty(downPath))
{
return "[Error] At least one face texture is required for 6-sided skybox.\n" +
"Parameters: front, back, left, right, up, down";
}
var shader = Shader.Find("Skybox/6 Sided");
if (shader == null)
{
return "[Error] Skybox/6 Sided shader not found";
}
skyboxMaterial = new Material(shader);
// Load and set textures for each face
var faces = new Dictionary<string, string>
{
{ "_FrontTex", frontPath },
{ "_BackTex", backPath },
{ "_LeftTex", leftPath },
{ "_RightTex", rightPath },
{ "_UpTex", upPath },
{ "_DownTex", downPath }
};
var loadedFaces = new List<string>();
foreach (var face in faces)
{
if (!string.IsNullOrEmpty(face.Value))
{
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(face.Value);
if (tex != null)
{
skyboxMaterial.SetTexture(face.Key, tex);
loadedFaces.Add($"{face.Key.Replace("_", "").Replace("Tex", "")}: {face.Value}");
}
else
{
SynLog.Warn($"[Skybox] Could not load texture: {face.Value}");
}
}
}
skyboxMaterial.SetFloat("_Exposure", exposure);
skyboxMaterial.SetFloat("_Rotation", rotation);
resultInfo = $" Type: 6-Sided\n Faces loaded: {loadedFaces.Count}/6\n";
foreach (var f in loadedFaces)
{
resultInfo += $" {f}\n";
}
}
else if (type == "sphere" || type == "dome" || type == "landscape")
{
// Sphere interior method - for regular landscape photos
var imagePath = parameters.GetValueOrDefault("imagePath", "");
if (string.IsNullOrEmpty(imagePath))
{
return "[Error] imagePath is required for sphere skybox";
}
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(imagePath);
if (texture == null)
{
return $"[Error] Could not load texture at: {imagePath}\nMake sure the path is relative to Assets folder (e.g., 'Assets/Textures/landscape.jpg')";
}
var sphereRadius = float.Parse(parameters.GetValueOrDefault("radius", "500"));
var followCamera = bool.Parse(parameters.GetValueOrDefault("followCamera", "true"));
var objectName = parameters.GetValueOrDefault("objectName", "SkySphere");
var applyToCameraSkybox = bool.Parse(parameters.GetValueOrDefault("applyToCamera", "true"));
var applyToSceneSkybox = bool.Parse(parameters.GetValueOrDefault("applyToScene", "false"));
// Create inverted sphere mesh
var skyObject = CreateInvertedSphere(objectName, sphereRadius, 64);
// Position at main camera
var mainCam = Camera.main;
if (mainCam != null)
{
skyObject.transform.position = mainCam.transform.position;
}
// Create material with SkySphere shader (Cull Front for inside visibility)
var shader = Shader.Find("Synaptic/SkySphere");
if (shader == null)
{
// Fallback to Unlit/Texture if custom shader not found
shader = Shader.Find("Unlit/Texture");
SynLog.Warn("[Synaptic] SkySphere shader not found, using Unlit/Texture fallback");
}
var sphereMaterial = new Material(shader);
sphereMaterial.mainTexture = texture;
// Apply material
var renderer = skyObject.GetComponent<MeshRenderer>();
renderer.material = sphereMaterial;
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
renderer.receiveShadows = false;
// Apply rotation
skyObject.transform.rotation = Quaternion.Euler(0, rotation, 0);
// Save material
var matSavePath = $"Assets/{materialName}_SphereMat.mat";
AssetDatabase.CreateAsset(sphereMaterial, matSavePath);
// Add camera follow component if requested
if (followCamera)
{
var follower = skyObject.AddComponent<SkySphereCameraFollow>();
follower.Initialize();
}
// Apply to MainCamera's skybox
string cameraApplyResult = "";
if (applyToCameraSkybox && mainCam != null)
{
// Ensure camera uses Skybox clear flags
mainCam.clearFlags = CameraClearFlags.Skybox;
// Add or get Skybox component on camera
var camSkybox = mainCam.GetComponent<Skybox>();
if (camSkybox == null)
{
camSkybox = mainCam.gameObject.AddComponent<Skybox>();
}
camSkybox.material = sphereMaterial;
cameraApplyResult = " Applied to: MainCamera skybox\n";
}
// Apply to scene's RenderSettings
string sceneApplyResult = "";
if (applyToSceneSkybox)
{
RenderSettings.skybox = sphereMaterial;
DynamicGI.UpdateEnvironment();
sceneApplyResult = " Applied to: Scene RenderSettings\n";
}
AssetDatabase.SaveAssets();
return $"[Synaptic] Sky Sphere created successfully!\n" +
$" Type: Sphere (Inverted)\n" +
$" Image: {imagePath}\n" +
$" Radius: {sphereRadius}\n" +
$" Rotation: {rotation}°\n" +
$" Follow Camera: {followCamera}\n" +
$" GameObject: {objectName}\n" +
$" Material saved: {matSavePath}\n" +
cameraApplyResult +
sceneApplyResult +
$"\n💡 Tips for landscape photos:\n" +
$" - Best results with wide panoramic shots\n" +
$" - Avoid obvious horizon lines if possible\n" +
$" - Higher resolution images (4K+) look better\n" +
$" - Adjust 'rotation' to align the view";
}
else
{
return $"[Error] Unknown skybox type: {type}\nSupported types: panoramic, hdri, 6sided, cubemap, sphere, dome, landscape";
}
// Save material to Assets
var savePath = $"Assets/{materialName}.mat";
AssetDatabase.CreateAsset(skyboxMaterial, savePath);
AssetDatabase.SaveAssets();
// Apply to scene
if (applyToScene)
{
RenderSettings.skybox = skyboxMaterial;
DynamicGI.UpdateEnvironment();
}
return $"[Synaptic] Skybox created successfully!\n" +
resultInfo +
$" Exposure: {exposure}\n" +
$" Rotation: {rotation}°\n" +
$" Material saved: {savePath}\n" +
$" Applied to scene: {applyToScene}";
}
catch (Exception e)
{
return CreateErrorResponse("CreateSkyboxFromImage", e, parameters);
}
}
/// <summary>
/// Create a sphere with inverted normals (visible from inside)
/// </summary>
private GameObject CreateInvertedSphere(string name, float radius, int segments)
{
var go = new GameObject(name);
var meshFilter = go.AddComponent<MeshFilter>();
var meshRenderer = go.AddComponent<MeshRenderer>();
// Generate sphere mesh with inverted normals
var mesh = new Mesh();
mesh.name = "InvertedSphere";
int latSegments = segments;
int lonSegments = segments * 2;
var vertices = new List<Vector3>();
var normals = new List<Vector3>();
var uvs = new List<Vector2>();
var triangles = new List<int>();
// Generate vertices
for (int lat = 0; lat <= latSegments; lat++)
{
float theta = lat * Mathf.PI / latSegments;
float sinTheta = Mathf.Sin(theta);
float cosTheta = Mathf.Cos(theta);
for (int lon = 0; lon <= lonSegments; lon++)
{
float phi = lon * 2 * Mathf.PI / lonSegments;
float sinPhi = Mathf.Sin(phi);
float cosPhi = Mathf.Cos(phi);
float x = cosPhi * sinTheta;
float y = cosTheta;
float z = sinPhi * sinTheta;
vertices.Add(new Vector3(x, y, z) * radius);
// Inverted normals (pointing inward)
normals.Add(new Vector3(-x, -y, -z));
// Flip U coordinate for correct texture orientation from inside
uvs.Add(new Vector2(1.0f - (float)lon / lonSegments, 1.0f - (float)lat / latSegments));
}
}
// Generate triangles (reversed winding for inside view)
for (int lat = 0; lat < latSegments; lat++)
{
for (int lon = 0; lon < lonSegments; lon++)
{
int first = lat * (lonSegments + 1) + lon;
int second = first + lonSegments + 1;
// Reversed winding order for inside visibility
triangles.Add(first);
triangles.Add(first + 1);
triangles.Add(second);
triangles.Add(second);
triangles.Add(first + 1);
triangles.Add(second + 1);
}
}
mesh.vertices = vertices.ToArray();
mesh.normals = normals.ToArray();
mesh.uv = uvs.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateBounds();
meshFilter.mesh = mesh;
// Set render queue to background
meshRenderer.sortingOrder = -32767;
return go;
}
private string CreateTimeEvent(Dictionary<string, string> parameters)
{
try
{
var eventName = parameters.GetValueOrDefault("eventName", "TimeEvent");
var triggerTime = float.Parse(parameters.GetValueOrDefault("triggerTime", "6"));
var eventType = parameters.GetValueOrDefault("eventType", "Once");
var action = parameters.GetValueOrDefault("action", "");
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var timeSystem = GameObject.Find("TimeOfDaySystem");
if (timeSystem == null)
{
return "Time of Day System not found. Create it first using CreateTimeOfDay";
}
var controller = timeSystem.GetComponent<TimeOfDayController>();
if (controller == null)
{
return "TimeOfDayController component not found";
}
var timeEvent = new TimeEvent
{
eventName = eventName,
triggerTime = triggerTime,
eventType = eventType,
action = action,
targetObject = targetObject
};
controller.AddTimeEvent(timeEvent);
return $"[NexusVisual] Time Event '{eventName}' created!\n" +
$" Trigger Time: {triggerTime}:00\n" +
$" Type: {eventType}\n" +
$" Action: {action}\n" +
$" Target: {targetObject}\n" +
$" Tip: Use for triggering gameplay events at specific times";
}
catch (Exception e)
{
return CreateErrorResponse("CreateTimeEvent", e, parameters);
}
}
private string SetupCamera(Dictionary<string, string> parameters)
{
var preset = parameters.GetValueOrDefault("preset", "default");
var mainCamera = Camera.main;
if (mainCamera == null)
{
return "No main camera found in scene";
}
switch (preset.ToLower())
{
case "topdown":
mainCamera.transform.position = new Vector3(0, 10, 0);
mainCamera.transform.rotation = Quaternion.Euler(90, 0, 0);
mainCamera.orthographic = true;
mainCamera.orthographicSize = 10;
break;
case "side":
mainCamera.transform.position = new Vector3(10, 0, 0);
mainCamera.transform.rotation = Quaternion.Euler(0, -90, 0);
break;
case "isometric":
mainCamera.transform.position = new Vector3(10, 10, 10);
mainCamera.transform.rotation = Quaternion.Euler(30, -45, 0);
mainCamera.orthographic = true;
mainCamera.orthographicSize = 8;
break;
case "fps":
mainCamera.transform.position = new Vector3(0, 1.8f, 0);
mainCamera.transform.rotation = Quaternion.identity;
mainCamera.orthographic = false;
mainCamera.fieldOfView = 75;
break;
case "custom":
default:
// Custom settings or maintain current settings
if (parameters.TryGetValue("position", out var pos))
mainCamera.transform.position = ParseVector3(pos);
if (parameters.TryGetValue("rotation", out var rot))
mainCamera.transform.rotation = Quaternion.Euler(ParseVector3(rot));
if (parameters.TryGetValue("fov", out var fov))
mainCamera.fieldOfView = float.Parse(fov);
if (parameters.TryGetValue("orthographic", out var ortho))
mainCamera.orthographic = bool.Parse(ortho);
break;
}
Selection.activeGameObject = mainCamera.gameObject;
return $"Camera setup completed: {preset}";
}
private string UndoOperation()
{
try
{
// Use Unity editor's UNDO system
UnityEditor.Undo.PerformUndo();
return "Previous operation undone";
}
catch (System.InvalidOperationException)
{
return "No operations available to UNDO";
}
catch (System.Exception e)
{
return $"UNDO execution error: {e.Message}";
}
}
private string RedoOperation()
{
try
{
// Use Unity editor's REDO system
UnityEditor.Undo.PerformRedo();
return "Operation redone";
}
catch (System.InvalidOperationException)
{
return "No operations available to REDO";
}
catch (System.Exception e)
{
return $"REDO execution error: {e.Message}";
}
}
private string GetOperationHistory()
{
return NexusOperationHistory.Instance.ExportHistory();
}
private string CreateCheckpoint(Dictionary<string, string> parameters)
{
try
{
string name = parameters.ContainsKey("name") ? parameters["name"] : $"Checkpoint_{DateTime.Now:yyyyMMdd_HHmmss}";
string description = parameters.ContainsKey("description") ? parameters["description"] : "Manual checkpoint";
bool success = NexusOperationHistory.Instance.CreateCheckpoint(name, description);
return success ? $"Checkpoint '{name}' created successfully" : "Failed to create checkpoint";
}
catch (Exception e)
{
return $"Error creating checkpoint: {e.Message}";
}
}
private string RestoreCheckpoint(Dictionary<string, string> parameters)
{
try
{
if (!parameters.ContainsKey("name"))
return "Error: Checkpoint name not specified";
string name = parameters["name"];
bool success = NexusOperationHistory.Instance.RestoreCheckpoint(name);
return success ? $"Checkpoint '{name}' restored successfully" : $"Failed to restore checkpoint '{name}'";
}
catch (Exception e)
{
return $"Error restoring checkpoint: {e.Message}";
}
}
// Real-time event monitoring method
private string StartPlayStateMonitoring(Dictionary<string, string> parameters)
{
try
{
bool enable = !parameters.ContainsKey("enable") || bool.Parse(parameters.GetValueOrDefault("enable", "true"));
bool success = NexusEventMonitor.Instance.StartPlayStateMonitoring(enable);
return success ?
$"Play state monitoring {(enable ? "started" : "stopped")} successfully" :
"Failed to change play state monitoring";
}
catch (Exception e)
{
return $"Error in play state monitoring: {e.Message}";
}
}
private string StartFileChangeMonitoring(Dictionary<string, string> parameters)
{
try
{
bool enable = !parameters.ContainsKey("enable") || bool.Parse(parameters.GetValueOrDefault("enable", "true"));
bool success = NexusEventMonitor.Instance.StartFileChangeMonitoring(enable);
return success ?
$"File change monitoring {(enable ? "started" : "stopped")} successfully" :
"Failed to change file change monitoring";
}
catch (Exception e)
{
return $"Error in file change monitoring: {e.Message}";
}
}
private string StartCompileMonitoring(Dictionary<string, string> parameters)
{
try
{
bool enable = !parameters.ContainsKey("enable") || bool.Parse(parameters.GetValueOrDefault("enable", "true"));
bool success = NexusEventMonitor.Instance.StartCompileMonitoring(enable);
return success ?
$"Compile monitoring {(enable ? "started" : "stopped")} successfully" :
"Failed to change compile monitoring";
}
catch (Exception e)
{
return $"Error in compile monitoring: {e.Message}";
}
}
private string SubscribeToEvents(Dictionary<string, string> parameters)
{
try
{
if (!parameters.ContainsKey("event_type"))
return "Error: event_type parameter required";
string eventType = parameters["event_type"];
string subscriberId = parameters.GetValueOrDefault("subscriber_id", Guid.NewGuid().ToString());
bool success = NexusEventMonitor.Instance.SubscribeToEvent(eventType, subscriberId);
return success ?
$"Successfully subscribed to event '{eventType}' with ID '{subscriberId}'" :
$"Failed to subscribe to event '{eventType}'";
}
catch (Exception e)
{
return $"Error subscribing to events: {e.Message}";
}
}
private string GetRecentEvents(Dictionary<string, string> parameters)
{
try
{
int count = int.Parse(parameters.GetValueOrDefault("count", "10"));
return NexusEventMonitor.Instance.GetRecentEvents(count);
}
catch (Exception e)
{
return $"Error getting recent events: {e.Message}";
}
}
private string GetMonitoringStatus()
{
try
{
return NexusEventMonitor.Instance.GetMonitoringStatus();
}
catch (Exception e)
{
return $"Error getting monitoring status: {e.Message}";
}
}
private string PlaceObjects(Dictionary<string, string> parameters)
{
var objectType = parameters.GetValueOrDefault("objectType", "Cube");
var pattern = parameters.GetValueOrDefault("pattern", "grid");
var countStr = parameters.GetValueOrDefault("count", "5");
var spacing = float.Parse(parameters.GetValueOrDefault("spacing", "2"));
if (!int.TryParse(countStr, out int count))
{
count = 5;
}
var createdObjects = new List<GameObject>();
switch (pattern.ToLower())
{
case "grid":
int gridSize = Mathf.CeilToInt(Mathf.Sqrt(count));
for (int i = 0; i < count; i++)
{
int x = i % gridSize;
int z = i / gridSize;
var position = new Vector3(x * spacing, 0, z * spacing);
var obj = CreateObjectAtPosition(objectType, position, i);
createdObjects.Add(obj);
}
break;
case "circle":
float radius = spacing * count / (2 * Mathf.PI);
for (int i = 0; i < count; i++)
{
float angle = i * (360f / count) * Mathf.Deg2Rad;
var position = new Vector3(Mathf.Cos(angle) * radius, 0, Mathf.Sin(angle) * radius);
var obj = CreateObjectAtPosition(objectType, position, i);
createdObjects.Add(obj);
}
break;
case "line":
for (int i = 0; i < count; i++)
{
var position = new Vector3(i * spacing, 0, 0);
var obj = CreateObjectAtPosition(objectType, position, i);
createdObjects.Add(obj);
}
break;
case "random":
float range = spacing * count / 2;
for (int i = 0; i < count; i++)
{
var position = new Vector3(
UnityEngine.Random.Range(-range, range),
0,
UnityEngine.Random.Range(-range, range)
);
var obj = CreateObjectAtPosition(objectType, position, i);
createdObjects.Add(obj);
}
break;
}
// Record operation to history
var paramDict = new Dictionary<string, object>();
foreach (var kvp in parameters)
{
paramDict[kvp.Key] = kvp.Value;
}
NexusOperationHistory.Instance.RecordOperation(
"PLACE_OBJECTS",
$"Place {count} {objectType} objects in {pattern} pattern",
paramDict,
null
);
return $"Placed {count} {objectType} objects in {pattern} pattern";
}
private GameObject CreateObjectAtPosition(string objectType, Vector3 position, int index)
{
GameObject obj = null;
switch (objectType.ToLower())
{
case "cube":
obj = CreatePrimitiveWithMaterial(PrimitiveType.Cube);
break;
case "sphere":
obj = CreatePrimitiveWithMaterial(PrimitiveType.Sphere);
break;
case "cylinder":
obj = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder);
break;
case "capsule":
obj = CreatePrimitiveWithMaterial(PrimitiveType.Capsule);
break;
case "plane":
obj = CreatePrimitiveWithMaterial(PrimitiveType.Plane);
break;
default:
obj = new GameObject();
break;
}
obj.name = $"{objectType}_{index}";
obj.transform.position = position;
return obj;
}
private string GetGameObjectDetails(Dictionary<string, string> parameters)
{
var targetName = parameters.GetValueOrDefault("name") ?? parameters.GetValueOrDefault("target");
GameObject target = null;
if (!string.IsNullOrEmpty(targetName))
{
target = GameObject.Find(targetName);
}
if (target == null)
{
return "GameObject not found: " + targetName;
}
var details = new System.Text.StringBuilder();
details.AppendLine($"=== GameObject Details: {target.name} ===");
// Transform info
details.AppendLine("\n[Transform]");
details.AppendLine($"Position: {target.transform.position}");
details.AppendLine($"Rotation: {target.transform.rotation.eulerAngles}");
details.AppendLine($"Scale: {target.transform.localScale}");
details.AppendLine($"Active: {target.activeInHierarchy}");
details.AppendLine($"Layer: {target.layer} ({LayerMask.LayerToName(target.layer)})");
details.AppendLine($"Tag: {target.tag}");
// Parent-child relationships
if (target.transform.parent != null)
{
details.AppendLine($"Parent: {target.transform.parent.name}");
}
if (target.transform.childCount > 0)
{
details.AppendLine($"Children: {target.transform.childCount}");
for (int i = 0; i < target.transform.childCount && i < 10; i++)
{
details.AppendLine($" - {target.transform.GetChild(i).name}");
}
if (target.transform.childCount > 10)
{
details.AppendLine($" ... and {target.transform.childCount - 10} more");
}
}
// Component info
details.AppendLine("\n[Components]");
var components = target.GetComponents<Component>();
foreach (var comp in components)
{
if (comp == null) continue;
details.AppendLine($"- {comp.GetType().Name}");
// Specific component details
if (comp is MeshRenderer mr)
{
details.AppendLine($" - Enabled: {mr.enabled}");
if (mr.sharedMaterial != null)
{
details.AppendLine($" - Material: {mr.sharedMaterial.name}");
}
}
else if (comp is MeshFilter mf && mf.sharedMesh != null)
{
details.AppendLine($" - Mesh: {mf.sharedMesh.name}");
details.AppendLine($" - Vertices: {mf.sharedMesh.vertexCount}");
}
else if (comp is Collider col)
{
details.AppendLine($" - Type: {col.GetType().Name}");
details.AppendLine($" - Enabled: {col.enabled}");
details.AppendLine($" - Trigger: {col.isTrigger}");
}
else if (comp is Rigidbody rb)
{
details.AppendLine($" - Mass: {rb.mass}");
details.AppendLine($" - Kinematic: {rb.isKinematic}");
details.AppendLine($" - Use Gravity: {rb.useGravity}");
}
}
return details.ToString();
}
private string GetSceneInfo(Dictionary<string, string> parameters)
{
try
{
var sceneInfo = new Dictionary<string, object>();
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
// Basic info
sceneInfo["scene_name"] = activeScene.name;
sceneInfo["scene_path"] = activeScene.path;
sceneInfo["is_dirty"] = activeScene.isDirty;
sceneInfo["is_loaded"] = activeScene.isLoaded;
sceneInfo["build_index"] = activeScene.buildIndex;
// GameObject hierarchy
var rootObjects = activeScene.GetRootGameObjects();
var hierarchy = new List<Dictionary<string, object>>();
foreach (var root in rootObjects)
{
hierarchy.Add(GetGameObjectHierarchy(root));
}
sceneInfo["hierarchy"] = hierarchy;
// Statistics info
var statistics = new Dictionary<string, object>();
statistics["total_root_objects"] = rootObjects.Length;
int totalGameObjects = 0;
int totalComponents = 0;
var componentCounts = new Dictionary<string, int>();
foreach (var root in rootObjects)
{
CountGameObjectsAndComponents(root.transform, ref totalGameObjects, ref totalComponents, componentCounts);
}
statistics["total_gameobjects"] = totalGameObjects;
statistics["total_components"] = totalComponents;
statistics["component_breakdown"] = componentCounts;
sceneInfo["statistics"] = statistics;
// Lighting info
var lightingInfo = new Dictionary<string, object>();
lightingInfo["ambient_mode"] = RenderSettings.ambientMode.ToString();
lightingInfo["ambient_color"] = ColorToDict(RenderSettings.ambientLight);
lightingInfo["fog_enabled"] = RenderSettings.fog;
if (RenderSettings.fog)
{
lightingInfo["fog_color"] = ColorToDict(RenderSettings.fogColor);
lightingInfo["fog_mode"] = RenderSettings.fogMode.ToString();
lightingInfo["fog_density"] = RenderSettings.fogDensity;
}
if (RenderSettings.skybox != null)
{
lightingInfo["skybox"] = RenderSettings.skybox.name;
}
sceneInfo["lighting"] = lightingInfo;
// Camera info
var cameras = GameObject.FindObjectsOfType<Camera>();
var cameraList = new List<Dictionary<string, object>>();
foreach (var cam in cameras)
{
var camInfo = new Dictionary<string, object>
{
["name"] = cam.name,
["enabled"] = cam.enabled,
["is_main"] = cam == Camera.main,
["position"] = Vector3ToDict(cam.transform.position),
["fov"] = cam.fieldOfView,
["depth"] = cam.depth,
["rendering_path"] = cam.renderingPath.ToString()
};
cameraList.Add(camInfo);
}
sceneInfo["cameras"] = cameraList;
// Return in JSON format
return JsonConvert.SerializeObject(sceneInfo, Formatting.Indented);
}
catch (Exception e)
{
return $"Error getting scene info: {e.Message}";
}
}
private Dictionary<string, object> GetGameObjectHierarchy(GameObject obj)
{
var info = new Dictionary<string, object>();
info["name"] = obj.name;
info["active"] = obj.activeInHierarchy;
info["tag"] = obj.tag;
info["layer"] = LayerMask.LayerToName(obj.layer);
// Transform information
var transform = obj.transform;
var transformInfo = new Dictionary<string, object>
{
["position_world"] = Vector3ToDict(transform.position),
["position_local"] = Vector3ToDict(transform.localPosition),
["rotation_world"] = QuaternionToDict(transform.rotation),
["rotation_local"] = QuaternionToDict(transform.localRotation),
["euler_angles_world"] = Vector3ToDict(transform.eulerAngles),
["euler_angles_local"] = Vector3ToDict(transform.localEulerAngles),
["scale_local"] = Vector3ToDict(transform.localScale)
};
info["transform"] = transformInfo;
// RectTransform specific information (for UI elements)
var rectTransform = obj.GetComponent<RectTransform>();
if (rectTransform != null)
{
var rectInfo = new Dictionary<string, object>
{
["anchor_min"] = Vector2ToDict(rectTransform.anchorMin),
["anchor_max"] = Vector2ToDict(rectTransform.anchorMax),
["anchored_position"] = Vector2ToDict(rectTransform.anchoredPosition),
["anchored_position_3d"] = Vector3ToDict(rectTransform.anchoredPosition3D),
["size_delta"] = Vector2ToDict(rectTransform.sizeDelta),
["pivot"] = Vector2ToDict(rectTransform.pivot),
["rect"] = new Dictionary<string, object>
{
["x"] = rectTransform.rect.x,
["y"] = rectTransform.rect.y,
["width"] = rectTransform.rect.width,
["height"] = rectTransform.rect.height,
["center"] = Vector2ToDict(rectTransform.rect.center),
["min"] = Vector2ToDict(rectTransform.rect.min),
["max"] = Vector2ToDict(rectTransform.rect.max)
},
["offset_min"] = Vector2ToDict(rectTransform.offsetMin),
["offset_max"] = Vector2ToDict(rectTransform.offsetMax)
};
info["rect_transform"] = rectInfo;
}
// Component list
var components = new List<string>();
foreach (var comp in obj.GetComponents<Component>())
{
if (comp != null)
components.Add(comp.GetType().Name);
}
info["components"] = components;
// Child objects
if (obj.transform.childCount > 0)
{
var children = new List<Dictionary<string, object>>();
for (int i = 0; i < obj.transform.childCount; i++)
{
children.Add(GetGameObjectHierarchy(obj.transform.GetChild(i).gameObject));
}
info["children"] = children;
}
return info;
}
private void CountGameObjectsAndComponents(Transform transform, ref int gameObjectCount, ref int componentCount, Dictionary<string, int> componentCounts)
{
gameObjectCount++;
var components = transform.GetComponents<Component>();
componentCount += components.Length;
foreach (var comp in components)
{
if (comp != null)
{
string typeName = comp.GetType().Name;
if (componentCounts.ContainsKey(typeName))
componentCounts[typeName]++;
else
componentCounts[typeName] = 1;
}
}
for (int i = 0; i < transform.childCount; i++)
{
CountGameObjectsAndComponents(transform.GetChild(i), ref gameObjectCount, ref componentCount, componentCounts);
}
}
#region Scene API v1.1.0 - Lightweight Scene Information
private string GetSceneSummary(Dictionary<string, string> parameters)
{
try
{
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
var allObjects = GameObject.FindObjectsOfType<GameObject>(true);
var rootObjects = activeScene.GetRootGameObjects();
var summary = new Dictionary<string, object>
{
["scene_name"] = activeScene.name,
["scene_path"] = activeScene.path,
["build_index"] = activeScene.buildIndex,
["is_dirty"] = activeScene.isDirty,
["total_gameobjects"] = allObjects.Length,
["active_gameobjects"] = allObjects.Count(go => go.activeInHierarchy),
["cameras"] = new Dictionary<string, object>
{
["count"] = Camera.allCameras.Length,
["main"] = Camera.main != null ? Camera.main.name : null
},
["lights"] = GetLightCounts(),
["root_gameobjects"] = rootObjects.Take(50).Select(go => new Dictionary<string, object>
{
["name"] = go.name,
["active"] = go.activeInHierarchy,
["child_count"] = go.transform.childCount,
["layer"] = LayerMask.LayerToName(go.layer),
["tag"] = go.tag
}).ToList()
};
return JsonConvert.SerializeObject(summary, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = $"Error getting scene summary: {e.Message}" });
}
}
private string GetGameObjectsList(Dictionary<string, string> parameters)
{
try
{
parameters.TryGetValue("layerFilter", out string layerFilter);
// Also accept "layer" as alias for "layerFilter"
if (string.IsNullOrEmpty(layerFilter))
parameters.TryGetValue("layer", out layerFilter);
parameters.TryGetValue("tagFilter", out string tagFilter);
// Also accept "tag" as alias for "tagFilter"
if (string.IsNullOrEmpty(tagFilter))
parameters.TryGetValue("tag", out tagFilter);
parameters.TryGetValue("nameFilter", out string nameFilter);
// Also accept "name" as alias for "nameFilter"
if (string.IsNullOrEmpty(nameFilter))
parameters.TryGetValue("name", out nameFilter);
parameters.TryGetValue("activeOnly", out string activeOnlyStr);
parameters.TryGetValue("maxCount", out string maxCountStr);
bool activeOnly = bool.TryParse(activeOnlyStr, out bool ao) && ao;
int maxCount = int.TryParse(maxCountStr, out int mc) ? mc : 100;
var allObjects = GameObject.FindObjectsOfType<GameObject>(true);
IEnumerable<GameObject> filtered = allObjects;
if (!string.IsNullOrEmpty(layerFilter))
filtered = filtered.Where(go => LayerMask.LayerToName(go.layer) == layerFilter);
if (!string.IsNullOrEmpty(tagFilter))
filtered = filtered.Where(go => go.tag == tagFilter);
if (!string.IsNullOrEmpty(nameFilter))
filtered = filtered.Where(go => go.name.Contains(nameFilter));
if (activeOnly)
filtered = filtered.Where(go => go.activeInHierarchy);
var result = filtered
.Take(maxCount)
.Select(go => new Dictionary<string, object>
{
["name"] = go.name,
["instance_id"] = go.GetInstanceID(),
["path"] = GetGameObjectPath(go),
["active"] = go.activeInHierarchy,
["layer"] = LayerMask.LayerToName(go.layer),
["tag"] = go.tag,
["component_count"] = go.GetComponents<Component>().Length
})
.ToList();
return JsonConvert.SerializeObject(new
{
success = true,
count = result.Count,
total_matches = filtered.Count(),
objects = result
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { error = $"Error getting GameObjects list: {e.Message}" });
}
}
private string GetGameObjectDetail(Dictionary<string, string> parameters)
{
try
{
if (!parameters.TryGetValue("nameOrId", out string nameOrId))
{
return JsonConvert.SerializeObject(new { error = "Missing 'nameOrId' parameter" });
}
GameObject target = null;
// Try by instanceId first
if (int.TryParse(nameOrId, out int id))
{
target = EditorUtility.InstanceIDToObject(id) as GameObject;
}
// Try by name
if (target == null)
{
target = GameObject.Find(nameOrId);
}
// Try finding in all objects (including inactive)
if (target == null)
{
var allObjects = GameObject.FindObjectsOfType<GameObject>(true);
target = allObjects.FirstOrDefault(go => go.name == nameOrId);
}
if (target == null)
{
return JsonConvert.SerializeObject(new { error = "GameObject not found" });
}
var detail = new Dictionary<string, object>
{
["name"] = target.name,
["instance_id"] = target.GetInstanceID(),
["path"] = GetGameObjectPath(target),
["active"] = target.activeInHierarchy,
["layer"] = LayerMask.LayerToName(target.layer),
["tag"] = target.tag,
["transform"] = new Dictionary<string, object>
{
["position"] = Vector3ToDict(target.transform.position),
["local_position"] = Vector3ToDict(target.transform.localPosition),
["rotation"] = Vector3ToDict(target.transform.rotation.eulerAngles),
["local_rotation"] = Vector3ToDict(target.transform.localEulerAngles),
["scale"] = Vector3ToDict(target.transform.localScale)
},
["components"] = target.GetComponents<Component>()
.Where(c => c != null)
.Select(c => new Dictionary<string, object>
{
["type"] = c.GetType().Name,
["enabled"] = (c is Behaviour behaviour) ? behaviour.enabled : true,
["details"] = GetComponentDetails(c)
})
.ToList(),
["children"] = target.transform.Cast<Transform>()
.Select(child => new Dictionary<string, object>
{
["name"] = child.name,
["instance_id"] = child.gameObject.GetInstanceID(),
["active"] = child.gameObject.activeInHierarchy
})
.ToList(),
["parent"] = target.transform.parent != null ? new Dictionary<string, object>
{
["name"] = target.transform.parent.name,
["instance_id"] = target.transform.parent.gameObject.GetInstanceID()
} : null
};
return JsonConvert.SerializeObject(detail, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = $"Error getting GameObject detail: {e.Message}" });
}
}
private object GetComponentDetails(Component component)
{
try
{
switch (component)
{
case MeshRenderer mr:
return new { materials = mr.sharedMaterials.Select(m => m != null ? m.name : null).ToArray() };
case Light light:
return new { type = light.type.ToString(), color = ColorToDict(light.color), intensity = light.intensity };
case Camera camera:
return new { fov = camera.fieldOfView, depth = camera.depth, clear_flags = camera.clearFlags.ToString() };
case Rigidbody rb:
return new { mass = rb.mass, use_gravity = rb.useGravity, is_kinematic = rb.isKinematic };
case Collider col:
return new { is_trigger = col.isTrigger, enabled = col.enabled };
case Animator animator:
return new { controller = animator.runtimeAnimatorController != null ? animator.runtimeAnimatorController.name : null };
case Canvas canvas:
return new { render_mode = canvas.renderMode.ToString(), sort_order = canvas.sortingOrder };
default:
// Return basic serializable properties for unhandled components
try
{
var type = component.GetType();
var properties = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(p => p.CanRead && IsSimpleType(p.PropertyType))
.Take(10) // Limit to avoid too much data
.ToDictionary(
p => p.Name,
p => {
try { return p.GetValue(component)?.ToString(); }
catch { return null; }
}
);
return properties.Count > 0 ? properties : new { type_name = type.Name };
}
catch
{
return new { type_name = component.GetType().Name };
}
}
}
catch
{
return null;
}
}
private bool IsSimpleType(Type type)
{
return type.IsPrimitive || type == typeof(string) || type == typeof(decimal) ||
type.IsEnum || type == typeof(bool);
}
private string GetSceneChangesSince(Dictionary<string, string> parameters)
{
try
{
if (!parameters.TryGetValue("timestamp", out string timestampStr))
{
return JsonConvert.SerializeObject(new { error = "Missing 'timestamp' parameter" });
}
if (!DateTime.TryParse(timestampStr, out DateTime since))
{
return JsonConvert.SerializeObject(new { error = "Invalid timestamp format. Use ISO 8601 format." });
}
var sceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
SceneSnapshot oldSnapshot = null;
if (sceneSnapshots.ContainsKey(sceneName))
{
oldSnapshot = sceneSnapshots[sceneName];
}
// Create current snapshot
var currentObjects = GameObject.FindObjectsOfType<GameObject>(true);
var currentSnapshot = new SceneSnapshot
{
timestamp = DateTime.Now,
gameObjectIds = new HashSet<int>(currentObjects.Select(go => go.GetInstanceID())),
gameObjectHashes = currentObjects.ToDictionary(
go => go.GetInstanceID(),
go => GetGameObjectHash(go)
)
};
if (oldSnapshot == null || oldSnapshot.timestamp < since)
{
// First time or snapshot too old - return full summary
sceneSnapshots[sceneName] = currentSnapshot;
return GetSceneSummary(new Dictionary<string, string>());
}
// Calculate differences
var added = currentSnapshot.gameObjectIds.Except(oldSnapshot.gameObjectIds).ToList();
var removed = oldSnapshot.gameObjectIds.Except(currentSnapshot.gameObjectIds).ToList();
var modified = currentSnapshot.gameObjectIds
.Intersect(oldSnapshot.gameObjectIds)
.Where(id => currentSnapshot.gameObjectHashes.ContainsKey(id) &&
oldSnapshot.gameObjectHashes.ContainsKey(id) &&
currentSnapshot.gameObjectHashes[id] != oldSnapshot.gameObjectHashes[id])
.ToList();
sceneSnapshots[sceneName] = currentSnapshot;
return JsonConvert.SerializeObject(new
{
since = since,
now = DateTime.Now,
changes = new
{
added = added.Select(id => EditorUtility.InstanceIDToObject(id) as GameObject)
.Where(go => go != null)
.Select(go => new { name = go.name, instance_id = go.GetInstanceID() })
.ToList(),
removed = removed,
modified = modified.Select(id => EditorUtility.InstanceIDToObject(id) as GameObject)
.Where(go => go != null)
.Select(go => new { name = go.name, instance_id = go.GetInstanceID() })
.ToList()
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { error = $"Error getting scene changes: {e.Message}" });
}
}
private int GetGameObjectHash(GameObject go)
{
unchecked
{
int hash = 17;
hash = hash * 31 + go.transform.position.GetHashCode();
hash = hash * 31 + go.transform.rotation.GetHashCode();
hash = hash * 31 + go.transform.localScale.GetHashCode();
hash = hash * 31 + go.activeInHierarchy.GetHashCode();
hash = hash * 31 + go.GetComponents<Component>().Length;
return hash;
}
}
#endregion
private Dictionary<string, float> ColorToDict(Color color)
{
return new Dictionary<string, float>
{
["r"] = color.r,
["g"] = color.g,
["b"] = color.b,
["a"] = color.a
};
}
private Dictionary<string, float> Vector3ToDict(Vector3 vector)
{
return new Dictionary<string, float>
{
["x"] = vector.x,
["y"] = vector.y,
["z"] = vector.z
};
}
private Dictionary<string, float> Vector2ToDict(Vector2 vector)
{
return new Dictionary<string, float>
{
["x"] = vector.x,
["y"] = vector.y
};
}
private Dictionary<string, float> QuaternionToDict(Quaternion quaternion)
{
return new Dictionary<string, float>
{
["x"] = quaternion.x,
["y"] = quaternion.y,
["z"] = quaternion.z,
["w"] = quaternion.w
};
}
private string CaptureGameView(Dictionary<string, string> parameters)
{
#if UNITY_EDITOR
try
{
// Get optional filename parameter
var filename = parameters.GetValueOrDefault("filename", $"GameView_{DateTime.Now:yyyyMMdd_HHmmss}.png");
var outputPath = parameters.GetValueOrDefault("path", "Assets/Screenshots");
// Ensure filename has .png extension
if (!filename.EndsWith(".png", System.StringComparison.OrdinalIgnoreCase))
{
filename += ".png";
}
// Ensure directory exists
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
var fullPath = Path.Combine(outputPath, filename);
// Check if already in play mode
if (EditorApplication.isPlaying)
{
// Already in play mode, capture immediately
return CaptureGameViewImmediate(fullPath);
}
else
{
// Need to enter play mode first
if (IsCapturingScreenshot)
{
SynLog.Warn("[NexusExecutor] Screenshot: Capture already in progress. Resetting state...");
// Force reset if stuck (safety measure)
IsCapturingScreenshot = false;
PendingScreenshotPath = null;
}
PendingScreenshotPath = fullPath;
IsCapturingScreenshot = true;
WasPlayingBeforeCapture = false;
// Register callback for play mode state change (redundant but safe)
EditorApplication.playModeStateChanged -= OnPlayModeStateChangedForScreenshot;
EditorApplication.playModeStateChanged += OnPlayModeStateChangedForScreenshot;
SynLog.Info($"[NexusExecutor] Entering Play mode to capture screenshot: {fullPath}");
// Enter play mode
EditorApplication.EnterPlaymode();
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["message"] = "Entering Play mode to capture screenshot (including Canvas/UI). This will take a moment...",
["status"] = "pending",
["path"] = fullPath
}, Formatting.Indented);
}
}
catch (Exception e)
{
IsCapturingScreenshot = false;
return $"Error capturing Game View: {e.Message}\n{e.StackTrace}";
}
#else
return "Error: This feature is only available in the Unity Editor";
#endif
}
private static int screenshotFrameCounter = 0;
private static EditorApplication.CallbackFunction screenshotUpdateHandler = null;
private static void OnPlayModeStateChangedForScreenshot(PlayModeStateChange state)
{
#if UNITY_EDITOR
if (!IsCapturingScreenshot && !IsCapturingRegion) return;
var captureType = IsCapturingRegion ? "Region" : "Screenshot";
SynLog.Info($"[NexusExecutor] {captureType}: Play mode state changed: {state}");
if (state == PlayModeStateChange.EnteredPlayMode)
{
if (IsCapturingRegion)
{
SynLog.Info($"[NexusExecutor] {captureType}: Entered Play mode, starting 120-frame wait (~2 seconds) for region capture");
}
else
{
SynLog.Info($"[NexusExecutor] {captureType}: Entered Play mode, starting 120-frame wait (~2 seconds) to capture: {PendingScreenshotPath}");
}
// Wait 120 frames (~2 seconds at 60fps) for rendering to stabilize before capturing
screenshotFrameCounter = 0;
screenshotUpdateHandler = ScreenshotFrameUpdate;
EditorApplication.update += screenshotUpdateHandler;
}
else if (state == PlayModeStateChange.EnteredEditMode)
{
SynLog.Info($"[NexusExecutor] {captureType}: Returned to Edit mode after capture");
// Clean up (keep ScreenshotCaptureResult for later retrieval)
IsCapturingScreenshot = false;
IsCapturingRegion = false;
PendingScreenshotPath = null;
PendingRegionParams = null;
// Don't clear ScreenshotCaptureResult here - it will be cleared when retrieved via GetScreenshotResult
EditorApplication.playModeStateChanged -= OnPlayModeStateChangedForScreenshot;
}
#endif
}
private static void CaptureImmediatelyAndExit()
{
#if UNITY_EDITOR
try
{
string result;
if (IsCapturingRegion)
{
// Restore parameters and determine which capture type
var paramsJson = PendingRegionParams;
SynLog.Info($"[NexusExecutor] Region: Capturing with params: {paramsJson}");
var parameters = JsonConvert.DeserializeObject<Dictionary<string, string>>(paramsJson);
var executor = new NexusUnityExecutor();
// Determine which capture method to use based on parameters
if (parameters.ContainsKey("grid"))
{
SynLog.Info($"[NexusExecutor] Grid: Capturing grid: {parameters["grid"]}");
result = executor.CaptureGrid(parameters);
}
else if (parameters.ContainsKey("elementName"))
{
SynLog.Info($"[NexusExecutor] UIElement: Capturing element: {parameters["elementName"]}");
result = executor.CaptureUIElement(parameters);
}
else
{
// Regular region capture
result = executor.CaptureRegion(parameters);
}
SynLog.Info($"[NexusExecutor] Capture result: {result}");
}
else
{
// Capture the screenshot
var path = PendingScreenshotPath;
SynLog.Info($"[NexusExecutor] Screenshot: Capturing to: {path}");
result = CaptureGameViewImmediateStatic(path);
SynLog.Info($"[NexusExecutor] Screenshot: Capture result: {result}");
}
// Store result for later retrieval via unity_get_screenshot_result
ScreenshotCaptureResult = result;
// Exit play mode immediately if we entered it for screenshot
if (!WasPlayingBeforeCapture)
{
SynLog.Info("[NexusExecutor] Screenshot: Exiting Play mode...");
EditorApplication.ExitPlaymode();
}
}
catch (Exception e)
{
var captureType = IsCapturingRegion ? "Region" : "Screenshot";
Debug.LogError($"[NexusExecutor] {captureType}: Error during capture: {e.Message}\n{e.StackTrace}");
if (!WasPlayingBeforeCapture)
{
EditorApplication.ExitPlaymode();
}
}
#endif
}
private static void ScreenshotFrameUpdate()
{
#if UNITY_EDITOR
screenshotFrameCounter++;
if (screenshotFrameCounter >= 120)
{
// Remove the update callback
if (screenshotUpdateHandler != null)
{
EditorApplication.update -= screenshotUpdateHandler;
screenshotUpdateHandler = null;
}
var captureType = IsCapturingRegion ? "Region" : "Screenshot";
SynLog.Info($"[NexusExecutor] {captureType}: 120 frames elapsed (~2 seconds), capturing now...");
// Perform the capture
CaptureImmediatelyAndExit();
}
#endif
}
private string CaptureGameViewImmediate(string fullPath)
{
return CaptureGameViewImmediateStatic(fullPath);
}
private static string CaptureGameViewImmediateStatic(string fullPath)
{
#if UNITY_EDITOR
try
{
// Use ScreenCapture.CaptureScreenshotAsTexture in Play mode (includes Canvas Overlay)
var screenshotTexture = ScreenCapture.CaptureScreenshotAsTexture(1);
if (screenshotTexture != null)
{
int width = screenshotTexture.width;
int height = screenshotTexture.height;
// Save to file
var bytes = screenshotTexture.EncodeToPNG();
File.WriteAllBytes(fullPath, bytes);
// Cleanup
UnityEngine.Object.DestroyImmediate(screenshotTexture);
AssetDatabase.Refresh();
SynLog.Info($"[NexusExecutor] Game View captured successfully (including Canvas/UI): {fullPath} ({width}x{height})");
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["path"] = fullPath,
["width"] = width,
["height"] = height,
["message"] = $"Screenshot captured and saved to {fullPath} ({width}x{height})"
}, Formatting.Indented);
}
else
{
return "Error: ScreenCapture.CaptureScreenshotAsTexture() returned null";
}
}
catch (Exception e)
{
return $"Error: {e.Message}";
}
#else
return "Error: This feature is only available in the Unity Editor";
#endif
}
private string CaptureSceneView(Dictionary<string, string> parameters)
{
#if UNITY_EDITOR
try
{
// Get Scene View window
var sceneView = SceneView.lastActiveSceneView;
if (sceneView == null)
{
return "Error: No active Scene View found";
}
// Get optional filename parameter
var filename = parameters.GetValueOrDefault("filename", $"SceneView_{DateTime.Now:yyyyMMdd_HHmmss}.png");
var outputPath = parameters.GetValueOrDefault("path", "Assets/Screenshots");
// Ensure filename has .png extension
if (!filename.EndsWith(".png", System.StringComparison.OrdinalIgnoreCase))
{
filename += ".png";
}
// Ensure directory exists
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
var fullPath = Path.Combine(outputPath, filename);
// Focus Scene View to ensure it's rendered
sceneView.Focus();
sceneView.Repaint();
// Capture immediately (synchronous execution)
var camera = sceneView.camera;
var position = sceneView.position;
// Capture using RenderTexture
var renderTexture = new RenderTexture((int)position.width, (int)position.height, 24);
var prevActive = RenderTexture.active;
var prevTarget = camera.targetTexture;
camera.targetTexture = renderTexture;
camera.Render();
camera.targetTexture = prevTarget;
RenderTexture.active = renderTexture;
var texture2D = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.RGB24, false);
texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
texture2D.Apply();
RenderTexture.active = prevActive;
// Save to file
var bytes = texture2D.EncodeToPNG();
File.WriteAllBytes(fullPath, bytes);
// Cleanup
UnityEngine.Object.DestroyImmediate(texture2D);
renderTexture.Release();
AssetDatabase.Refresh();
SynLog.Info($"[NexusExecutor] Scene View captured successfully: {fullPath}");
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["path"] = fullPath,
["message"] = $"Screenshot captured and saved to {fullPath}"
}, Formatting.Indented);
}
catch (Exception e)
{
return $"Error capturing Scene View: {e.Message}";
}
#else
return "Error: This feature is only available in the Unity Editor";
#endif
}
private string CaptureRegion(Dictionary<string, string> parameters)
{
#if UNITY_EDITOR
try
{
// Get region parameters
if (!parameters.ContainsKey("x") || !parameters.ContainsKey("y") ||
!parameters.ContainsKey("width") || !parameters.ContainsKey("height"))
{
return "Error: Missing required parameters. Need: x, y, width, height";
}
if (!int.TryParse(parameters["x"], out var x))
{
return "Error: Invalid x coordinate - must be an integer";
}
if (!int.TryParse(parameters["y"], out var y))
{
return "Error: Invalid y coordinate - must be an integer";
}
if (!int.TryParse(parameters["width"], out var width))
{
return "Error: Invalid width - must be an integer";
}
if (!int.TryParse(parameters["height"], out var height))
{
return "Error: Invalid height - must be an integer";
}
// Get optional parameters
var filename = parameters.GetValueOrDefault("filename", $"Region_{DateTime.Now:yyyyMMdd_HHmmss}.png");
var outputPath = parameters.GetValueOrDefault("path", "Assets/Screenshots");
var viewType = parameters.GetValueOrDefault("view", "game").ToLower(); // "scene" or "game" (default game)
// Ensure filename has .png extension
if (!filename.EndsWith(".png", System.StringComparison.OrdinalIgnoreCase))
{
filename += ".png";
}
// Ensure directory exists
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
var fullPath = Path.Combine(outputPath, filename);
EditorWindow targetView = null;
Camera targetCamera = null;
if (viewType == "game")
{
// Get Game View window
var gameViewType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GameView");
targetView = EditorWindow.GetWindow(gameViewType, false, null, false);
}
else
{
// Get Scene View window
var sceneView = SceneView.lastActiveSceneView;
if (sceneView == null)
{
return "Error: No active Scene View found";
}
targetView = sceneView;
targetCamera = sceneView.camera;
}
if (targetView == null)
{
return $"Error: Could not access {viewType} view";
}
// Focus view to ensure it's rendered
targetView.Focus();
targetView.Repaint();
// Wait for repaint to complete
EditorApplication.QueuePlayerLoopUpdate();
// Capture immediately (synchronous execution)
var viewPosition = targetView.position;
// Validate region bounds
if (x < 0 || y < 0 || x + width > viewPosition.width || y + height > viewPosition.height)
{
SynLog.Warn($"[NexusExecutor] Region bounds adjusted to fit view: {viewPosition.width}x{viewPosition.height}");
x = Mathf.Max(0, x);
y = Mathf.Max(0, y);
width = Mathf.Min(width, (int)viewPosition.width - x);
height = Mathf.Min(height, (int)viewPosition.height - y);
}
Texture2D texture2D;
if (viewType == "game")
{
// For Game View, must be in Play mode to capture Canvas Overlay
if (!EditorApplication.isPlaying)
{
// Auto-enter Play mode for Game View capture
if (IsCapturingRegion)
{
return "Error: Region screenshot capture already in progress. Please wait.";
}
// Store parameters for after Play mode transition
PendingRegionParams = JsonConvert.SerializeObject(parameters);
IsCapturingRegion = true;
WasPlayingBeforeCapture = false;
// Register callback for play mode state change
EditorApplication.playModeStateChanged -= OnPlayModeStateChangedForScreenshot;
EditorApplication.playModeStateChanged += OnPlayModeStateChangedForScreenshot;
SynLog.Info($"[NexusExecutor] Entering Play mode to capture region: {fullPath}");
// Enter play mode
EditorApplication.EnterPlaymode();
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["message"] = "Entering Play mode to capture region (including Canvas/UI). This will take a moment...",
["status"] = "pending",
["path"] = fullPath
}, Formatting.Indented);
}
// Capture full Game View with Canvas using ScreenCapture
var fullScreenshot = ScreenCapture.CaptureScreenshotAsTexture(1);
if (fullScreenshot == null)
{
return "Error: Failed to capture screenshot. Make sure Game View is visible.";
}
// Validate region bounds against captured screenshot
if (x < 0 || y < 0 || x + width > fullScreenshot.width || y + height > fullScreenshot.height)
{
SynLog.Warn($"[NexusExecutor] Region bounds adjusted to fit screenshot: {fullScreenshot.width}x{fullScreenshot.height}");
x = Mathf.Max(0, x);
y = Mathf.Max(0, y);
width = Mathf.Min(width, fullScreenshot.width - x);
height = Mathf.Min(height, fullScreenshot.height - y);
}
// Extract the specified region
texture2D = new Texture2D(width, height, TextureFormat.RGB24, false);
var pixels = fullScreenshot.GetPixels(x, fullScreenshot.height - y - height, width, height);
texture2D.SetPixels(pixels);
texture2D.Apply();
// Cleanup
UnityEngine.Object.DestroyImmediate(fullScreenshot);
}
else
{
// For Scene View, use RenderTexture (Canvas doesn't appear in Scene View anyway)
if (targetCamera == null)
{
return "Error: Could not access Scene View camera";
}
var renderTexture = new RenderTexture((int)viewPosition.width, (int)viewPosition.height, 24);
var prevActive = RenderTexture.active;
var prevTarget = targetCamera.targetTexture;
targetCamera.targetTexture = renderTexture;
targetCamera.Render();
targetCamera.targetTexture = prevTarget;
RenderTexture.active = renderTexture;
// Read the specific region
texture2D = new Texture2D(width, height, TextureFormat.RGB24, false);
texture2D.ReadPixels(new Rect(x, (int)viewPosition.height - y - height, width, height), 0, 0);
texture2D.Apply();
RenderTexture.active = prevActive;
// Cleanup
renderTexture.Release();
}
// Save to file
var bytes = texture2D.EncodeToPNG();
File.WriteAllBytes(fullPath, bytes);
// Get captured texture dimensions
int capturedWidth = texture2D.width;
int capturedHeight = texture2D.height;
// Cleanup
UnityEngine.Object.DestroyImmediate(texture2D);
AssetDatabase.Refresh();
SynLog.Info($"[NexusExecutor] Region captured successfully: {fullPath} (x:{x}, y:{y}, {capturedWidth}x{capturedHeight})");
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["path"] = fullPath,
["width"] = capturedWidth,
["height"] = capturedHeight,
["region"] = new Dictionary<string, int>
{
["x"] = x,
["y"] = y,
["width"] = capturedWidth,
["height"] = capturedHeight
},
["message"] = $"Screenshot captured and saved to {fullPath} ({capturedWidth}x{capturedHeight})"
}, Formatting.Indented);
}
catch (Exception e)
{
return $"Error capturing region: {e.Message}";
}
#else
return "Error: This feature is only available in the Unity Editor";
#endif
}
private string GetScreenshotResult()
{
#if UNITY_EDITOR
try
{
// Check if there's a pending screenshot result
var result = ScreenshotCaptureResult;
if (string.IsNullOrEmpty(result))
{
// Check if screenshot is still being captured
if (IsCapturingScreenshot)
{
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = false,
["status"] = "capturing",
["message"] = "Screenshot capture is still in progress. Please wait a moment and try again."
}, Formatting.Indented);
}
else
{
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = false,
["status"] = "no_result",
["message"] = "No screenshot result available. Make sure you called CaptureGameView first."
}, Formatting.Indented);
}
}
// Clear the result after retrieving it
ScreenshotCaptureResult = null;
SynLog.Info("[NexusExecutor] Screenshot result retrieved and cleared");
return result;
}
catch (Exception e)
{
return $"Error retrieving screenshot result: {e.Message}";
}
#else
return "Error: This feature is only available in the Unity Editor";
#endif
}
private string CaptureGrid(Dictionary<string, string> parameters)
{
#if UNITY_EDITOR
try
{
// Get grid parameter (e.g., "2x2", "3x3", "4x4")
if (!parameters.ContainsKey("grid"))
{
return "Error: Missing required parameter 'grid'. Format: '2x2', '3x3', etc.";
}
var grid = parameters["grid"];
var parts = grid.Split('x');
if (parts.Length != 2 || !int.TryParse(parts[0], out var cols) || !int.TryParse(parts[1], out var rows))
{
return "Error: Invalid grid format. Use format like '2x2', '3x3', '4x4'";
}
if (cols < 1 || cols > 5 || rows < 1 || rows > 5)
{
return "Error: Grid size must be between 1x1 and 5x5";
}
var outputPath = parameters.GetValueOrDefault("path", "Assets/Screenshots");
var baseName = parameters.GetValueOrDefault("basename", $"Grid_{DateTime.Now:yyyyMMdd_HHmmss}");
// Ensure directory exists
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
// Get Game View size
var gameViewType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GameView");
var gameView = EditorWindow.GetWindow(gameViewType, false, null, false);
if (gameView == null)
{
return "Error: Could not access Game View";
}
// Check if Play mode is needed
if (!EditorApplication.isPlaying)
{
// Store grid parameters for after Play mode transition
PendingRegionParams = JsonConvert.SerializeObject(parameters);
IsCapturingRegion = true;
WasPlayingBeforeCapture = false;
EditorApplication.playModeStateChanged -= OnPlayModeStateChangedForScreenshot;
EditorApplication.playModeStateChanged += OnPlayModeStateChangedForScreenshot;
SynLog.Info($"[NexusExecutor] Entering Play mode to capture grid: {grid}");
EditorApplication.EnterPlaymode();
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["message"] = $"Entering Play mode to capture {grid} grid. This will take a moment...",
["status"] = "pending",
["grid"] = grid
}, Formatting.Indented);
}
// Capture in Play mode
var screenshotTexture = ScreenCapture.CaptureScreenshotAsTexture(1);
if (screenshotTexture == null)
{
return "Error: Failed to capture screenshot. Make sure Game View is visible.";
}
int fullWidth = screenshotTexture.width;
int fullHeight = screenshotTexture.height;
int cellWidth = fullWidth / cols;
int cellHeight = fullHeight / rows;
var capturedFiles = new List<Dictionary<string, object>>();
// Capture each grid cell
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
var cellX = col * cellWidth;
var cellY = row * cellHeight;
// Extract region (note: Unity texture coordinates are bottom-left origin)
var cellTexture = new Texture2D(cellWidth, cellHeight, TextureFormat.RGB24, false);
var pixels = screenshotTexture.GetPixels(cellX, fullHeight - cellY - cellHeight, cellWidth, cellHeight);
cellTexture.SetPixels(pixels);
cellTexture.Apply();
// Save to file
var filename = $"{baseName}_r{row}_c{col}.png";
var fullPath = Path.Combine(outputPath, filename);
var bytes = cellTexture.EncodeToPNG();
File.WriteAllBytes(fullPath, bytes);
UnityEngine.Object.DestroyImmediate(cellTexture);
capturedFiles.Add(new Dictionary<string, object>
{
["path"] = fullPath,
["row"] = row,
["col"] = col,
["x"] = cellX,
["y"] = cellY,
["width"] = cellWidth,
["height"] = cellHeight
});
}
}
UnityEngine.Object.DestroyImmediate(screenshotTexture);
AssetDatabase.Refresh();
SynLog.Info($"[NexusExecutor] Grid {grid} captured successfully: {capturedFiles.Count} files");
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["grid"] = grid,
["total_files"] = capturedFiles.Count,
["full_width"] = fullWidth,
["full_height"] = fullHeight,
["files"] = capturedFiles,
["message"] = $"Captured {grid} grid ({capturedFiles.Count} files)"
}, Formatting.Indented);
}
catch (Exception e)
{
return $"Error capturing grid: {e.Message}\n{e.StackTrace}";
}
#else
return "Error: This feature is only available in the Unity Editor";
#endif
}
private string CaptureUIElement(Dictionary<string, string> parameters)
{
#if UNITY_EDITOR
try
{
// Get element name parameter
if (!parameters.ContainsKey("elementName"))
{
return "Error: Missing required parameter 'elementName'";
}
var elementName = parameters["elementName"];
var outputPath = parameters.GetValueOrDefault("path", "Assets/Screenshots");
var filename = parameters.GetValueOrDefault("filename", $"UIElement_{elementName}_{DateTime.Now:yyyyMMdd_HHmmss}.png");
// Ensure filename has .png extension
if (!filename.EndsWith(".png", System.StringComparison.OrdinalIgnoreCase))
{
filename += ".png";
}
// Ensure directory exists
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
var fullPath = Path.Combine(outputPath, filename);
// Check if Play mode is needed
if (!EditorApplication.isPlaying)
{
// Store parameters for after Play mode transition
PendingRegionParams = JsonConvert.SerializeObject(parameters);
IsCapturingRegion = true;
WasPlayingBeforeCapture = false;
EditorApplication.playModeStateChanged -= OnPlayModeStateChangedForScreenshot;
EditorApplication.playModeStateChanged += OnPlayModeStateChangedForScreenshot;
SynLog.Info($"[NexusExecutor] Entering Play mode to capture UI element: {elementName}");
EditorApplication.EnterPlaymode();
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["message"] = $"Entering Play mode to capture UI element '{elementName}'. This will take a moment...",
["status"] = "pending",
["elementName"] = elementName
}, Formatting.Indented);
}
// Find the GameObject by name
var targetObject = GameObject.Find(elementName);
if (targetObject == null)
{
// Try finding in all objects (including inactive, exclude prefabs)
var allObjects = Resources.FindObjectsOfTypeAll<GameObject>()
.Where(go => !UnityEditor.EditorUtility.IsPersistent(go) && go.scene.IsValid() && go.scene.isLoaded);
targetObject = allObjects.FirstOrDefault(obj => obj.name == elementName);
}
if (targetObject == null)
{
return $"Error: Could not find UI element named '{elementName}' in the scene";
}
// Get RectTransform
var rectTransform = targetObject.GetComponent<RectTransform>();
if (rectTransform == null)
{
return $"Error: GameObject '{elementName}' does not have a RectTransform component (not a UI element)";
}
// Get canvas
var canvas = rectTransform.GetComponentInParent<Canvas>();
if (canvas == null)
{
return $"Error: UI element '{elementName}' is not under a Canvas";
}
// Calculate screen coordinates based on canvas render mode
int x, y, width, height;
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
// For Screen Space Overlay, use direct rect calculations
// Get corners in canvas space
var corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
// For Overlay, world corners are already in screen pixels
var minX = corners.Min(c => c.x);
var maxX = corners.Max(c => c.x);
var minY = corners.Min(c => c.y);
var maxY = corners.Max(c => c.y);
x = Mathf.RoundToInt(minX);
y = Mathf.RoundToInt(minY);
width = Mathf.RoundToInt(maxX - minX);
height = Mathf.RoundToInt(maxY - minY);
SynLog.Info($"[NexusExecutor] Overlay mode: corners ({minX},{minY}) to ({maxX},{maxY})");
}
else
{
// For Screen Space Camera or World Space, use camera projection
Camera targetCamera = null;
if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
{
targetCamera = canvas.worldCamera ?? Camera.main;
}
else // WorldSpace
{
targetCamera = Camera.main ?? Camera.allCameras.FirstOrDefault();
}
if (targetCamera == null)
{
return "Error: Could not find a camera to calculate screen coordinates";
}
// Get world corners and convert to screen space
var corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
var screenCorners = corners.Select(c => RectTransformUtility.WorldToScreenPoint(targetCamera, c)).ToArray();
var minX = screenCorners.Min(p => p.x);
var maxX = screenCorners.Max(p => p.x);
var minY = screenCorners.Min(p => p.y);
var maxY = screenCorners.Max(p => p.y);
x = Mathf.RoundToInt(minX);
y = Mathf.RoundToInt(minY);
width = Mathf.RoundToInt(maxX - minX);
height = Mathf.RoundToInt(maxY - minY);
SynLog.Info($"[NexusExecutor] Camera/WorldSpace mode: corners ({minX},{minY}) to ({maxX},{maxY})");
}
// Debug log for troubleshooting
SynLog.Info($"[NexusExecutor] UI element '{elementName}' screen bounds: x={x}, y={y}, width={width}, height={height}");
// Validate dimensions before creating texture
if (width <= 0 || height <= 0)
{
return $"Error: UI element '{elementName}' has invalid dimensions (width={width}, height={height}). " +
$"The element may be invisible, inactive, or have zero size.";
}
// Capture full screenshot
SynLog.Info($"[NexusExecutor] About to capture full screenshot. EditorApplication.isPlaying={EditorApplication.isPlaying}");
var fullScreenshot = ScreenCapture.CaptureScreenshotAsTexture(1);
if (fullScreenshot == null)
{
return "Error: Failed to capture screenshot";
}
SynLog.Info($"[NexusExecutor] Screenshot captured: {fullScreenshot.width}x{fullScreenshot.height}");
// Check if screenshot size is abnormally small (3024x40 bug)
if (fullScreenshot.height < 100)
{
UnityEngine.Object.DestroyImmediate(fullScreenshot);
return $"Error: Screenshot has abnormal size ({fullScreenshot.width}x{fullScreenshot.height}). " +
$"This usually means Game View is not properly visible or rendering hasn't stabilized. " +
$"Try: 1) Make sure Game View is visible and has normal size, 2) Try again in a few seconds.";
}
// Validate bounds
if (x < 0 || y < 0 || x + width > fullScreenshot.width || y + height > fullScreenshot.height)
{
SynLog.Warn($"[NexusExecutor] Element bounds need adjustment: original ({x},{y},{width},{height}), screenshot={fullScreenshot.width}x{fullScreenshot.height}");
x = Mathf.Max(0, x);
y = Mathf.Max(0, y);
width = Mathf.Min(width, fullScreenshot.width - x);
height = Mathf.Min(height, fullScreenshot.height - y);
// Check if adjusted dimensions are still valid
if (width <= 0 || height <= 0)
{
UnityEngine.Object.DestroyImmediate(fullScreenshot);
return $"Error: UI element '{elementName}' is completely out of screenshot bounds. " +
$"Screenshot size: {fullScreenshot.width}x{fullScreenshot.height}, " +
$"Element bounds: x={x}, y={y}, width={width}, height={height}. " +
$"The element may be outside the Game View.";
}
}
SynLog.Info($"[NexusExecutor] Extracting pixels: x={x}, y={y}, width={width}, height={height}, fullScreenshot={fullScreenshot.width}x{fullScreenshot.height}");
// Extract region (both GetWorldCorners and GetPixels use bottom-left origin, no conversion needed)
var texture2D = new Texture2D(width, height, TextureFormat.RGB24, false);
var pixels = fullScreenshot.GetPixels(x, y, width, height);
texture2D.SetPixels(pixels);
texture2D.Apply();
// Save to file
var bytes = texture2D.EncodeToPNG();
File.WriteAllBytes(fullPath, bytes);
UnityEngine.Object.DestroyImmediate(texture2D);
UnityEngine.Object.DestroyImmediate(fullScreenshot);
AssetDatabase.Refresh();
SynLog.Info($"[NexusExecutor] UI element '{elementName}' captured: {fullPath} ({width}x{height})");
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["path"] = fullPath,
["elementName"] = elementName,
["width"] = width,
["height"] = height,
["x"] = x,
["y"] = y,
["message"] = $"UI element '{elementName}' captured: {fullPath} ({width}x{height})"
}, Formatting.Indented);
}
catch (Exception e)
{
return $"Error capturing UI element: {e.Message}\n{e.StackTrace}";
}
#else
return "Error: This feature is only available in the Unity Editor";
#endif
}
private Task<string> BatchCreate(NexusUnityOperation operation)
{
// TODO: Implement batch operations
return Task.FromResult("Batch operations not yet implemented");
}
private string ForceRefreshAssets(Dictionary<string, string> parameters)
{
#if UNITY_EDITOR
try
{
// Get optional parameters
var importMode = parameters.GetValueOrDefault("importMode", "default");
SynLog.Info("[NexusExecutor] Force refreshing assets...");
// Force Unity to reimport and recompile
switch (importMode.ToLower())
{
case "force":
// Force reimport of all assets
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
break;
case "forcesynchronous":
// Force reimport synchronously (blocks until complete)
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
break;
default:
// Default refresh
AssetDatabase.Refresh();
break;
}
SynLog.Info("[NexusExecutor] Asset refresh completed successfully");
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["message"] = $"Asset database refreshed with mode: {importMode}",
["import_mode"] = importMode
}, Formatting.Indented);
}
catch (Exception e)
{
return $"Error refreshing assets: {e.Message}";
}
#else
return "Error: This feature is only available in the Unity Editor";
#endif
}
private string InvokeContextMenu(Dictionary<string, string> parameters)
{
#if UNITY_EDITOR
try
{
// Get required parameters
if (!parameters.ContainsKey("componentName") || !parameters.ContainsKey("methodName"))
{
return "Error: componentName and methodName parameters are required";
}
var componentName = parameters["componentName"];
var methodName = parameters["methodName"];
var gameObjectName = parameters.GetValueOrDefault("gameObjectName", "");
SynLog.Info($"[NexusExecutor] Invoking ContextMenu: {componentName}.{methodName}");
// Find the component type
var componentType = FindComponentType(componentName);
if (componentType == null)
{
return $"Error: Component type '{componentName}' not found. Make sure the script is compiled.";
}
// Find the method with ContextMenu attribute
var method = componentType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (method == null)
{
return $"Error: Method '{methodName}' not found in component '{componentName}'";
}
// Verify method has ContextMenu attribute
var contextMenuAttr = method.GetCustomAttribute<ContextMenu>();
if (contextMenuAttr == null)
{
SynLog.Warn($"[NexusExecutor] Method '{methodName}' does not have [ContextMenu] attribute, but will execute anyway");
}
// Find the component instance
Component componentInstance = null;
if (!string.IsNullOrEmpty(gameObjectName))
{
// Search by GameObject name
var gameObject = GameObject.Find(gameObjectName);
if (gameObject == null)
{
return $"Error: GameObject '{gameObjectName}' not found";
}
componentInstance = gameObject.GetComponent(componentType);
}
else
{
// Find any instance of this component type
componentInstance = UnityEngine.Object.FindObjectOfType(componentType) as Component;
}
if (componentInstance == null)
{
return $"Error: No instance of component '{componentName}' found in scene";
}
// Invoke the method
try
{
// Handle optional parameters by providing default values
var methodParams = method.GetParameters();
var args = new object[methodParams.Length];
for (int i = 0; i < methodParams.Length; i++)
{
args[i] = methodParams[i].HasDefaultValue ? methodParams[i].DefaultValue : null;
}
method.Invoke(componentInstance, args);
}
catch (System.Reflection.TargetInvocationException tie)
{
// TargetInvocationException wraps the actual exception from the invoked method
var actualException = tie.InnerException ?? tie;
Debug.LogError($"[NexusExecutor] Method execution failed: {actualException.Message}");
return $"Error: Method '{methodName}' threw an exception: {actualException.Message}\n\nStack trace: {actualException.StackTrace}";
}
SynLog.Info($"[NexusExecutor] Successfully invoked {componentName}.{methodName}");
return JsonConvert.SerializeObject(new Dictionary<string, object>
{
["success"] = true,
["message"] = $"Successfully invoked {componentName}.{methodName}",
["component"] = componentName,
["method"] = methodName,
["gameObject"] = componentInstance.gameObject.name
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] Failed to invoke context menu: {e.Message}");
return $"Error invoking context menu: {e.Message}\n\nStack trace: {e.StackTrace}";
}
#else
return "Error: This feature is only available in the Unity Editor";
#endif
}
/// <summary>
/// Execute Unity Editor menu item by path (e.g., "Edit/Preferences", "Window/Package Manager")
/// </summary>
private string ExecuteMenuItem(Dictionary<string, string> parameters)
{
#if UNITY_EDITOR
try
{
var menuPath = parameters.GetValueOrDefault("menuPath", "") ??
parameters.GetValueOrDefault("path", "") ??
parameters.GetValueOrDefault("menu", "");
if (string.IsNullOrEmpty(menuPath))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "menuPath parameter is required",
example = "Edit/Preferences, Window/Package Manager, Assets/Refresh"
});
}
SynLog.Info($"[NexusExecutor] Executing menu item: {menuPath}");
// Execute the menu item
bool result = EditorApplication.ExecuteMenuItem(menuPath);
if (result)
{
SynLog.Info($"[NexusExecutor] Successfully executed menu item: {menuPath}");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Successfully executed menu item: {menuPath}",
menuPath = menuPath
});
}
else
{
// Menu item not found or couldn't be executed
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Menu item '{menuPath}' not found or could not be executed",
hint = "Check the exact menu path. Use '/' as separator. Example: 'Edit/Preferences', 'Window/Package Manager'"
});
}
}
catch (Exception e)
{
Debug.LogError($"[NexusExecutor] Failed to execute menu item: {e.Message}");
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
#else
return JsonConvert.SerializeObject(new { success = false, error = "This feature is only available in the Unity Editor" });
#endif
}
private Type FindComponentType(string componentName)
{
// Search in all loaded assemblies
var assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
var types = assembly.GetTypes();
foreach (var type in types)
{
// Check if Component subclass
if (!typeof(Component).IsAssignableFrom(type))
continue;
// Match by simple name or full name (with namespace)
if (type.Name == componentName || type.FullName == componentName)
{
return type;
}
}
}
catch
{
// Skip assemblies that can't be loaded
continue;
}
}
return null;
}
private string GetAssetImportSettings(Dictionary<string, string> parameters)
{
try
{
var assetPath = parameters.GetValueOrDefault("assetPath", "");
var includeAllSettings = parameters.GetValueOrDefault("includeAllSettings", "true") == "true";
var includeOverrides = parameters.GetValueOrDefault("includeOverrides", "true") == "true";
if (string.IsNullOrEmpty(assetPath))
{
return "Error: assetPath parameter is required";
}
var importer = AssetImporter.GetAtPath(assetPath);
if (importer == null)
{
return $"Error: No importer found for asset at path: {assetPath}";
}
var report = new System.Text.StringBuilder();
report.AppendLine($"=== Import Settings for {assetPath} ===");
report.AppendLine($"Importer Type: {importer.GetType().Name}");
report.AppendLine($"Asset Bundle: {importer.assetBundleName}");
report.AppendLine($"Asset Bundle Variant: {importer.assetBundleVariant}");
report.AppendLine();
// Type-specific settings
switch (importer)
{
case TextureImporter texImporter:
report.AppendLine("=== Texture Import Settings ===");
report.AppendLine($"Texture Type: {texImporter.textureType}");
report.AppendLine($"Max Texture Size: {texImporter.maxTextureSize}");
report.AppendLine($"Compression: {texImporter.textureCompression}");
report.AppendLine($"sRGB: {texImporter.sRGBTexture}");
report.AppendLine($"Generate Mipmaps: {texImporter.mipmapEnabled}");
report.AppendLine($"Wrap Mode: {texImporter.wrapMode}");
report.AppendLine($"Filter Mode: {texImporter.filterMode}");
break;
case AudioImporter audioImporter:
report.AppendLine("=== Audio Import Settings ===");
report.AppendLine($"Force To Mono: {audioImporter.forceToMono}");
var audioDefaultSettings = audioImporter.defaultSampleSettings;
report.AppendLine($"Preload Audio Data: {audioDefaultSettings.loadType == AudioClipLoadType.CompressedInMemory}");
report.AppendLine($"Load In Background: {audioImporter.loadInBackground}");
var defaultSettings = audioImporter.defaultSampleSettings;
report.AppendLine($"Load Type: {defaultSettings.loadType}");
report.AppendLine($"Compression Format: {defaultSettings.compressionFormat}");
report.AppendLine($"Quality: {defaultSettings.quality}");
break;
default:
report.AppendLine("=== Generic Import Settings ===");
report.AppendLine($"User Data: {importer.userData}");
break;
}
return report.ToString();
}
catch (Exception e)
{
return $"Error getting import settings: {e.Message}";
}
}
// Helper methods
private Type GetComponentType(string typeName)
{
if (string.IsNullOrEmpty(typeName)) return null;
// Common aliases mapping
var aliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "VisualEffect", "UnityEngine.VFX.VisualEffect" },
{ "VFX", "UnityEngine.VFX.VisualEffect" },
{ "ParticleSystem", "UnityEngine.ParticleSystem" },
{ "Rigidbody", "UnityEngine.Rigidbody" },
{ "Rigidbody2D", "UnityEngine.Rigidbody2D" },
{ "BoxCollider", "UnityEngine.BoxCollider" },
{ "SphereCollider", "UnityEngine.SphereCollider" },
{ "CapsuleCollider", "UnityEngine.CapsuleCollider" },
{ "MeshCollider", "UnityEngine.MeshCollider" },
{ "CharacterController", "UnityEngine.CharacterController" },
{ "AudioSource", "UnityEngine.AudioSource" },
{ "AudioListener", "UnityEngine.AudioListener" },
{ "Light", "UnityEngine.Light" },
{ "Camera", "UnityEngine.Camera" },
{ "Animator", "UnityEngine.Animator" },
{ "Animation", "UnityEngine.Animation" },
{ "NavMeshAgent", "UnityEngine.AI.NavMeshAgent" },
{ "NavMeshObstacle", "UnityEngine.AI.NavMeshObstacle" },
{ "Canvas", "UnityEngine.Canvas" },
{ "CanvasScaler", "UnityEngine.UI.CanvasScaler" },
{ "GraphicRaycaster", "UnityEngine.UI.GraphicRaycaster" },
{ "Image", "UnityEngine.UI.Image" },
{ "Text", "UnityEngine.UI.Text" },
{ "Button", "UnityEngine.UI.Button" },
{ "InputField", "UnityEngine.UI.InputField" },
{ "Slider", "UnityEngine.UI.Slider" },
{ "Toggle", "UnityEngine.UI.Toggle" },
{ "Dropdown", "UnityEngine.UI.Dropdown" },
{ "ScrollRect", "UnityEngine.UI.ScrollRect" },
{ "RectTransform", "UnityEngine.RectTransform" },
{ "EventSystem", "UnityEngine.EventSystems.EventSystem" },
{ "StandaloneInputModule", "UnityEngine.EventSystems.StandaloneInputModule" },
{ "LineRenderer", "UnityEngine.LineRenderer" },
{ "TrailRenderer", "UnityEngine.TrailRenderer" },
{ "MeshRenderer", "UnityEngine.MeshRenderer" },
{ "MeshFilter", "UnityEngine.MeshFilter" },
{ "SkinnedMeshRenderer", "UnityEngine.SkinnedMeshRenderer" },
{ "SpriteRenderer", "UnityEngine.SpriteRenderer" },
{ "Volume", "UnityEngine.Rendering.Volume" },
};
// Check alias first
if (aliases.TryGetValue(typeName, out string fullName))
{
typeName = fullName;
}
// Namespaces to search
var namespaces = new[]
{
"", // Already fully qualified
"UnityEngine.",
"UnityEngine.UI.",
"UnityEngine.VFX.",
"UnityEngine.AI.",
"UnityEngine.EventSystems.",
"UnityEngine.Rendering.",
"UnityEngine.Rendering.Universal.",
"Unity.Cinemachine.",
"Cinemachine.",
};
// Search all assemblies
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
// Try exact match first
var type = assembly.GetType(typeName);
if (type != null && typeof(Component).IsAssignableFrom(type))
return type;
// Try with namespaces
foreach (var ns in namespaces)
{
if (string.IsNullOrEmpty(ns) && typeName.Contains("."))
{
// Already has namespace
type = assembly.GetType(typeName);
}
else if (!string.IsNullOrEmpty(ns))
{
type = assembly.GetType($"{ns}{typeName}");
}
if (type != null && typeof(Component).IsAssignableFrom(type))
return type;
}
}
// Last resort: search by simple name
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
var types = assembly.GetTypes()
.Where(t => t.Name.Equals(typeName, StringComparison.OrdinalIgnoreCase) &&
typeof(Component).IsAssignableFrom(t))
.ToArray();
if (types.Length == 1)
return types[0];
}
catch { }
}
return null;
}
private void SetComponentProperty(Component component, string propertyName, string value)
{
// Null checks
if (component == null)
{
Debug.LogError("[SetComponentProperty] Component is null");
return;
}
if (string.IsNullOrEmpty(propertyName))
{
Debug.LogError("[SetComponentProperty] PropertyName is null or empty");
return;
}
var type = component.GetType();
SynLog.Info($"[SetComponentProperty] Setting '{propertyName}' = '{value}' on {type.Name}");
try
{
// Special component handling
if (HandleSpecialComponentProperty(component, propertyName, value))
{
SynLog.Info($"[SetComponentProperty] Successfully handled special case for {propertyName}");
return;
}
// Common property name aliases
var actualPropertyName = GetActualPropertyName(type, propertyName);
SynLog.Info($"[SetComponentProperty] Mapped '{propertyName}' to '{actualPropertyName}'");
// Try property
var property = type.GetProperty(actualPropertyName, BindingFlags.Public | BindingFlags.Instance);
if (property != null && property.CanWrite)
{
var convertedValue = ConvertValue(value, property.PropertyType);
SynLog.Info($"[SetComponentProperty] Setting property {actualPropertyName} (type: {property.PropertyType.Name})");
property.SetValue(component, convertedValue);
SynLog.Info($"[SetComponentProperty] Successfully set property {actualPropertyName}");
return;
}
// Try field
var field = type.GetField(actualPropertyName, BindingFlags.Public | BindingFlags.Instance);
if (field != null)
{
var convertedValue = ConvertValue(value, field.FieldType);
SynLog.Info($"[SetComponentProperty] Setting field {actualPropertyName} (type: {field.FieldType.Name})");
field.SetValue(component, convertedValue);
SynLog.Info($"[SetComponentProperty] Successfully set field {actualPropertyName}");
return;
}
// Log available properties and fields
var availableProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanWrite)
.Select(p => $"{p.Name} ({p.PropertyType.Name})")
.ToArray();
var availableFields = type.GetFields(BindingFlags.Public | BindingFlags.Instance)
.Select(f => $"{f.Name} ({f.FieldType.Name})")
.ToArray();
Debug.LogError($"[SetComponentProperty] Available properties: {string.Join(", ", availableProperties)}");
Debug.LogError($"[SetComponentProperty] Available fields: {string.Join(", ", availableFields)}");
throw new ArgumentException($"Property or field '{actualPropertyName}' not found on component '{type.Name}'. Available: {string.Join(", ", availableProperties.Concat(availableFields))}");
}
catch (Exception ex)
{
Debug.LogError($"Failed to set property '{propertyName}' on '{type.Name}': {ex.Message}");
throw;
}
}
/// <summary>
/// Special component handling (complex cases like Transform, Renderer, etc.)
/// </summary>
private bool HandleSpecialComponentProperty(Component component, string propertyName, string value)
{
// Null checks
if (component == null)
{
SynLog.Warn("[HandleSpecialComponentProperty] Component is null");
return false;
}
if (string.IsNullOrEmpty(propertyName))
{
SynLog.Warn("[HandleSpecialComponentProperty] PropertyName is null or empty");
return false;
}
var type = component.GetType();
var lowerPropertyName = propertyName.ToLower();
SynLog.Info($"[HandleSpecialComponentProperty] Checking special cases for {type.Name}.{propertyName}");
// Record Undo before special handling
UnityEditor.Undo.RecordObject(component, $"Set {type.Name}.{propertyName}");
// Array element access (e.g., tetrominoPrefabs[0])
if (propertyName.Contains("[") && propertyName.Contains("]"))
{
var arrayMatch = System.Text.RegularExpressions.Regex.Match(propertyName, @"^(.+)\[(\d+)\]$");
if (arrayMatch.Success)
{
var arrayName = arrayMatch.Groups[1].Value;
var index = int.Parse(arrayMatch.Groups[2].Value);
SynLog.Info($"[HandleSpecialComponentProperty] Array access detected: {arrayName}[{index}]");
// Get property or field
var prop = type.GetProperty(arrayName);
var field = type.GetField(arrayName);
if (prop != null && prop.PropertyType.IsArray)
{
var array = prop.GetValue(component) as Array;
if (array != null && index >= 0 && index < array.Length)
{
var elementType = prop.PropertyType.GetElementType();
var convertedValue = ConvertValue(value, elementType);
array.SetValue(convertedValue, index);
SynLog.Info($"[HandleSpecialComponentProperty] Set array element {arrayName}[{index}] = {value}");
return true;
}
}
else if (field != null && field.FieldType.IsArray)
{
var array = field.GetValue(component) as Array;
if (array != null && index >= 0 && index < array.Length)
{
var elementType = field.FieldType.GetElementType();
var convertedValue = ConvertValue(value, elementType);
array.SetValue(convertedValue, index);
SynLog.Info($"[HandleSpecialComponentProperty] Set array element {arrayName}[{index}] = {value}");
return true;
}
}
}
}
// Transform special handling
if (component is Transform transform)
{
switch (lowerPropertyName)
{
case "x":
var pos = transform.position;
pos.x = float.Parse(value);
transform.position = pos;
return true;
case "y":
var pos2 = transform.position;
pos2.y = float.Parse(value);
transform.position = pos2;
return true;
case "z":
var pos3 = transform.position;
pos3.z = float.Parse(value);
transform.position = pos3;
return true;
case "pos":
case "position":
transform.position = ParseVector3(value);
return true;
case "rot":
case "rotation":
transform.rotation = Quaternion.Euler(ParseVector3(value));
return true;
case "scale":
transform.localScale = ParseVector3(value);
return true;
}
}
// Renderer color settings (URP/HDRP compatible)
if (component is Renderer renderer)
{
switch (lowerPropertyName)
{
case "color":
if (renderer.sharedMaterial != null)
{
// Create instance to avoid modifying shared material
var mat = renderer.material; // This creates an instance
var color = ParseColor(value);
// URP/HDRP use _BaseColor, Legacy uses _Color
if (mat.HasProperty("_BaseColor"))
{
mat.SetColor("_BaseColor", color);
SynLog.Info($"[HandleSpecialComponentProperty] Set _BaseColor to {color}");
}
else if (mat.HasProperty("_Color"))
{
mat.SetColor("_Color", color);
SynLog.Info($"[HandleSpecialComponentProperty] Set _Color to {color}");
}
else
{
// Fallback: try setting color property directly
mat.color = color;
SynLog.Info($"[HandleSpecialComponentProperty] Set material.color to {color}");
}
return true;
}
break;
case "enabled":
renderer.enabled = bool.Parse(value);
return true;
}
}
// Light handling
if (component is Light light)
{
switch (lowerPropertyName)
{
case "color":
light.color = ParseColor(value);
return true;
case "intensity":
light.intensity = float.Parse(value);
return true;
case "range":
light.range = float.Parse(value);
return true;
case "enabled":
light.enabled = bool.Parse(value);
return true;
}
}
// Camera handling
if (component is Camera camera)
{
switch (lowerPropertyName)
{
case "fov":
case "fieldofview":
camera.fieldOfView = float.Parse(value);
return true;
case "near":
case "nearplane":
camera.nearClipPlane = float.Parse(value);
return true;
case "far":
case "farplane":
camera.farClipPlane = float.Parse(value);
return true;
case "orthographic":
camera.orthographic = bool.Parse(value);
return true;
}
}
// Rigidbody handling
if (component is Rigidbody rigidbody)
{
switch (lowerPropertyName)
{
case "mass":
rigidbody.mass = float.Parse(value);
return true;
case "usegravity":
rigidbody.useGravity = bool.Parse(value);
return true;
case "drag":
rigidbody.linearDamping = float.Parse(value);
return true;
case "angulardrag":
rigidbody.angularDamping = float.Parse(value);
return true;
case "iskinematic":
rigidbody.isKinematic = bool.Parse(value);
return true;
case "interpolate":
if (System.Enum.TryParse<RigidbodyInterpolation>(value, true, out var interpolation))
{
rigidbody.interpolation = interpolation;
return true;
}
break;
case "freezeposition":
case "freezepositionx":
rigidbody.freezeRotation = bool.Parse(value);
return true;
}
}
// AudioSource handling
if (component is AudioSource audioSource)
{
switch (lowerPropertyName)
{
case "clip":
case "audioclip":
var audioClip = ConvertValue(value, typeof(AudioClip)) as AudioClip;
if (audioClip != null)
{
audioSource.clip = audioClip;
SynLog.Info($"[HandleSpecialComponentProperty] Set AudioSource.clip to {audioClip.name}");
return true;
}
Debug.LogError($"[HandleSpecialComponentProperty] Failed to load AudioClip: {value}");
return false;
case "volume":
audioSource.volume = float.Parse(value);
return true;
case "pitch":
audioSource.pitch = float.Parse(value);
return true;
case "play":
if (bool.Parse(value))
{
audioSource.Play();
SynLog.Info($"[HandleSpecialComponentProperty] AudioSource.Play() called");
}
else
{
audioSource.Stop();
SynLog.Info($"[HandleSpecialComponentProperty] AudioSource.Stop() called");
}
return true;
case "loop":
audioSource.loop = bool.Parse(value);
return true;
case "mute":
audioSource.mute = bool.Parse(value);
return true;
case "playonawake":
audioSource.playOnAwake = bool.Parse(value);
return true;
case "spatiallblend":
case "spatialblend":
audioSource.spatialBlend = float.Parse(value);
return true;
case "priority":
audioSource.priority = int.Parse(value);
return true;
}
}
// VisualEffect handling
if (type.Name == "VisualEffect" || type.FullName == "UnityEngine.VFX.VisualEffect")
{
switch (lowerPropertyName)
{
case "visualeffectasset":
case "asset":
case "vfxasset":
var vfxAsset = AssetDatabase.LoadAssetAtPath<UnityEngine.VFX.VisualEffectAsset>(value);
if (vfxAsset != null)
{
var assetProp = type.GetProperty("visualEffectAsset", BindingFlags.Public | BindingFlags.Instance);
if (assetProp != null)
{
assetProp.SetValue(component, vfxAsset);
SynLog.Info($"[HandleSpecialComponentProperty] Set VisualEffect.visualEffectAsset to {vfxAsset.name}");
return true;
}
}
SynLog.Warn($"[HandleSpecialComponentProperty] Failed to load VisualEffectAsset: {value}");
return false;
case "playrate":
var playRateProp = type.GetProperty("playRate", BindingFlags.Public | BindingFlags.Instance);
if (playRateProp != null)
{
playRateProp.SetValue(component, float.Parse(value));
return true;
}
break;
case "pause":
case "paused":
var pauseProp = type.GetProperty("pause", BindingFlags.Public | BindingFlags.Instance);
if (pauseProp != null)
{
pauseProp.SetValue(component, bool.Parse(value));
return true;
}
break;
}
}
SynLog.Info($"[HandleSpecialComponentProperty] No special case found for {type.Name}.{propertyName}");
return false;
}
private string GetActualPropertyName(Type componentType, string propertyName)
{
SynLog.Info($"[GetActualPropertyName] Component: {componentType.Name}, Property: {propertyName}");
// Try exact match first
if (componentType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance) != null ||
componentType.GetField(propertyName, BindingFlags.Public | BindingFlags.Instance) != null)
{
SynLog.Info($"[GetActualPropertyName] Exact match found: {propertyName}");
return propertyName;
}
// Component-specific mapping
var lowerPropertyName = propertyName.ToLower();
string mappedName = null;
// Transform specialized
if (componentType.Name == "Transform")
{
switch (lowerPropertyName)
{
case "x": case "posx": mappedName = "position"; break;
case "y": case "posy": mappedName = "position"; break;
case "z": case "posz": mappedName = "position"; break;
case "pos": case "position": mappedName = "position"; break;
case "rot": case "rotation": mappedName = "rotation"; break;
case "scale": mappedName = "localScale"; break;
}
}
// Light specialized
else if (componentType.Name == "Light")
{
switch (lowerPropertyName)
{
case "color": mappedName = "color"; break;
case "intensity": mappedName = "intensity"; break;
case "range": mappedName = "range"; break;
case "type": mappedName = "type"; break;
case "enabled": mappedName = "enabled"; break;
}
}
// Camera specialized
else if (componentType.Name == "Camera")
{
switch (lowerPropertyName)
{
case "fov": case "fieldofview": mappedName = "fieldOfView"; break;
case "near": case "nearplane": mappedName = "nearClipPlane"; break;
case "far": case "farplane": mappedName = "farClipPlane"; break;
case "orthographic": mappedName = "orthographic"; break;
}
}
// Renderer types
else if (componentType.Name.Contains("Renderer"))
{
switch (lowerPropertyName)
{
case "enabled": mappedName = "enabled"; break;
case "material": mappedName = "material"; break;
case "color": mappedName = "material"; break; // Requires special handling
}
}
// UI Text
else if (componentType.Name == "Text")
{
switch (lowerPropertyName)
{
case "text": case "content": mappedName = "text"; break;
case "color": mappedName = "color"; break;
case "fontsize": case "size": mappedName = "fontSize"; break;
}
}
// Common properties
if (string.IsNullOrEmpty(mappedName))
{
switch (lowerPropertyName)
{
case "enabled": case "active": mappedName = "enabled"; break;
case "color": mappedName = "color"; break;
case "position": mappedName = "position"; break;
case "rotation": mappedName = "rotation"; break;
case "scale": mappedName = "localScale"; break;
}
}
// Check if mapped name actually exists
if (!string.IsNullOrEmpty(mappedName))
{
if (componentType.GetProperty(mappedName, BindingFlags.Public | BindingFlags.Instance) != null ||
componentType.GetField(mappedName, BindingFlags.Public | BindingFlags.Instance) != null)
{
SynLog.Info($"[GetActualPropertyName] Mapped '{propertyName}' to '{mappedName}'");
return mappedName;
}
}
SynLog.Info($"[GetActualPropertyName] No mapping found for '{propertyName}', using original");
return propertyName; // Return original name if not found
}
private object ConvertValue(string value, Type targetType)
{
try
{
SynLog.Info($"[ConvertValue] Converting '{value}' to {targetType.Name}");
if (targetType == typeof(int))
{
if (int.TryParse(value, out var intVal))
return intVal;
if (float.TryParse(value, out var floatVal))
return (int)floatVal; // Convert decimal to integer
}
else if (targetType == typeof(float))
{
if (float.TryParse(value, out var floatVal))
return floatVal;
if (double.TryParse(value, out var doubleVal))
return (float)doubleVal;
}
else if (targetType == typeof(double))
{
if (double.TryParse(value, out var doubleVal))
return doubleVal;
}
else if (targetType == typeof(bool))
{
// Flexible bool conversion
var lowerValue = value.ToLower().Trim();
if (lowerValue == "true" || lowerValue == "1" || lowerValue == "yes" || lowerValue == "on")
return true;
if (lowerValue == "false" || lowerValue == "0" || lowerValue == "no" || lowerValue == "off")
return false;
return bool.Parse(value);
}
else if (targetType == typeof(string))
return value;
else if (targetType == typeof(Vector3))
return ParseVector3(value);
else if (targetType == typeof(Vector2))
return ParseVector2(value);
else if (targetType == typeof(Color))
return ParseColor(value);
else if (targetType.IsEnum)
return Enum.Parse(targetType, value, true);
else if (targetType.IsArray)
{
// Array type processing
var elementType = targetType.GetElementType();
if (elementType == typeof(GameObject))
{
// Process comma-separated prefab paths
var paths = value.Split(',').Select(p => p.Trim()).Where(p => !string.IsNullOrEmpty(p)).ToArray();
var gameObjects = new GameObject[paths.Length];
for (int i = 0; i < paths.Length; i++)
{
// Load prefab from AssetDatabase
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(paths[i]);
if (prefab != null)
{
gameObjects[i] = prefab;
SynLog.Info($"[ConvertValue] Loaded prefab from: {paths[i]}");
}
else
{
SynLog.Warn($"[ConvertValue] Failed to load prefab from: {paths[i]}");
}
}
return gameObjects;
}
else if (elementType == typeof(string))
{
return value.Split(',').Select(s => s.Trim()).ToArray();
}
else if (elementType == typeof(float))
{
return value.Split(',').Select(s => float.Parse(s.Trim())).ToArray();
}
else if (elementType == typeof(int))
{
return value.Split(',').Select(s => int.Parse(s.Trim())).ToArray();
}
}
else if (targetType == typeof(GameObject))
{
// For single GameObject
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(value);
if (prefab != null)
{
return prefab;
}
// Search for object in scene
return GameObject.Find(value);
}
else if (targetType == typeof(AudioClip))
{
// Search and load AudioClip
// 1. Search by direct path
var audioClip = AssetDatabase.LoadAssetAtPath<AudioClip>(value);
if (audioClip != null) return audioClip;
// 2. Search by name in Assets folder
string[] searchPaths = new[]
{
$"Assets/Audio/{value}.wav",
$"Assets/Audio/{value}.mp3",
$"Assets/Audio/{value}.ogg",
$"Assets/Sounds/{value}.wav",
$"Assets/Sounds/{value}.mp3",
$"Assets/Sounds/{value}.ogg",
$"Assets/{value}.wav",
$"Assets/{value}.mp3",
$"Assets/{value}.ogg"
};
foreach (var path in searchPaths)
{
audioClip = AssetDatabase.LoadAssetAtPath<AudioClip>(path);
if (audioClip != null) return audioClip;
}
// 3. Search by name in AssetDatabase
var guids = AssetDatabase.FindAssets($"{value} t:AudioClip");
if (guids.Length > 0)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
audioClip = AssetDatabase.LoadAssetAtPath<AudioClip>(assetPath);
if (audioClip != null) return audioClip;
}
// 4. Search by partial match
guids = AssetDatabase.FindAssets("t:AudioClip");
foreach (var guid in guids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var assetName = System.IO.Path.GetFileNameWithoutExtension(assetPath);
if (assetName.Contains(value))
{
audioClip = AssetDatabase.LoadAssetAtPath<AudioClip>(assetPath);
if (audioClip != null)
{
SynLog.Info($"[ConvertValue] Found AudioClip by partial match: {assetPath}");
return audioClip;
}
}
}
Debug.LogError($"[ConvertValue] AudioClip '{value}' not found in project");
return null;
}
else if (targetType == typeof(Material))
{
// Search and load Material
var material = AssetDatabase.LoadAssetAtPath<Material>(value);
if (material != null) return material;
// Search by name in AssetDatabase
var guids = AssetDatabase.FindAssets($"{value} t:Material");
if (guids.Length > 0)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
return AssetDatabase.LoadAssetAtPath<Material>(assetPath);
}
Debug.LogError($"[ConvertValue] Material '{value}' not found in project");
return null;
}
else if (targetType == typeof(Texture) || targetType == typeof(Texture2D))
{
// Search and load Texture
var texture = AssetDatabase.LoadAssetAtPath<Texture>(value);
if (texture != null) return texture;
// Search by name in AssetDatabase
var guids = AssetDatabase.FindAssets($"{value} t:Texture");
if (guids.Length > 0)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
return AssetDatabase.LoadAssetAtPath<Texture>(assetPath);
}
Debug.LogError($"[ConvertValue] Texture '{value}' not found in project");
return null;
}
else if (targetType == typeof(Sprite))
{
// Search and load Sprite
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(value);
if (sprite != null) return sprite;
// Search by name in AssetDatabase
var guids = AssetDatabase.FindAssets($"{value} t:Sprite");
if (guids.Length > 0)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
return AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
}
Debug.LogError($"[ConvertValue] Sprite '{value}' not found in project");
return null;
}
// Try default conversion
return Convert.ChangeType(value, targetType);
}
catch (Exception ex)
{
Debug.LogError($"[ConvertValue] Failed to convert value '{value}' to type '{targetType.Name}': {ex.Message}");
Debug.LogError($"[ConvertValue] Exception type: {ex.GetType().Name}");
Debug.LogError($"[ConvertValue] Value length: {value?.Length ?? 0}");
Debug.LogError($"[ConvertValue] Stack trace: {ex.StackTrace}");
// More detailed fallback processing
if (targetType == typeof(float))
{
SynLog.Warn($"[ConvertValue] Using fallback float value 0.0f for '{value}'");
return 0.0f;
}
if (targetType == typeof(double))
{
SynLog.Warn($"[ConvertValue] Using fallback double value 0.0 for '{value}'");
return 0.0;
}
if (targetType == typeof(int))
{
SynLog.Warn($"[ConvertValue] Using fallback int value 0 for '{value}'");
return 0;
}
if (targetType == typeof(bool))
{
SynLog.Warn($"[ConvertValue] Using fallback bool value false for '{value}'");
return false;
}
if (targetType == typeof(Vector3))
{
SynLog.Warn($"[ConvertValue] Using fallback Vector3.zero for '{value}'");
return Vector3.zero;
}
if (targetType == typeof(Color))
{
SynLog.Warn($"[ConvertValue] Using fallback Color.white for '{value}'");
return Color.white;
}
SynLog.Warn($"[ConvertValue] No specific fallback for {targetType.Name}, throwing exception");
throw new InvalidCastException($"Cannot convert value '{value}' to type {targetType.Name}: {ex.Message}");
}
}
private Vector3 ParseVector3(string value)
{
// null/empty check
if (string.IsNullOrWhiteSpace(value))
{
SynLog.Warn("[ParseVector3] Empty or null value provided, returning Vector3.zero");
return Vector3.zero;
}
value = value.Trim();
// Check JSON format
if (value.StartsWith("{"))
{
try
{
var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(value);
if (dict != null && dict.ContainsKey("x") && dict.ContainsKey("y") && dict.ContainsKey("z"))
{
float x = Convert.ToSingle(dict["x"]);
float y = Convert.ToSingle(dict["y"]);
float z = Convert.ToSingle(dict["z"]);
// Check for NaN/Infinity
if (float.IsNaN(x) || float.IsNaN(y) || float.IsNaN(z) ||
float.IsInfinity(x) || float.IsInfinity(y) || float.IsInfinity(z))
{
SynLog.Warn($"[ParseVector3] Invalid float values detected in JSON: {value}");
return Vector3.zero;
}
return new Vector3(x, y, z);
}
}
catch (Exception ex)
{
SynLog.Warn($"[ParseVector3] Failed to parse JSON format '{value}': {ex.Message}");
}
}
// Try comma-separated format
try
{
var parts = value.Trim('(', ')', '[', ']').Split(new char[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 3)
{
float x = float.Parse(parts[0].Trim());
float y = float.Parse(parts[1].Trim());
float z = float.Parse(parts[2].Trim());
// Check for NaN/Infinity
if (float.IsNaN(x) || float.IsNaN(y) || float.IsNaN(z) ||
float.IsInfinity(x) || float.IsInfinity(y) || float.IsInfinity(z))
{
SynLog.Warn($"[ParseVector3] Invalid float values detected: {value}");
return Vector3.zero;
}
return new Vector3(x, y, z);
}
else if (parts.Length == 1)
{
// Set same value for all axes for single value
float val = float.Parse(parts[0].Trim());
if (!float.IsNaN(val) && !float.IsInfinity(val))
{
return new Vector3(val, val, val);
}
}
}
catch (Exception ex)
{
SynLog.Warn($"[ParseVector3] Failed to parse comma-separated format '{value}': {ex.Message}");
}
SynLog.Warn($"[ParseVector3] Unable to parse vector3 value '{value}', returning Vector3.zero");
return Vector3.zero;
}
// Safe Vector3 normalization method
private Vector3 SafeNormalize(Vector3 vector)
{
if (vector == Vector3.zero || vector.magnitude < 0.0001f)
{
SynLog.Warn($"[SafeNormalize] Cannot normalize zero or near-zero vector: {vector}");
return Vector3.forward; // Return default direction
}
Vector3 normalized = vector.normalized;
// Check normalization result
if (float.IsNaN(normalized.x) || float.IsNaN(normalized.y) || float.IsNaN(normalized.z) ||
float.IsInfinity(normalized.x) || float.IsInfinity(normalized.y) || float.IsInfinity(normalized.z))
{
SynLog.Warn($"[SafeNormalize] Normalized vector contains invalid values: {normalized} from {vector}");
return Vector3.forward;
}
return normalized;
}
private Vector2 ParseVector2(string value)
{
// Check JSON format
if (value.TrimStart().StartsWith("{"))
{
try
{
var dict = JsonConvert.DeserializeObject<Dictionary<string, float>>(value);
if (dict != null && dict.ContainsKey("x") && dict.ContainsKey("y"))
{
return new Vector2(dict["x"], dict["y"]);
}
}
catch (Exception ex)
{
SynLog.Warn($"[ParseVector2] Failed to parse JSON format: {ex.Message}");
}
}
// Try comma-separated format
var parts = value.Trim('(', ')').Split(',');
if (parts.Length >= 2)
{
return new Vector2(
float.Parse(parts[0].Trim()),
float.Parse(parts[1].Trim())
);
}
return Vector2.zero;
}
private Vector4 ParseVector4(string value)
{
// Check JSON format
if (value.TrimStart().StartsWith("{"))
{
try
{
var dict = JsonConvert.DeserializeObject<Dictionary<string, float>>(value);
if (dict != null && dict.ContainsKey("x") && dict.ContainsKey("y") && dict.ContainsKey("z") && dict.ContainsKey("w"))
{
return new Vector4(dict["x"], dict["y"], dict["z"], dict["w"]);
}
}
catch (Exception ex)
{
SynLog.Warn($"[ParseVector4] Failed to parse JSON format: {ex.Message}");
}
}
// Try comma-separated format: "x,y,z,w"
var parts = value.Trim('(', ')').Split(',');
if (parts.Length >= 4)
{
return new Vector4(
float.Parse(parts[0].Trim()),
float.Parse(parts[1].Trim()),
float.Parse(parts[2].Trim()),
float.Parse(parts[3].Trim())
);
}
return Vector4.zero;
}
private Color ParseColor(string value)
{
// Check JSON format
if (value.TrimStart().StartsWith("{"))
{
try
{
var dict = JsonConvert.DeserializeObject<Dictionary<string, float>>(value);
if (dict != null && dict.ContainsKey("r") && dict.ContainsKey("g") && dict.ContainsKey("b"))
{
return new Color(
dict["r"],
dict["g"],
dict["b"],
dict.ContainsKey("a") ? dict["a"] : 1f
);
}
}
catch (Exception ex)
{
SynLog.Warn($"[ParseColor] Failed to parse JSON format: {ex.Message}");
}
}
switch (value.ToLower())
{
case "red": return Color.red;
case "green": return Color.green;
case "blue": return Color.blue;
case "white": return Color.white;
case "black": return Color.black;
case "yellow": return Color.yellow;
case "cyan": return Color.cyan;
case "magenta": return Color.magenta;
case "gray":
case "grey": return Color.gray;
default:
// Try to parse as hex or RGB
if (value.StartsWith("#"))
{
ColorUtility.TryParseHtmlString(value, out var color);
return color;
}
else if (value.Contains(","))
{
var parts = value.Trim('(', ')').Split(',');
if (parts.Length >= 3)
{
return new Color(
float.Parse(parts[0].Trim()),
float.Parse(parts[1].Trim()),
float.Parse(parts[2].Trim()),
parts.Length > 3 ? float.Parse(parts[3].Trim()) : 1f
);
}
}
return Color.white;
}
}
public List<GameObject> GetCreatedObjects()
{
return createdObjects.Where(o => o != null).ToList();
}
public void ClearCreatedObjects()
{
foreach (var obj in createdObjects.Where(o => o != null))
{
#if UNITY_EDITOR
if (Application.isPlaying)
GameObject.Destroy(obj);
else
UnityEngine.Object.DestroyImmediate(obj);
#else
GameObject.Destroy(obj);
#endif
}
createdObjects.Clear();
}
// New implementation: Terrain tools
private string CreateTerrain(Dictionary<string, string> parameters)
{
try
{
var name = parameters.GetValueOrDefault("name", "Terrain");
var width = float.Parse(parameters.GetValueOrDefault("width", "100"));
var height = float.Parse(parameters.GetValueOrDefault("height", "30"));
var length = float.Parse(parameters.GetValueOrDefault("length", "100"));
var resolution = int.Parse(parameters.GetValueOrDefault("resolution", "513"));
// Create TerrainData
var terrainData = new TerrainData();
terrainData.heightmapResolution = resolution;
terrainData.size = new Vector3(width, height, length);
terrainData.name = name + "_Data";
// Save TerrainData as asset
string folderPath = "Assets/Nexus_Generated/Terrains";
if (!AssetDatabase.IsValidFolder(folderPath))
{
if (!AssetDatabase.IsValidFolder("Assets/Nexus_Generated"))
{
AssetDatabase.CreateFolder("Assets", "Nexus_Generated");
}
AssetDatabase.CreateFolder("Assets/Nexus_Generated", "Terrains");
}
string dataPath = $"{folderPath}/{terrainData.name}.asset";
AssetDatabase.CreateAsset(terrainData, dataPath);
AssetDatabase.SaveAssets();
// Create Terrain GameObject
GameObject terrainObject = Terrain.CreateTerrainGameObject(terrainData);
terrainObject.name = name;
// Set position
if (parameters.ContainsKey("position"))
{
terrainObject.transform.position = ParseVector3(parameters["position"]);
}
// Basic terrain settings
var terrain = terrainObject.GetComponent<Terrain>();
terrain.materialTemplate = AssetDatabase.GetBuiltinExtraResource<Material>("Default-Terrain-Standard.mat");
// Layer settings for terrain painting (optional)
if (parameters.GetValueOrDefault("addGrassLayer", "false") == "true")
{
var grassLayer = new TerrainLayer();
grassLayer.diffuseTexture = AssetDatabase.GetBuiltinExtraResource<Texture2D>("Grass (Hill).psd");
grassLayer.tileSize = new Vector2(15, 15);
grassLayer.name = "Grass";
string layerPath = $"{folderPath}/{name}_GrassLayer.terrainlayer";
AssetDatabase.CreateAsset(grassLayer, layerPath);
terrain.terrainData.terrainLayers = new TerrainLayer[] { grassLayer };
}
lastCreatedObject = terrainObject;
createdObjects.Add(terrainObject);
// Register to UNDO
UnityEditor.Undo.RegisterCreatedObjectUndo(terrainObject, $"Create Terrain {name}");
EditorUtility.SetDirty(terrainObject);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Terrain '{name}' created successfully",
gameObjectPath = GetFullPath(terrainObject),
terrainDataPath = dataPath,
size = new { width, height, length },
resolution = resolution
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string ModifyTerrain(Dictionary<string, string> parameters)
{
try
{
// Use name parameter as main per API definition, also support terrain
var terrainName = parameters.GetValueOrDefault("name", "") ??
parameters.GetValueOrDefault("terrain", "");
if (string.IsNullOrEmpty(terrainName))
{
return CreateMissingParameterResponse("ModifyTerrain", "name", parameters);
}
var terrain = GameObject.Find(terrainName)?.GetComponent<Terrain>();
if (terrain == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Terrain '{terrainName}' not found"
});
}
var operation = parameters.GetValueOrDefault("operation", "raise");
int x = 0;
int z = 0;
int radius = 10;
float strength = 0.1f;
try
{
if (parameters.TryGetValue("x", out var xStr))
x = int.Parse(xStr);
if (parameters.TryGetValue("z", out var zStr))
z = int.Parse(zStr);
if (parameters.TryGetValue("radius", out var radiusStr))
radius = int.Parse(radiusStr);
if (parameters.TryGetValue("strength", out var strengthStr))
strength = float.Parse(strengthStr);
}
catch (Exception ex)
{
SynLog.Warn($"Failed to parse parameters, using defaults: {ex.Message}");
}
var terrainData = terrain.terrainData;
if (terrainData == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Terrain data is null"
});
}
var heights = terrainData.GetHeights(0, 0, terrainData.heightmapResolution, terrainData.heightmapResolution);
// Terrain modification processing
int centerX = (int)(x * terrainData.heightmapResolution / terrainData.size.x);
int centerZ = (int)(z * terrainData.heightmapResolution / terrainData.size.z);
for (int i = -radius; i <= radius; i++)
{
for (int j = -radius; j <= radius; j++)
{
int hx = centerX + i;
int hz = centerZ + j;
if (hx >= 0 && hx < terrainData.heightmapResolution &&
hz >= 0 && hz < terrainData.heightmapResolution)
{
float distance = Mathf.Sqrt(i * i + j * j);
if (distance <= radius)
{
float falloff = 1 - (distance / radius);
falloff = Mathf.SmoothStep(0, 1, falloff);
switch (operation)
{
case "raise":
heights[hz, hx] += strength * falloff;
break;
case "lower":
heights[hz, hx] -= strength * falloff;
break;
case "flatten":
float targetHeight = float.Parse(parameters.GetValueOrDefault("targetHeight", "0.5"));
heights[hz, hx] = Mathf.Lerp(heights[hz, hx], targetHeight, falloff);
break;
}
heights[hz, hx] = Mathf.Clamp01(heights[hz, hx]);
}
}
}
}
terrainData.SetHeights(0, 0, heights);
EditorUtility.SetDirty(terrain);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Terrain '{terrainName}' modified successfully",
operation = operation,
position = new { x, z },
radius = radius,
strength = strength
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string GetCameraInfo(Dictionary<string, string> parameters)
{
try
{
var cameraName = parameters.GetValueOrDefault("cameraName", "");
var cameras = new List<Camera>();
if (!string.IsNullOrEmpty(cameraName))
{
var camera = GameObject.Find(cameraName)?.GetComponent<Camera>();
if (camera != null)
{
cameras.Add(camera);
}
}
else
{
cameras.AddRange(Camera.allCameras);
}
var cameraInfoList = new List<Dictionary<string, object>>();
foreach (var camera in cameras)
{
var info = new Dictionary<string, object>
{
["name"] = camera.name,
["tag"] = camera.tag,
["enabled"] = camera.enabled,
["depth"] = camera.depth,
["fieldOfView"] = camera.fieldOfView,
["nearClipPlane"] = camera.nearClipPlane,
["farClipPlane"] = camera.farClipPlane,
["rect"] = new
{
x = camera.rect.x,
y = camera.rect.y,
width = camera.rect.width,
height = camera.rect.height
},
["clearFlags"] = camera.clearFlags.ToString(),
["backgroundColor"] = ColorToHex(camera.backgroundColor),
["cullingMask"] = LayerMaskToLayers(camera.cullingMask),
["renderingPath"] = camera.renderingPath.ToString(),
["targetTexture"] = camera.targetTexture != null ? camera.targetTexture.name : "None",
["isMainCamera"] = camera == Camera.main,
["transform"] = new
{
position = new { x = camera.transform.position.x, y = camera.transform.position.y, z = camera.transform.position.z },
rotation = new { x = camera.transform.eulerAngles.x, y = camera.transform.eulerAngles.y, z = camera.transform.eulerAngles.z },
forward = new { x = camera.transform.forward.x, y = camera.transform.forward.y, z = camera.transform.forward.z },
right = new { x = camera.transform.right.x, y = camera.transform.right.y, z = camera.transform.right.z },
up = new { x = camera.transform.up.x, y = camera.transform.up.y, z = camera.transform.up.z }
},
["components"] = camera.GetComponents<Component>()
.Select(c => c.GetType().Name)
.ToList()
};
// HDR settings
info["allowHDR"] = camera.allowHDR;
info["allowMSAA"] = camera.allowMSAA;
cameraInfoList.Add(info);
}
return JsonConvert.SerializeObject(new
{
success = true,
cameras = cameraInfoList,
count = cameraInfoList.Count,
mainCamera = Camera.main != null ? Camera.main.name : "None"
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string GetTerrainInfo(Dictionary<string, string> parameters)
{
try
{
var terrainName = parameters.GetValueOrDefault("terrainName", "");
var terrains = new List<Terrain>();
if (!string.IsNullOrEmpty(terrainName))
{
var terrain = GameObject.Find(terrainName)?.GetComponent<Terrain>();
if (terrain != null)
{
terrains.Add(terrain);
}
}
else
{
terrains.AddRange(Terrain.activeTerrains);
}
var terrainInfoList = new List<Dictionary<string, object>>();
foreach (var terrain in terrains)
{
var terrainData = terrain.terrainData;
var info = new Dictionary<string, object>
{
["name"] = terrain.name,
["position"] = Vector3ToString(terrain.transform.position),
["size"] = new
{
width = terrainData.size.x,
height = terrainData.size.y,
length = terrainData.size.z
},
["heightmapResolution"] = terrainData.heightmapResolution,
["basemapResolution"] = terrainData.baseMapResolution,
["detailResolution"] = terrainData.detailResolution,
["detailResolutionPerPatch"] = terrainData.detailResolutionPerPatch,
["layers"] = terrainData.terrainLayers?.Select(layer => new
{
name = layer.name,
diffuseTexture = layer.diffuseTexture?.name,
normalMapTexture = layer.normalMapTexture?.name,
tileSize = $"({layer.tileSize.x}, {layer.tileSize.y})",
tileOffset = $"({layer.tileOffset.x}, {layer.tileOffset.y})"
}).ToList(),
["treeInstanceCount"] = terrainData.treeInstanceCount,
["treePrototypes"] = terrainData.treePrototypes?.Select(proto => new
{
prefab = proto.prefab?.name,
bendFactor = proto.bendFactor
}).ToList(),
["detailPrototypes"] = terrainData.detailPrototypes?.Select(proto => new
{
prototype = proto.prototype?.name,
prototypeTexture = proto.prototypeTexture?.name,
minWidth = proto.minWidth,
maxWidth = proto.maxWidth,
minHeight = proto.minHeight,
maxHeight = proto.maxHeight
}).ToList(),
["drawTreesAndFoliage"] = terrain.drawTreesAndFoliage,
["terrainLightingEnabled"] = terrain.bakeLightProbesForTrees,
["materialTemplate"] = terrain.materialTemplate?.name,
["reflectionProbeUsage"] = terrain.reflectionProbeUsage.ToString()
};
terrainInfoList.Add(info);
}
return JsonConvert.SerializeObject(new
{
success = true,
terrains = terrainInfoList,
count = terrainInfoList.Count
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string GetLightingInfo(Dictionary<string, string> parameters)
{
try
{
var includeEnvironment = parameters.GetValueOrDefault("includeEnvironment", "true") == "true";
var includeLights = parameters.GetValueOrDefault("includeLights", "true") == "true";
var includeFog = parameters.GetValueOrDefault("includeFog", "true") == "true";
var lightingInfo = new Dictionary<string, object>();
// Environment settings
if (includeEnvironment)
{
lightingInfo["environment"] = new Dictionary<string, object>
{
["ambientMode"] = RenderSettings.ambientMode.ToString(),
["ambientColor"] = ColorToHex(RenderSettings.ambientSkyColor),
["ambientIntensity"] = RenderSettings.ambientIntensity,
["ambientGroundColor"] = ColorToHex(RenderSettings.ambientGroundColor),
["ambientEquatorColor"] = ColorToHex(RenderSettings.ambientEquatorColor),
["skybox"] = RenderSettings.skybox?.name ?? "None",
["sun"] = RenderSettings.sun?.name ?? "None",
["reflectionIntensity"] = RenderSettings.reflectionIntensity,
["defaultReflectionMode"] = RenderSettings.defaultReflectionMode.ToString()
};
}
// Light list
if (includeLights)
{
var lights = GameObject.FindObjectsOfType<Light>();
lightingInfo["lights"] = lights.Select(light => new Dictionary<string, object>
{
["name"] = light.name,
["type"] = light.type.ToString(),
["color"] = ColorToHex(light.color),
["intensity"] = light.intensity,
["range"] = light.range,
["spotAngle"] = light.spotAngle,
["shadowType"] = light.shadows.ToString(),
["shadowStrength"] = light.shadowStrength,
["shadowResolution"] = light.shadowResolution.ToString(),
["position"] = Vector3ToString(light.transform.position),
["rotation"] = Vector3ToString(light.transform.eulerAngles),
["enabled"] = light.enabled
}).ToList();
lightingInfo["lightCount"] = lights.Length;
}
// Fog settings
if (includeFog)
{
lightingInfo["fog"] = new Dictionary<string, object>
{
["enabled"] = RenderSettings.fog,
["color"] = ColorToHex(RenderSettings.fogColor),
["mode"] = RenderSettings.fogMode.ToString(),
["density"] = RenderSettings.fogDensity,
["startDistance"] = RenderSettings.fogStartDistance,
["endDistance"] = RenderSettings.fogEndDistance
};
}
return JsonConvert.SerializeObject(new
{
success = true,
lighting = lightingInfo
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string GetMaterialInfo(Dictionary<string, string> parameters)
{
try
{
var includeSceneMaterials = parameters.GetValueOrDefault("includeSceneMaterials", "true") == "true";
var includeProjectMaterials = parameters.GetValueOrDefault("includeProjectMaterials", "false") == "true";
var materialInfo = new Dictionary<string, object>();
var materials = new HashSet<Material>();
// Collect Materials in scene
if (includeSceneMaterials)
{
var renderers = GameObject.FindObjectsOfType<Renderer>();
foreach (var renderer in renderers)
{
foreach (var mat in renderer.sharedMaterials)
{
if (mat != null)
{
materials.Add(mat);
}
}
}
}
// Collect Materials in project
if (includeProjectMaterials)
{
var guids = AssetDatabase.FindAssets("t:Material");
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var mat = AssetDatabase.LoadAssetAtPath<Material>(path);
if (mat != null)
{
materials.Add(mat);
}
}
}
materialInfo["materials"] = materials.Select(mat => new Dictionary<string, object>
{
["name"] = mat.name,
["shader"] = mat.shader.name,
["renderQueue"] = mat.renderQueue,
["mainTexture"] = mat.mainTexture?.name ?? "None",
["color"] = mat.HasProperty("_Color") ? ColorToHex(mat.GetColor("_Color")) : "N/A",
["properties"] = GetMaterialProperties(mat),
["keywords"] = mat.shaderKeywords.ToList(),
["path"] = AssetDatabase.GetAssetPath(mat)
}).ToList();
materialInfo["count"] = materials.Count;
return JsonConvert.SerializeObject(new
{
success = true,
materials = materialInfo
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string GetUIInfo(Dictionary<string, string> parameters)
{
try
{
var canvasName = parameters.GetValueOrDefault("canvasName", "");
var canvases = new List<Canvas>();
if (!string.IsNullOrEmpty(canvasName))
{
var canvas = GameObject.Find(canvasName)?.GetComponent<Canvas>();
if (canvas != null)
{
canvases.Add(canvas);
}
}
else
{
canvases.AddRange(GameObject.FindObjectsOfType<Canvas>());
}
var uiInfo = new Dictionary<string, object>
{
["eventSystem"] = EventSystem.current != null,
["canvases"] = canvases.Select(canvas => new Dictionary<string, object>
{
["name"] = canvas.name,
["renderMode"] = canvas.renderMode.ToString(),
["sortingOrder"] = canvas.sortingOrder,
["sortingLayerName"] = canvas.sortingLayerName,
["pixelPerfect"] = canvas.pixelPerfect,
["worldCamera"] = canvas.worldCamera?.name ?? "None",
["planeDistance"] = canvas.planeDistance,
["scaleFactor"] = canvas.scaleFactor,
["referencePixelsPerUnit"] = canvas.referencePixelsPerUnit,
["overrideSorting"] = canvas.overrideSorting,
["position"] = Vector3ToString(canvas.transform.position),
["uiElements"] = GetUIElementsInCanvas(canvas)
}).ToList()
};
uiInfo["totalCanvasCount"] = canvases.Count;
return JsonConvert.SerializeObject(new
{
success = true,
ui = uiInfo
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string GetPhysicsInfo(Dictionary<string, string> parameters)
{
try
{
var includeRigidbodies = parameters.GetValueOrDefault("includeRigidbodies", "true") == "true";
var includeColliders = parameters.GetValueOrDefault("includeColliders", "true") == "true";
var includeJoints = parameters.GetValueOrDefault("includeJoints", "false") == "true";
var physicsInfo = new Dictionary<string, object>
{
["gravity"] = Vector3ToString(Physics.gravity),
["defaultSolverIterations"] = Physics.defaultSolverIterations,
["defaultSolverVelocityIterations"] = Physics.defaultSolverVelocityIterations,
["bounceThreshold"] = Physics.bounceThreshold,
["sleepThreshold"] = Physics.sleepThreshold,
["defaultContactOffset"] = Physics.defaultContactOffset,
["autoSyncTransforms"] = Physics.autoSyncTransforms
};
if (includeRigidbodies)
{
var rigidbodies = GameObject.FindObjectsOfType<Rigidbody>();
physicsInfo["rigidbodies"] = rigidbodies.Select(rb => new Dictionary<string, object>
{
["name"] = rb.name,
["mass"] = rb.mass,
["drag"] = rb.linearDamping,
["angularDrag"] = rb.angularDamping,
["useGravity"] = rb.useGravity,
["isKinematic"] = rb.isKinematic,
["velocity"] = Vector3ToString(rb.linearVelocity),
["angularVelocity"] = Vector3ToString(rb.angularVelocity),
["constraints"] = rb.constraints.ToString()
}).ToList();
physicsInfo["rigidbodyCount"] = rigidbodies.Length;
}
if (includeColliders)
{
var colliders = GameObject.FindObjectsOfType<Collider>();
physicsInfo["colliders"] = colliders.GroupBy(c => c.GetType().Name)
.ToDictionary(g => g.Key, g => g.Count());
physicsInfo["totalColliderCount"] = colliders.Length;
}
if (includeJoints)
{
var joints = GameObject.FindObjectsOfType<Joint>();
physicsInfo["joints"] = joints.GroupBy(j => j.GetType().Name)
.ToDictionary(g => g.Key, g => g.Count());
physicsInfo["totalJointCount"] = joints.Length;
}
return JsonConvert.SerializeObject(new
{
success = true,
physics = physicsInfo
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string ListAssets(Dictionary<string, string> parameters)
{
try
{
var assetType = parameters.GetValueOrDefault("assetType", "all");
var folder = parameters.GetValueOrDefault("folder", parameters.GetValueOrDefault("path", "Assets"));
var searchPattern = parameters.GetValueOrDefault("searchPattern", "");
string filter = assetType switch
{
"scripts" => "t:Script",
"prefabs" => "t:Prefab",
"materials" => "t:Material",
"textures" => "t:Texture",
"audio" => "t:AudioClip",
_ => ""
};
if (!string.IsNullOrEmpty(searchPattern))
{
filter = string.IsNullOrEmpty(filter) ? searchPattern : $"{filter} {searchPattern}";
}
var guids = AssetDatabase.FindAssets(filter, new[] { folder });
var assets = new List<Dictionary<string, object>>();
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadMainAssetAtPath(path);
if (asset != null)
{
var fileInfo = new System.IO.FileInfo(path);
assets.Add(new Dictionary<string, object>
{
["name"] = asset.name,
["type"] = asset.GetType().Name,
["path"] = path,
["size"] = fileInfo.Exists ? fileInfo.Length : 0,
["lastModified"] = fileInfo.Exists ? fileInfo.LastWriteTime.ToString() : "N/A"
});
}
}
return JsonConvert.SerializeObject(new
{
success = true,
assets = assets,
count = assets.Count,
folder = folder,
type = assetType
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string GetProjectStats(Dictionary<string, string> parameters)
{
try
{
var includeScripts = parameters.GetValueOrDefault("includeScripts", "true") == "true";
var includeAssets = parameters.GetValueOrDefault("includeAssets", "true") == "true";
var includeSceneInfo = parameters.GetValueOrDefault("includeSceneInfo", "true") == "true";
var stats = new Dictionary<string, object>();
// Basic project information
stats["projectName"] = Application.productName;
stats["companyName"] = Application.companyName;
stats["unityVersion"] = Application.unityVersion;
stats["platform"] = Application.platform.ToString();
// Memory usage
stats["memory"] = new Dictionary<string, object>
{
["totalAllocatedMemory"] = Profiler.GetTotalAllocatedMemoryLong() / (1024 * 1024) + " MB",
["totalReservedMemory"] = Profiler.GetTotalReservedMemoryLong() / (1024 * 1024) + " MB",
["monoHeapSize"] = Profiler.GetMonoHeapSizeLong() / (1024 * 1024) + " MB",
["monoUsedSize"] = Profiler.GetMonoUsedSizeLong() / (1024 * 1024) + " MB"
};
// Scene information
if (includeSceneInfo)
{
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
stats["activeScene"] = new Dictionary<string, object>
{
["name"] = scene.name,
["path"] = scene.path,
["isDirty"] = scene.isDirty,
["rootCount"] = scene.rootCount,
["isLoaded"] = scene.isLoaded
};
// Count GameObjects
var allObjects = GameObject.FindObjectsOfType<GameObject>();
stats["gameObjectCount"] = allObjects.Length;
}
// Script statistics
if (includeScripts)
{
var scriptGuids = AssetDatabase.FindAssets("t:Script");
stats["scriptCount"] = scriptGuids.Length;
// MonoBehaviour count
var monoBehaviours = GameObject.FindObjectsOfType<MonoBehaviour>();
stats["monoBehaviourCount"] = monoBehaviours.Length;
}
// Asset statistics
if (includeAssets)
{
var assetStats = new Dictionary<string, int>
{
["materials"] = AssetDatabase.FindAssets("t:Material").Length,
["textures"] = AssetDatabase.FindAssets("t:Texture").Length,
["meshes"] = AssetDatabase.FindAssets("t:Mesh").Length,
["prefabs"] = AssetDatabase.FindAssets("t:Prefab").Length,
["audioClips"] = AssetDatabase.FindAssets("t:AudioClip").Length,
["animations"] = AssetDatabase.FindAssets("t:AnimationClip").Length,
["shaders"] = AssetDatabase.FindAssets("t:Shader").Length
};
stats["assetCounts"] = assetStats;
stats["totalAssets"] = assetStats.Values.Sum();
}
return JsonConvert.SerializeObject(new
{
success = true,
statistics = stats
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string ManagePackage(Dictionary<string, string> parameters)
{
try
{
var operation = parameters.GetValueOrDefault("operation", "list");
var packageId = parameters.GetValueOrDefault("packageId", "");
var version = parameters.GetValueOrDefault("version", "");
switch (operation)
{
case "list":
var packageListRequest = UnityEditor.PackageManager.Client.List();
while (!packageListRequest.IsCompleted)
{
System.Threading.Thread.Sleep(100);
}
if (packageListRequest.Status == UnityEditor.PackageManager.StatusCode.Success)
{
var packages = packageListRequest.Result.Select(pkg => new Dictionary<string, object>
{
["name"] = pkg.name,
["displayName"] = pkg.displayName,
["version"] = pkg.version,
["source"] = pkg.source.ToString(),
["isDirectDependency"] = pkg.isDirectDependency
}).ToList();
return JsonConvert.SerializeObject(new
{
success = true,
packages = packages,
count = packages.Count
}, Formatting.Indented);
}
else
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Failed to list packages"
});
}
case "add":
if (string.IsNullOrEmpty(packageId))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "packageId is required for add operation"
});
}
var identifier = string.IsNullOrEmpty(version) ? packageId : $"{packageId}@{version}";
var addRequest = UnityEditor.PackageManager.Client.Add(identifier);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Package add request initiated for: {identifier}",
note = "Check Unity Package Manager window for progress"
});
case "remove":
if (string.IsNullOrEmpty(packageId))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "packageId is required for remove operation"
});
}
var removeRequest = UnityEditor.PackageManager.Client.Remove(packageId);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Package remove request initiated for: {packageId}",
note = "Check Unity Package Manager window for progress"
});
default:
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Unknown operation: {operation}"
});
}
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string ManageScene(Dictionary<string, string> parameters)
{
try
{
var operation = parameters.GetValueOrDefault("operation", "list");
var sceneName = parameters.GetValueOrDefault("sceneName", "");
var scenePath = parameters.GetValueOrDefault("scenePath", "");
switch (operation)
{
case "list":
var sceneCount = UnityEngine.SceneManagement.SceneManager.sceneCount;
var scenes = new List<Dictionary<string, object>>();
for (int i = 0; i < sceneCount; i++)
{
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i);
scenes.Add(new Dictionary<string, object>
{
["name"] = scene.name,
["path"] = scene.path,
["buildIndex"] = scene.buildIndex,
["isLoaded"] = scene.isLoaded,
["isDirty"] = scene.isDirty,
["rootCount"] = scene.rootCount
});
}
return JsonConvert.SerializeObject(new
{
success = true,
scenes = scenes,
activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name
}, Formatting.Indented);
case "new":
case "create":
if (string.IsNullOrEmpty(sceneName))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "sceneName is required for create operation"
});
}
var newScene = UnityEditor.SceneManagement.EditorSceneManager.NewScene(
UnityEditor.SceneManagement.NewSceneSetup.DefaultGameObjects,
UnityEditor.SceneManagement.NewSceneMode.Single
);
if (!string.IsNullOrEmpty(scenePath))
{
UnityEditor.SceneManagement.EditorSceneManager.SaveScene(newScene, scenePath);
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"New scene created",
sceneName = newScene.name,
scenePath = newScene.path
});
case "save":
var activeScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
bool saved;
// If scene has no path (never saved), save to default location
if (string.IsNullOrEmpty(activeScene.path))
{
// Use provided path or generate default
string savePath = scenePath;
if (string.IsNullOrEmpty(savePath))
{
string sceneNameToUse = string.IsNullOrEmpty(sceneName) ? activeScene.name : sceneName;
if (string.IsNullOrEmpty(sceneNameToUse) || sceneNameToUse == "Untitled")
{
sceneNameToUse = $"Scene_{DateTime.Now:yyyyMMdd_HHmmss}";
}
savePath = $"Assets/Scenes/{sceneNameToUse}.unity";
}
// Ensure directory exists
string directory = System.IO.Path.GetDirectoryName(savePath);
if (!string.IsNullOrEmpty(directory) && !System.IO.Directory.Exists(directory))
{
System.IO.Directory.CreateDirectory(directory);
}
saved = UnityEditor.SceneManagement.EditorSceneManager.SaveScene(activeScene, savePath);
}
else
{
saved = UnityEditor.SceneManagement.EditorSceneManager.SaveScene(activeScene);
}
return JsonConvert.SerializeObject(new
{
success = saved,
message = saved ? "Scene saved successfully" : "Failed to save scene",
sceneName = activeScene.name,
scenePath = activeScene.path
});
default:
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Unknown operation: {operation}"
});
}
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
/// <summary>
/// Load scene
/// </summary>
private string LoadScene(Dictionary<string, string> parameters)
{
try
{
string scenePath = parameters.GetValueOrDefault("scenePath", "");
string sceneName = parameters.GetValueOrDefault("sceneName", "");
string mode = parameters.GetValueOrDefault("mode", "Single");
bool setActive = parameters.GetValueOrDefault("setActive", "true").ToLower() == "true";
// Determine scene path
if (string.IsNullOrEmpty(scenePath) && !string.IsNullOrEmpty(sceneName))
{
// Search by scene name
string[] guids = AssetDatabase.FindAssets($"{sceneName} t:Scene");
if (guids.Length == 0)
{
return CreateErrorResponse($"Scene '{sceneName}' not found in project");
}
scenePath = AssetDatabase.GUIDToAssetPath(guids[0]);
}
if (string.IsNullOrEmpty(scenePath))
{
return CreateErrorResponse("Either scenePath or sceneName must be provided");
}
if (!System.IO.File.Exists(scenePath))
{
return CreateErrorResponse($"Scene file not found: {scenePath}");
}
// Load scene
var openMode = mode == "Additive" ?
UnityEditor.SceneManagement.OpenSceneMode.Additive :
UnityEditor.SceneManagement.OpenSceneMode.Single;
var scene = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(scenePath, openMode);
// Set as active in Additive mode
if (mode == "Additive" && setActive)
{
UnityEngine.SceneManagement.SceneManager.SetActiveScene(scene);
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Scene loaded: {scene.name}",
sceneName = scene.name,
scenePath = scene.path,
mode = mode,
isActive = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == scene.name
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to load scene: {e.Message}");
}
}
/// <summary>
/// Unload scene
/// </summary>
private string UnloadScene(Dictionary<string, string> parameters)
{
try
{
string sceneName = parameters.GetValueOrDefault("sceneName", "");
bool removeUnsavedChanges = parameters.GetValueOrDefault("removeUnsavedChanges", "false").ToLower() == "true";
if (string.IsNullOrEmpty(sceneName))
{
return CreateErrorResponse("sceneName is required");
}
// Search scene
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(sceneName);
if (!scene.isLoaded)
{
return CreateErrorResponse($"Scene '{sceneName}' is not loaded");
}
// Cannot unload last scene
if (UnityEngine.SceneManagement.SceneManager.sceneCount <= 1)
{
return CreateErrorResponse("Cannot unload the last scene. At least one scene must be loaded.");
}
// If there are unsaved changes
if (scene.isDirty && !removeUnsavedChanges)
{
return CreateErrorResponse($"Scene '{sceneName}' has unsaved changes. Set removeUnsavedChanges=true to force unload.");
}
// Close scene
bool success = UnityEditor.SceneManagement.EditorSceneManager.CloseScene(scene, removeUnsavedChanges);
return JsonConvert.SerializeObject(new
{
success = success,
message = success ? $"Scene '{sceneName}' unloaded" : "Failed to unload scene",
sceneName = sceneName
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to unload scene: {e.Message}");
}
}
/// <summary>
/// Set active scene
/// </summary>
private string SetActiveScene(Dictionary<string, string> parameters)
{
try
{
string sceneName = parameters.GetValueOrDefault("sceneName", "");
if (string.IsNullOrEmpty(sceneName))
{
return CreateErrorResponse("sceneName is required");
}
// Search scene
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(sceneName);
if (!scene.isLoaded)
{
return CreateErrorResponse($"Scene '{sceneName}' is not loaded. Load it first.");
}
// Set as active
bool success = UnityEngine.SceneManagement.SceneManager.SetActiveScene(scene);
return JsonConvert.SerializeObject(new
{
success = success,
message = success ? $"Active scene set to '{sceneName}'" : "Failed to set active scene",
sceneName = sceneName,
scenePath = scene.path
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to set active scene: {e.Message}");
}
}
/// <summary>
/// List all scenes in project
/// </summary>
private string ListAllScenes(Dictionary<string, string> parameters)
{
try
{
bool includeInactive = parameters.GetValueOrDefault("includeInactive", "true").ToLower() == "true";
string sortBy = parameters.GetValueOrDefault("sortBy", "name");
string searchPath = parameters.GetValueOrDefault("searchPath", "Assets");
// Search all scene files
string[] sceneGuids = AssetDatabase.FindAssets("t:Scene", new[] { searchPath });
var allScenes = new List<object>();
// Get scenes in build settings
var buildScenes = new Dictionary<string, int>();
var buildSettings = UnityEditor.EditorBuildSettings.scenes;
for (int i = 0; i < buildSettings.Length; i++)
{
buildScenes[buildSettings[i].path] = i;
}
foreach (string guid in sceneGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
string name = System.IO.Path.GetFileNameWithoutExtension(path);
bool inBuild = buildScenes.ContainsKey(path);
if (!includeInactive && !inBuild)
continue;
allScenes.Add(new
{
name = name,
path = path,
inBuildSettings = inBuild,
buildIndex = inBuild ? buildScenes[path] : -1,
enabled = inBuild && buildSettings[buildScenes[path]].enabled
});
}
// Sort
switch (sortBy.ToLower())
{
case "path":
allScenes = allScenes.OrderBy(s => ((dynamic)s).path).ToList();
break;
case "buildindex":
allScenes = allScenes.OrderBy(s => ((dynamic)s).buildIndex).ToList();
break;
case "name":
default:
allScenes = allScenes.OrderBy(s => ((dynamic)s).name).ToList();
break;
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {allScenes.Count} scene(s)",
sceneCount = allScenes.Count,
scenes = allScenes
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to list scenes: {e.Message}");
}
}
/// <summary>
/// Add/Remove scene to/from build settings
/// </summary>
private string AddSceneToBuild(Dictionary<string, string> parameters)
{
try
{
string scenePath = parameters.GetValueOrDefault("scenePath", "");
string operation = parameters.GetValueOrDefault("operation", "add");
int buildIndex = int.Parse(parameters.GetValueOrDefault("buildIndex", "-1"));
if (string.IsNullOrEmpty(scenePath))
{
return CreateErrorResponse("scenePath is required");
}
// Check file existence
if (!System.IO.File.Exists(scenePath))
{
return CreateErrorResponse($"Scene file not found: {scenePath}");
}
var buildScenes = UnityEditor.EditorBuildSettings.scenes.ToList();
switch (operation.ToLower())
{
case "add":
// Check if already exists
if (buildScenes.Any(s => s.path == scenePath))
{
return CreateErrorResponse($"Scene already in build settings: {scenePath}");
}
var newScene = new UnityEditor.EditorBuildSettingsScene(scenePath, true);
if (buildIndex >= 0 && buildIndex < buildScenes.Count)
{
buildScenes.Insert(buildIndex, newScene);
}
else
{
buildScenes.Add(newScene);
}
break;
case "remove":
var sceneToRemove = buildScenes.FirstOrDefault(s => s.path == scenePath);
if (sceneToRemove == null)
{
return CreateErrorResponse($"Scene not found in build settings: {scenePath}");
}
buildScenes.Remove(sceneToRemove);
break;
case "enable":
case "disable":
var sceneToModify = buildScenes.FirstOrDefault(s => s.path == scenePath);
if (sceneToModify == null)
{
return CreateErrorResponse($"Scene not found in build settings: {scenePath}");
}
sceneToModify.enabled = operation == "enable";
break;
default:
return CreateErrorResponse($"Unknown operation: {operation}. Use add/remove/enable/disable");
}
// Save changes
UnityEditor.EditorBuildSettings.scenes = buildScenes.ToArray();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Build settings updated: {operation} - {System.IO.Path.GetFileNameWithoutExtension(scenePath)}",
scenePath = scenePath,
operation = operation,
totalScenesInBuild = buildScenes.Count
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to update build settings: {e.Message}");
}
}
/// <summary>
/// Search prefabs by component
/// </summary>
private string SearchPrefabsByComponent(Dictionary<string, string> parameters)
{
try
{
string componentName = parameters.GetValueOrDefault("componentName", "");
string searchPath = parameters.GetValueOrDefault("searchPath", "Assets");
bool recursive = parameters.GetValueOrDefault("recursive", "true").ToLower() == "true";
bool includeDisabled = parameters.GetValueOrDefault("includeDisabled", "true").ToLower() == "true";
if (string.IsNullOrEmpty(componentName))
{
return CreateErrorResponse("componentName is required");
}
// Search prefabs
string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { searchPath });
var matchingPrefabs = new List<object>();
foreach (string guid in prefabGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab == null) continue;
// Search components
var components = prefab.GetComponentsInChildren(
System.Type.GetType(componentName) ?? typeof(Component),
includeDisabled
);
if (components != null && components.Length > 0)
{
matchingPrefabs.Add(new
{
name = prefab.name,
path = path,
componentCount = components.Length,
components = components.Select(c => new
{
gameObject = c.gameObject.name,
enabled = c is Behaviour ? ((Behaviour)c).enabled : true
}).ToArray()
});
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {matchingPrefabs.Count} prefab(s) with component '{componentName}'",
componentName = componentName,
matchCount = matchingPrefabs.Count,
prefabs = matchingPrefabs
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to search prefabs: {e.Message}");
}
}
/// <summary>
/// Search material usage
/// </summary>
private string FindMaterialUsage(Dictionary<string, string> parameters)
{
try
{
string materialPath = parameters.GetValueOrDefault("materialPath", "");
string materialName = parameters.GetValueOrDefault("materialName", "");
bool searchInScenes = parameters.GetValueOrDefault("searchInScenes", "true").ToLower() == "true";
bool searchInPrefabs = parameters.GetValueOrDefault("searchInPrefabs", "true").ToLower() == "true";
Material targetMaterial = null;
// Get material
if (!string.IsNullOrEmpty(materialPath))
{
targetMaterial = AssetDatabase.LoadAssetAtPath<Material>(materialPath);
}
else if (!string.IsNullOrEmpty(materialName))
{
string[] guids = AssetDatabase.FindAssets($"{materialName} t:Material");
if (guids.Length > 0)
{
targetMaterial = AssetDatabase.LoadAssetAtPath<Material>(AssetDatabase.GUIDToAssetPath(guids[0]));
materialPath = AssetDatabase.GUIDToAssetPath(guids[0]);
}
}
if (targetMaterial == null)
{
return CreateErrorResponse("Material not found");
}
var usageList = new List<object>();
// Search scene
if (searchInScenes)
{
var renderers = UnityEngine.Object.FindObjectsOfType<Renderer>();
foreach (var renderer in renderers)
{
if (renderer.sharedMaterials.Contains(targetMaterial))
{
usageList.Add(new
{
type = "Scene",
gameObject = renderer.gameObject.name,
path = GetGameObjectPath(renderer.gameObject),
materialIndex = System.Array.IndexOf(renderer.sharedMaterials, targetMaterial)
});
}
}
}
// Search prefabs
if (searchInPrefabs)
{
string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab");
foreach (string guid in prefabGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab == null) continue;
var renderers = prefab.GetComponentsInChildren<Renderer>(true);
foreach (var renderer in renderers)
{
if (renderer.sharedMaterials.Contains(targetMaterial))
{
usageList.Add(new
{
type = "Prefab",
prefab = prefab.name,
path = path,
gameObject = renderer.gameObject.name,
materialIndex = System.Array.IndexOf(renderer.sharedMaterials, targetMaterial)
});
}
}
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {usageList.Count} usage(s) of material '{targetMaterial.name}'",
materialPath = materialPath,
materialName = targetMaterial.name,
usageCount = usageList.Count,
usages = usageList
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to find material usage: {e.Message}");
}
}
/// <summary>
/// Search texture usage
/// </summary>
private string FindTextureUsage(Dictionary<string, string> parameters)
{
try
{
string texturePath = parameters.GetValueOrDefault("texturePath", "");
string textureName = parameters.GetValueOrDefault("textureName", "");
bool includeShaderProperties = parameters.GetValueOrDefault("includeShaderProperties", "true").ToLower() == "true";
Texture targetTexture = null;
// Get texture
if (!string.IsNullOrEmpty(texturePath))
{
targetTexture = AssetDatabase.LoadAssetAtPath<Texture>(texturePath);
}
else if (!string.IsNullOrEmpty(textureName))
{
string[] guids = AssetDatabase.FindAssets($"{textureName} t:Texture");
if (guids.Length > 0)
{
targetTexture = AssetDatabase.LoadAssetAtPath<Texture>(AssetDatabase.GUIDToAssetPath(guids[0]));
texturePath = AssetDatabase.GUIDToAssetPath(guids[0]);
}
}
if (targetTexture == null)
{
return CreateErrorResponse("Texture not found");
}
var usageList = new List<object>();
// Search all materials
string[] materialGuids = AssetDatabase.FindAssets("t:Material");
foreach (string guid in materialGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
Material material = AssetDatabase.LoadAssetAtPath<Material>(path);
if (material == null) continue;
// Check shader properties
Shader shader = material.shader;
var properties = new List<string>();
for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++)
{
if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv)
{
string propName = ShaderUtil.GetPropertyName(shader, i);
Texture tex = material.GetTexture(propName);
if (tex == targetTexture)
{
properties.Add(propName);
}
}
}
if (properties.Count > 0)
{
usageList.Add(new
{
material = material.name,
path = path,
shader = shader.name,
properties = includeShaderProperties ? properties.ToArray() : null
});
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {usageList.Count} material(s) using texture '{targetTexture.name}'",
texturePath = texturePath,
textureName = targetTexture.name,
usageCount = usageList.Count,
materials = usageList
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to find texture usage: {e.Message}");
}
}
/// <summary>
/// Get asset dependencies
/// </summary>
private string GetAssetDependencies(Dictionary<string, string> parameters)
{
try
{
string assetPath = parameters.GetValueOrDefault("assetPath", "");
bool recursive = parameters.GetValueOrDefault("recursive", "true").ToLower() == "true";
bool includeBuiltIn = parameters.GetValueOrDefault("includeBuiltIn", "false").ToLower() == "true";
bool groupByType = parameters.GetValueOrDefault("groupByType", "true").ToLower() == "true";
if (string.IsNullOrEmpty(assetPath))
{
return CreateErrorResponse("assetPath is required");
}
if (!System.IO.File.Exists(assetPath))
{
return CreateErrorResponse($"Asset not found: {assetPath}");
}
// Get dependencies
string[] dependencies = AssetDatabase.GetDependencies(assetPath, recursive);
var dependencyList = new List<object>();
foreach (string dep in dependencies)
{
// Exclude asset itself
if (dep == assetPath) continue;
// Exclude built-in assets
if (!includeBuiltIn && dep.StartsWith("Resources/")) continue;
var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(dep);
if (asset == null) continue;
dependencyList.Add(new
{
path = dep,
name = asset.name,
type = asset.GetType().Name,
fileSize = new System.IO.FileInfo(dep).Length
});
}
object result;
if (groupByType)
{
var grouped = dependencyList
.GroupBy(d => ((dynamic)d).type)
.Select(g => new
{
type = g.Key,
count = g.Count(),
assets = g.ToList()
})
.ToList();
result = new
{
success = true,
message = $"Found {dependencyList.Count} dependency(ies)",
assetPath = assetPath,
totalDependencies = dependencyList.Count,
grouped = grouped
};
}
else
{
result = new
{
success = true,
message = $"Found {dependencyList.Count} dependency(ies)",
assetPath = assetPath,
totalDependencies = dependencyList.Count,
dependencies = dependencyList
};
}
return JsonConvert.SerializeObject(result, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to get asset dependencies: {e.Message}");
}
}
/// <summary>
/// Detect missing references
/// </summary>
private string FindMissingReferences(Dictionary<string, string> parameters)
{
try
{
string searchPath = parameters.GetValueOrDefault("searchPath", "Assets");
bool searchInScenes = parameters.GetValueOrDefault("searchInScenes", "true").ToLower() == "true";
bool searchInPrefabs = parameters.GetValueOrDefault("searchInPrefabs", "true").ToLower() == "true";
bool includeChildren = parameters.GetValueOrDefault("includeChildren", "true").ToLower() == "true";
var missingList = new List<object>();
// Search scene
if (searchInScenes)
{
var gameObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (var go in gameObjects)
{
var components = includeChildren ?
go.GetComponentsInChildren<Component>(true) :
go.GetComponents<Component>();
foreach (var comp in components)
{
if (comp == null)
{
missingList.Add(new
{
type = "Scene",
gameObject = go.name,
path = GetGameObjectPath(go),
issue = "Missing Script"
});
}
else
{
// Check null reference with SerializedObject
SerializedObject so = new SerializedObject(comp);
SerializedProperty sp = so.GetIterator();
while (sp.NextVisible(true))
{
if (sp.propertyType == SerializedPropertyType.ObjectReference)
{
if (sp.objectReferenceValue == null && sp.objectReferenceInstanceIDValue != 0)
{
missingList.Add(new
{
type = "Scene",
gameObject = go.name,
path = GetGameObjectPath(go),
component = comp.GetType().Name,
property = sp.name,
issue = "Missing Reference"
});
}
}
}
}
}
}
}
// Search prefabs
if (searchInPrefabs)
{
string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { searchPath });
foreach (string guid in prefabGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab == null) continue;
var components = includeChildren ?
prefab.GetComponentsInChildren<Component>(true) :
prefab.GetComponents<Component>();
foreach (var comp in components)
{
if (comp == null)
{
missingList.Add(new
{
type = "Prefab",
prefab = prefab.name,
path = path,
issue = "Missing Script"
});
}
else
{
SerializedObject so = new SerializedObject(comp);
SerializedProperty sp = so.GetIterator();
while (sp.NextVisible(true))
{
if (sp.propertyType == SerializedPropertyType.ObjectReference)
{
if (sp.objectReferenceValue == null && sp.objectReferenceInstanceIDValue != 0)
{
missingList.Add(new
{
type = "Prefab",
prefab = prefab.name,
path = path,
gameObject = comp.gameObject.name,
component = comp.GetType().Name,
property = sp.name,
issue = "Missing Reference"
});
}
}
}
}
}
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {missingList.Count} missing reference(s)",
issueCount = missingList.Count,
issues = missingList
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Failed to find missing references: {e.Message}");
}
}
private string CreateAnimation(Dictionary<string, string> parameters)
{
try
{
// Use gameObject parameter as main per API definition, also support target
var targetName = parameters.GetValueOrDefault("gameObject", "") ??
parameters.GetValueOrDefault("target", "");
var animationName = parameters.GetValueOrDefault("animationName", "NewAnimation");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "1"));
var loop = parameters.GetValueOrDefault("loop", "false") == "true";
SynLog.Info($"[CreateAnimation] Called with target: '{targetName}', animationName: '{animationName}'");
if (string.IsNullOrEmpty(targetName))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "gameObject parameter is required",
receivedParameters = parameters.Keys.ToArray()
});
}
var targetObj = GetTargetGameObject(parameters);
if (targetObj == null)
{
var availableObjects = GameObject.FindObjectsOfType<GameObject>().Take(10).Select(o => o.name);
return JsonConvert.SerializeObject(new
{
success = false,
error = $"GameObject '{targetName}' not found",
availableObjects = availableObjects.ToArray(),
searchedName = targetName
});
}
// Create AnimationClip
var clip = new AnimationClip();
clip.name = animationName;
// Loop settings
if (loop)
{
var settings = AnimationUtility.GetAnimationClipSettings(clip);
settings.loopTime = true;
AnimationUtility.SetAnimationClipSettings(clip, settings);
}
// Add basic keyframes (position animation example)
var curve = AnimationCurve.Linear(0, targetObj.transform.position.y, duration, targetObj.transform.position.y + 2);
clip.SetCurve("", typeof(Transform), "localPosition.y", curve);
// Save as asset
string folderPath = "Assets/Nexus_Generated/Animations";
if (!AssetDatabase.IsValidFolder(folderPath))
{
if (!AssetDatabase.IsValidFolder("Assets/Nexus_Generated"))
{
AssetDatabase.CreateFolder("Assets", "Nexus_Generated");
}
AssetDatabase.CreateFolder("Assets/Nexus_Generated", "Animations");
}
string clipPath = $"{folderPath}/{animationName}.anim";
AssetDatabase.CreateAsset(clip, clipPath);
// Add Animator component if needed
var animator = targetObj.GetComponent<Animator>();
if (animator == null)
{
animator = targetObj.AddComponent<Animator>();
}
// Create or get AnimatorController
if (animator.runtimeAnimatorController == null)
{
var controller = UnityEditor.Animations.AnimatorController.CreateAnimatorControllerAtPath(
$"{folderPath}/{targetName}_Controller.controller"
);
animator.runtimeAnimatorController = controller;
// Add animation as default state
var rootStateMachine = controller.layers[0].stateMachine;
var state = rootStateMachine.AddState(animationName);
state.motion = clip;
rootStateMachine.defaultState = state;
}
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Animation '{animationName}' created for '{targetName}'",
clipPath = clipPath,
duration = duration,
loop = loop
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
// Helper methods
private string ColorToHex(Color color)
{
return $"#{ColorUtility.ToHtmlStringRGBA(color)}";
}
private string Vector3ToString(Vector3 v)
{
return $"({v.x:F3}, {v.y:F3}, {v.z:F3})";
}
private List<string> LayerMaskToLayers(LayerMask mask)
{
var layers = new List<string>();
for (int i = 0; i < 32; i++)
{
if ((mask.value & (1 << i)) != 0)
{
var layerName = LayerMask.LayerToName(i);
if (!string.IsNullOrEmpty(layerName))
{
layers.Add(layerName);
}
}
}
return layers;
}
private Dictionary<string, object> GetMaterialProperties(Material material)
{
var properties = new Dictionary<string, object>();
var shader = material.shader;
for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++)
{
var propName = ShaderUtil.GetPropertyName(shader, i);
var propType = ShaderUtil.GetPropertyType(shader, i);
switch (propType)
{
case ShaderUtil.ShaderPropertyType.Color:
if (material.HasProperty(propName))
properties[propName] = ColorToHex(material.GetColor(propName));
break;
case ShaderUtil.ShaderPropertyType.Float:
case ShaderUtil.ShaderPropertyType.Range:
if (material.HasProperty(propName))
properties[propName] = material.GetFloat(propName);
break;
case ShaderUtil.ShaderPropertyType.TexEnv:
if (material.HasProperty(propName))
{
var tex = material.GetTexture(propName);
properties[propName] = tex != null ? tex.name : "None";
}
break;
case ShaderUtil.ShaderPropertyType.Vector:
if (material.HasProperty(propName))
{
var vec = material.GetVector(propName);
properties[propName] = new { x = vec.x, y = vec.y, z = vec.z, w = vec.w };
}
break;
}
}
return properties;
}
private List<Dictionary<string, object>> GetUIElementsInCanvas(Canvas canvas)
{
var elements = new List<Dictionary<string, object>>();
var uiComponents = canvas.GetComponentsInChildren<Graphic>();
foreach (var component in uiComponents.Take(50)) // Max 50 items
{
var elem = new Dictionary<string, object>
{
["name"] = component.name,
["type"] = component.GetType().Name,
["enabled"] = component.enabled,
["raycastTarget"] = component.raycastTarget,
["color"] = ColorToHex(component.color)
};
// For text elements
if (component is Text text)
{
elem["text"] = text.text.Length > 100 ? text.text.Substring(0, 100) + "..." : text.text;
elem["fontSize"] = text.fontSize;
elem["font"] = text.font?.name ?? "None";
}
else if (component is TMP_Text tmpText)
{
elem["text"] = tmpText.text.Length > 100 ? tmpText.text.Substring(0, 100) + "..." : tmpText.text;
elem["fontSize"] = tmpText.fontSize;
elem["font"] = tmpText.font?.name ?? "None";
}
// For buttons
else if (component.GetComponent<Button>() != null)
{
elem["isButton"] = true;
elem["interactable"] = component.GetComponent<Button>().interactable;
}
// For images
else if (component is Image image)
{
elem["sprite"] = image.sprite?.name ?? "None";
elem["imageType"] = image.type.ToString();
elem["fillCenter"] = image.fillCenter;
}
elements.Add(elem);
}
return elements;
}
// Additional implementation: Lighting settings tools
private void ApplyLightingPreset(string preset)
{
var directionalLight = GameObject.FindObjectOfType<Light>();
if (directionalLight == null || directionalLight.type != LightType.Directional)
{
var lightGO = new GameObject("Directional Light");
directionalLight = lightGO.AddComponent<Light>();
directionalLight.type = LightType.Directional;
lightGO.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
}
switch (preset.ToLower())
{
case "studio":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.5f, 0.7f, 0.9f);
RenderSettings.ambientEquatorColor = new Color(0.4f, 0.5f, 0.7f);
RenderSettings.ambientGroundColor = new Color(0.2f, 0.3f, 0.4f);
RenderSettings.ambientIntensity = 1.2f;
directionalLight.intensity = 1f;
directionalLight.color = Color.white;
directionalLight.shadows = LightShadows.Soft;
RenderSettings.fog = false;
break;
case "sunset":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(1f, 0.4f, 0.2f);
RenderSettings.ambientEquatorColor = new Color(0.9f, 0.3f, 0.1f);
RenderSettings.ambientGroundColor = new Color(0.3f, 0.1f, 0.05f);
RenderSettings.ambientIntensity = 0.8f;
directionalLight.intensity = 0.7f;
directionalLight.color = new Color(1f, 0.7f, 0.4f);
directionalLight.shadows = LightShadows.Soft;
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(1f, 0.5f, 0.3f);
RenderSettings.fogMode = FogMode.Linear;
RenderSettings.fogStartDistance = 10f;
RenderSettings.fogEndDistance = 100f;
break;
case "night":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.05f, 0.05f, 0.15f);
RenderSettings.ambientEquatorColor = new Color(0.03f, 0.03f, 0.1f);
RenderSettings.ambientGroundColor = new Color(0.01f, 0.01f, 0.02f);
RenderSettings.ambientIntensity = 0.3f;
directionalLight.intensity = 0.2f;
directionalLight.color = new Color(0.7f, 0.7f, 1f);
directionalLight.shadows = LightShadows.Hard;
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.05f, 0.05f, 0.1f);
RenderSettings.fogMode = FogMode.Exponential;
RenderSettings.fogDensity = 0.05f;
break;
case "overcast":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.6f, 0.65f, 0.7f);
RenderSettings.ambientIntensity = 1.5f;
directionalLight.intensity = 0.4f;
directionalLight.color = new Color(0.9f, 0.9f, 0.95f);
directionalLight.shadows = LightShadows.None;
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.7f, 0.75f, 0.8f);
RenderSettings.fogMode = FogMode.Linear;
RenderSettings.fogStartDistance = 20f;
RenderSettings.fogEndDistance = 150f;
break;
case "desert":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.9f, 0.8f, 0.6f);
RenderSettings.ambientEquatorColor = new Color(0.8f, 0.7f, 0.5f);
RenderSettings.ambientGroundColor = new Color(0.6f, 0.5f, 0.3f);
RenderSettings.ambientIntensity = 1.3f;
directionalLight.intensity = 2f;
directionalLight.color = new Color(1f, 0.95f, 0.8f);
directionalLight.shadows = LightShadows.Hard;
RenderSettings.fog = false;
break;
case "forest":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.3f, 0.5f, 0.3f);
RenderSettings.ambientEquatorColor = new Color(0.2f, 0.4f, 0.2f);
RenderSettings.ambientGroundColor = new Color(0.1f, 0.2f, 0.1f);
RenderSettings.ambientIntensity = 0.7f;
directionalLight.intensity = 0.6f;
directionalLight.color = new Color(0.8f, 0.9f, 0.7f);
directionalLight.shadows = LightShadows.Soft;
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.4f, 0.5f, 0.4f);
RenderSettings.fogMode = FogMode.Exponential;
RenderSettings.fogDensity = 0.02f;
break;
case "underwater":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.1f, 0.3f, 0.5f);
RenderSettings.ambientIntensity = 0.5f;
directionalLight.intensity = 0.3f;
directionalLight.color = new Color(0.4f, 0.6f, 0.8f);
directionalLight.shadows = LightShadows.Soft;
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.1f, 0.3f, 0.5f);
RenderSettings.fogMode = FogMode.Exponential;
RenderSettings.fogDensity = 0.1f;
break;
case "space":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.05f, 0.05f, 0.1f);
RenderSettings.ambientIntensity = 0.1f;
directionalLight.intensity = 1.5f;
directionalLight.color = Color.white;
directionalLight.shadows = LightShadows.Hard;
RenderSettings.fog = false;
break;
case "neon":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.5f, 0f, 0.8f);
RenderSettings.ambientIntensity = 0.8f;
directionalLight.intensity = 0.5f;
directionalLight.color = new Color(1f, 0f, 0.5f);
directionalLight.shadows = LightShadows.None;
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.3f, 0f, 0.5f);
RenderSettings.fogMode = FogMode.Linear;
RenderSettings.fogStartDistance = 5f;
RenderSettings.fogEndDistance = 50f;
break;
}
}
private string SetupLighting(Dictionary<string, string> parameters)
{
try
{
// Check for preset first
if (parameters.TryGetValue("preset", out var preset))
{
ApplyLightingPreset(preset);
return $"Applied lighting preset: {preset}";
}
var lightingType = parameters.GetValueOrDefault("type", "standard");
var skyboxPath = parameters.GetValueOrDefault("skybox", "");
var ambientMode = parameters.GetValueOrDefault("ambientMode", "Trilight");
var ambientIntensity = float.Parse(parameters.GetValueOrDefault("ambientIntensity", "1"));
var results = new Dictionary<string, object>
{
["success"] = true,
["changes"] = new List<string>()
};
var changes = results["changes"] as List<string>;
// Set ambient mode
switch (ambientMode.ToLower())
{
case "skybox":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Skybox;
break;
case "trilight":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
break;
case "flat":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
break;
case "custom":
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Custom;
break;
}
changes.Add($"Ambient mode set to: {RenderSettings.ambientMode}");
// Ambient intensity
RenderSettings.ambientIntensity = ambientIntensity;
changes.Add($"Ambient intensity set to: {ambientIntensity}");
// Set ambient color
if (parameters.ContainsKey("ambientSkyColor"))
{
RenderSettings.ambientSkyColor = ParseColor(parameters["ambientSkyColor"]);
changes.Add($"Ambient sky color set to: {ColorToHex(RenderSettings.ambientSkyColor)}");
}
if (parameters.ContainsKey("ambientEquatorColor"))
{
RenderSettings.ambientEquatorColor = ParseColor(parameters["ambientEquatorColor"]);
changes.Add($"Ambient equator color set to: {ColorToHex(RenderSettings.ambientEquatorColor)}");
}
if (parameters.ContainsKey("ambientGroundColor"))
{
RenderSettings.ambientGroundColor = ParseColor(parameters["ambientGroundColor"]);
changes.Add($"Ambient ground color set to: {ColorToHex(RenderSettings.ambientGroundColor)}");
}
// Set skybox
if (!string.IsNullOrEmpty(skyboxPath))
{
var skyboxMaterial = AssetDatabase.LoadAssetAtPath<Material>(skyboxPath);
if (skyboxMaterial != null)
{
RenderSettings.skybox = skyboxMaterial;
changes.Add($"Skybox set to: {skyboxMaterial.name}");
}
}
// Extended ambient color settings
if (parameters.TryGetValue("ambientSkyColor", out var skyColor))
{
RenderSettings.ambientSkyColor = ParseColor(skyColor);
changes.Add($"Ambient sky color: {ColorToHex(RenderSettings.ambientSkyColor)}");
}
if (parameters.TryGetValue("ambientEquatorColor", out var equatorColor))
{
RenderSettings.ambientEquatorColor = ParseColor(equatorColor);
changes.Add($"Ambient equator color: {ColorToHex(RenderSettings.ambientEquatorColor)}");
}
if (parameters.TryGetValue("ambientGroundColor", out var groundColor))
{
RenderSettings.ambientGroundColor = ParseColor(groundColor);
changes.Add($"Ambient ground color: {ColorToHex(RenderSettings.ambientGroundColor)}");
}
// Directional light settings
var directionalLight = GameObject.FindObjectOfType<Light>();
if (directionalLight != null && directionalLight.type == LightType.Directional)
{
if (parameters.TryGetValue("directionalLightIntensity", out var intensityStr) &&
float.TryParse(intensityStr, out var intensity))
{
directionalLight.intensity = intensity;
changes.Add($"Directional light intensity: {intensity}");
}
if (parameters.TryGetValue("directionalLightColor", out var lightColor))
{
directionalLight.color = ParseColor(lightColor);
changes.Add($"Directional light color: {ColorToHex(directionalLight.color)}");
}
if (parameters.TryGetValue("directionalLightShadows", out var shadows))
{
switch (shadows.ToLower())
{
case "none":
directionalLight.shadows = LightShadows.None;
break;
case "hard":
directionalLight.shadows = LightShadows.Hard;
break;
case "soft":
directionalLight.shadows = LightShadows.Soft;
break;
}
changes.Add($"Directional light shadows: {directionalLight.shadows}");
}
}
// Fog settings
if (parameters.ContainsKey("fogEnabled"))
{
RenderSettings.fog = parameters["fogEnabled"] == "true";
changes.Add($"Fog enabled: {RenderSettings.fog}");
if (RenderSettings.fog)
{
if (parameters.ContainsKey("fogColor"))
{
RenderSettings.fogColor = ParseColor(parameters["fogColor"]);
changes.Add($"Fog color set to: {ColorToHex(RenderSettings.fogColor)}");
}
if (parameters.ContainsKey("fogDensity") &&
float.TryParse(parameters["fogDensity"], out var density))
{
RenderSettings.fogDensity = density;
changes.Add($"Fog density: {density}");
}
if (parameters.ContainsKey("fogStartDistance") &&
float.TryParse(parameters["fogStartDistance"], out var startDist))
{
RenderSettings.fogStartDistance = startDist;
changes.Add($"Fog start distance: {startDist}");
}
if (parameters.ContainsKey("fogEndDistance") &&
float.TryParse(parameters["fogEndDistance"], out var endDist))
{
RenderSettings.fogEndDistance = endDist;
changes.Add($"Fog end distance: {endDist}");
}
if (parameters.ContainsKey("fogMode"))
{
switch (parameters["fogMode"].ToLower())
{
case "linear":
RenderSettings.fogMode = FogMode.Linear;
break;
case "exponential":
RenderSettings.fogMode = FogMode.Exponential;
break;
case "exponentialsquared":
RenderSettings.fogMode = FogMode.ExponentialSquared;
break;
}
changes.Add($"Fog mode set to: {RenderSettings.fogMode}");
}
if (parameters.ContainsKey("fogDensity"))
{
RenderSettings.fogDensity = float.Parse(parameters["fogDensity"]);
changes.Add($"Fog density set to: {RenderSettings.fogDensity}");
}
if (parameters.ContainsKey("fogStartDistance"))
{
RenderSettings.fogStartDistance = float.Parse(parameters["fogStartDistance"]);
changes.Add($"Fog start distance set to: {RenderSettings.fogStartDistance}");
}
if (parameters.ContainsKey("fogEndDistance"))
{
RenderSettings.fogEndDistance = float.Parse(parameters["fogEndDistance"]);
changes.Add($"Fog end distance set to: {RenderSettings.fogEndDistance}");
}
}
}
// Set sun light source
if (parameters.ContainsKey("createSun") && parameters["createSun"] == "true")
{
var sunGO = new GameObject("Directional Light (Sun)");
var sunLight = sunGO.AddComponent<Light>();
sunLight.type = LightType.Directional;
sunLight.intensity = 1.0f;
sunLight.color = Color.white;
sunLight.shadows = LightShadows.Soft;
sunGO.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
RenderSettings.sun = sunLight;
lastCreatedObject = sunGO;
createdObjects.Add(sunGO);
changes.Add("Created sun light");
}
// Set Reflection Probe
if (parameters.ContainsKey("reflectionIntensity"))
{
RenderSettings.reflectionIntensity = float.Parse(parameters["reflectionIntensity"]);
changes.Add($"Reflection intensity set to: {RenderSettings.reflectionIntensity}");
}
results["message"] = $"Lighting setup completed with {changes.Count} changes";
return JsonConvert.SerializeObject(results, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
// Console operation tools
private string ConsoleOperation(Dictionary<string, string> parameters)
{
try
{
var operation = parameters.GetValueOrDefault("operation", "read");
var logType = parameters.GetValueOrDefault("logType", "all").ToLower();
var limit = int.Parse(parameters.GetValueOrDefault("limit", "50"));
var logEntries = System.Type.GetType("UnityEditor.LogEntries, UnityEditor");
switch (operation)
{
case "read":
// Get Unity Editor console logs
if (logEntries == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "LogEntries not available"
});
}
// Get logs from LogEntries
var getCountMethod = logEntries.GetMethod("GetCount", BindingFlags.Public | BindingFlags.Static);
var getEntryInternalMethod = logEntries.GetMethod("GetEntryInternal", BindingFlags.Public | BindingFlags.Static);
var getCountsByTypeMethod = logEntries.GetMethod("GetCountsByType", BindingFlags.Public | BindingFlags.Static);
if (getCountMethod == null || getEntryInternalMethod == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Unable to access log methods"
});
}
int totalCount = (int)getCountMethod.Invoke(null, null);
var logs = new List<Dictionary<string, object>>();
// First, get latest logs from realtime log buffer
if (logBuffer.Count > 0)
{
var recentLogs = logBuffer.OrderByDescending(l => l.timestamp).Take(limit).ToList();
foreach (var log in recentLogs)
{
string type = log.type switch
{
LogType.Error or LogType.Assert or LogType.Exception => "error",
LogType.Warning => "warning",
_ => "info"
};
if (logType == "all" || logType.ToLower() == type.ToLower())
{
logs.Add(new Dictionary<string, object>
{
["message"] = log.condition,
["type"] = type,
["stackTrace"] = log.stackTrace,
["timestamp"] = log.timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")
});
}
}
SynLog.Info($"[NexusConsole] Retrieved {logs.Count} logs from buffer (buffer size: {logBuffer.Count})");
}
// If log buffer is insufficient, try to get from LogEntries
if (logs.Count < limit)
{
// Define LogEntry structure
var logEntry = Activator.CreateInstance(System.Type.GetType("UnityEditor.LogEntry, UnityEditor"));
int startIndex = Math.Max(0, totalCount - limit);
for (int i = startIndex; i < totalCount && logs.Count < limit; i++)
{
getEntryInternalMethod.Invoke(null, new object[] { i, logEntry });
// Explicitly specify BindingFlags (try Public -> NonPublic)
var conditionField = logEntry.GetType().GetField("condition", BindingFlags.Instance | BindingFlags.Public);
var modeField = logEntry.GetType().GetField("mode", BindingFlags.Instance | BindingFlags.Public);
// If field not found, try NonPublic
if (conditionField == null)
conditionField = logEntry.GetType().GetField("condition", BindingFlags.Instance | BindingFlags.NonPublic);
if (modeField == null)
modeField = logEntry.GetType().GetField("mode", BindingFlags.Instance | BindingFlags.NonPublic);
if (conditionField != null && modeField != null)
{
string condition = (string)conditionField.GetValue(logEntry);
int mode = (int)modeField.GetValue(logEntry);
// Debug info (first item only)
if (i == startIndex && string.IsNullOrEmpty(condition))
{
SynLog.Warn($"[NexusConsole] LogEntry appears empty. Fields found: condition={conditionField != null}, mode={modeField != null}");
}
// mode: 1 = Error, 2 = Assert, 4 = Log, 8 = Fatal, 16 = DontPrefilter,
// 32 = AssetImportError, 64 = AssetImportWarning, 128 = ScriptingError,
// 256 = ScriptingWarning, 512 = ScriptingLog, 1024 = ScriptCompileError,
// 2048 = ScriptCompileWarning, 4096 = StickyError, 8192 = MayIgnoreLineNumber
string type = "info";
if ((mode & 1) != 0 || (mode & 128) != 0 || (mode & 1024) != 0) type = "error";
else if ((mode & 256) != 0 || (mode & 2048) != 0 || (mode & 64) != 0) type = "warning";
if (logType == "all" || logType.ToLower() == type.ToLower())
{
logs.Add(new Dictionary<string, object>
{
["message"] = condition,
["type"] = type,
["mode"] = mode
});
}
}
}
}
return JsonConvert.SerializeObject(new
{
success = true,
logs = logs,
count = logs.Count,
totalCount = totalCount,
filter = logType
}, Formatting.Indented);
case "clear":
if (logEntries != null)
{
var clearMethod = logEntries.GetMethod("Clear", BindingFlags.Public | BindingFlags.Static);
if (clearMethod != null)
{
clearMethod.Invoke(null, null);
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = "Console cleared"
});
case "detail":
// Get detailed information of specific log entry
var logIndex = int.Parse(parameters.GetValueOrDefault("index", "0"));
if (logEntries == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "LogEntries not available"
});
}
// Get getEntryInternalMethod
var getEntryInternalMethodForDetail = logEntries.GetMethod("GetEntryInternal", BindingFlags.Public | BindingFlags.Static);
if (getEntryInternalMethodForDetail == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Unable to access log entry method"
});
}
// Additional methods to get detailed log information
var getEntryDataMethod = logEntries.GetMethod("GetEntryData", BindingFlags.Public | BindingFlags.Static);
var getStatusTextMethod = logEntries.GetMethod("GetStatusText", BindingFlags.Public | BindingFlags.Static);
var getFirstTwoLinesMethod = logEntries.GetMethod("GetFirstTwoLinesEntryTextAndModeInternal",
BindingFlags.NonPublic | BindingFlags.Static);
var detailEntry = Activator.CreateInstance(System.Type.GetType("UnityEditor.LogEntry, UnityEditor"));
getEntryInternalMethodForDetail.Invoke(null, new object[] { logIndex, detailEntry });
var detail = new Dictionary<string, object>();
var entryType = detailEntry.GetType();
// Get all field information
foreach (var field in entryType.GetFields())
{
var value = field.GetValue(detailEntry);
detail[field.Name] = value?.ToString() ?? "null";
}
// Get additional detailed information
if (getEntryDataMethod != null)
{
try
{
var entryData = getEntryDataMethod.Invoke(null, new object[] { logIndex });
if (entryData != null)
{
detail["entryData"] = entryData.ToString();
}
}
catch { }
}
if (getStatusTextMethod != null)
{
try
{
var statusText = getStatusTextMethod.Invoke(null, null);
if (statusText != null)
{
detail["statusText"] = statusText.ToString();
}
}
catch { }
}
return JsonConvert.SerializeObject(new
{
success = true,
index = logIndex,
detail = detail
}, Formatting.Indented);
default:
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Unknown operation: {operation}"
});
}
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
/// <summary>
/// Detailed console log analysis (new method)
/// </summary>
private string AnalyzeConsoleLogs(Dictionary<string, string> parameters)
{
try
{
var operation = parameters.GetValueOrDefault("operation", "analyze");
var logType = parameters.GetValueOrDefault("logType", "error"); // Default is errors only
var limit = int.Parse(parameters.GetValueOrDefault("limit", "10")); // Default is 10 items
var includeStackTrace = parameters.GetValueOrDefault("includeStackTrace", "true") == "true";
// New filtering options for token optimization
var filter = parameters.GetValueOrDefault("filter", ""); // Include only logs matching this
var exclude = parameters.GetValueOrDefault("exclude", ""); // Exclude logs matching this (comma-separated)
var excludeSynaptic = parameters.GetValueOrDefault("excludeSynaptic", "true") == "true"; // Exclude Synaptic internal logs by default
var groupByMessage = parameters.GetValueOrDefault("groupByMessage", "false") == "true"; // Group duplicate messages
// Default exclude patterns for Synaptic internal logs
var excludePatterns = new List<string>();
if (excludeSynaptic)
{
excludePatterns.AddRange(new[] { "[Synaptic", "[Nexus", "[MCP]", "HTTP server" });
}
if (!string.IsNullOrEmpty(exclude))
{
excludePatterns.AddRange(exclude.Split(',').Select(s => s.Trim()));
}
var logEntries = System.Type.GetType("UnityEditor.LogEntries, UnityEditor");
if (logEntries == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "LogEntries not available"
});
}
// Get logs from LogEntries
var getCountMethod = logEntries.GetMethod("GetCount", BindingFlags.Public | BindingFlags.Static);
var getEntryInternalMethod = logEntries.GetMethod("GetEntryInternal", BindingFlags.Public | BindingFlags.Static);
if (getCountMethod == null || getEntryInternalMethod == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Unable to access log methods"
});
}
int totalCount = (int)getCountMethod.Invoke(null, null);
var logs = new List<Dictionary<string, object>>();
// First, get latest logs from realtime log buffer
if (logBuffer.Count > 0)
{
var filteredLogs = logBuffer.OrderByDescending(l => l.timestamp).ToList();
foreach (var log in filteredLogs)
{
string type = log.type switch
{
LogType.Error or LogType.Assert or LogType.Exception => "Error",
LogType.Warning => "Warning",
_ => "Log"
};
if (logType == "all" || logType.ToLower() == type.ToLower())
{
// Separate message and stack trace
string message = log.condition;
string stackTrace = "";
// Apply exclude filter
bool shouldExclude = excludePatterns.Any(p => message.Contains(p, StringComparison.OrdinalIgnoreCase));
if (shouldExclude) continue;
// Apply include filter
if (!string.IsNullOrEmpty(filter) && !message.Contains(filter, StringComparison.OrdinalIgnoreCase))
continue;
if (includeStackTrace && !string.IsNullOrEmpty(log.stackTrace))
{
stackTrace = log.stackTrace;
}
var logData = new Dictionary<string, object>
{
["message"] = message,
["type"] = type,
["timestamp"] = log.timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")
};
if (includeStackTrace && !string.IsNullOrEmpty(stackTrace))
{
logData["stackTrace"] = stackTrace;
}
logs.Add(logData);
if (logs.Count >= limit)
break;
}
}
}
// If log buffer is insufficient, try to get from LogEntries
if (logs.Count < limit)
{
// Define LogEntry structure
var logEntryType = System.Type.GetType("UnityEditor.LogEntry, UnityEditor");
var logEntry = Activator.CreateInstance(logEntryType);
// Must call StartGettingEntries before GetEntryInternal
var startGettingEntriesMethod = logEntries.GetMethod("StartGettingEntries", BindingFlags.Public | BindingFlags.Static);
var endGettingEntriesMethod = logEntries.GetMethod("EndGettingEntries", BindingFlags.Public | BindingFlags.Static);
try
{
// Start getting entries
startGettingEntriesMethod?.Invoke(null, null);
int startIndex = Math.Max(0, totalCount - limit * 3); // Scan more to get needed count
for (int i = startIndex; i < totalCount && logs.Count < limit; i++)
{
getEntryInternalMethod.Invoke(null, new object[] { i, logEntry });
// Get all fields (specify BindingFlags)
var conditionField = logEntryType.GetField("condition", BindingFlags.Instance | BindingFlags.Public);
var modeField = logEntryType.GetField("mode", BindingFlags.Instance | BindingFlags.Public);
var fileField = logEntryType.GetField("file", BindingFlags.Instance | BindingFlags.Public);
var lineField = logEntryType.GetField("line", BindingFlags.Instance | BindingFlags.Public);
var columnField = logEntryType.GetField("column", BindingFlags.Instance | BindingFlags.Public);
var instanceIDField = logEntryType.GetField("instanceID", BindingFlags.Instance | BindingFlags.Public);
if (conditionField != null && modeField != null)
{
string condition = (string)conditionField.GetValue(logEntry) ?? "";
int mode = (int)modeField.GetValue(logEntry);
string file = fileField?.GetValue(logEntry)?.ToString() ?? "";
int line = lineField != null ? (int)lineField.GetValue(logEntry) : 0;
int column = columnField != null ? (int)columnField.GetValue(logEntry) : 0;
int instanceID = instanceIDField != null ? (int)instanceIDField.GetValue(logEntry) : 0;
string type = "Log";
if ((mode & 1) != 0 || (mode & 128) != 0 || (mode & 1024) != 0) type = "Error";
else if ((mode & 256) != 0 || (mode & 2048) != 0 || (mode & 64) != 0) type = "Warning";
if (logType == "all" || logType.ToLower() == type.ToLower())
{
// Separate message and stack trace
string message = condition;
string stackTrace = "";
int stackTraceIndex = condition.IndexOf("\n");
if (stackTraceIndex > 0)
{
message = condition.Substring(0, stackTraceIndex);
if (includeStackTrace)
{
stackTrace = condition.Substring(stackTraceIndex + 1);
}
}
// Apply exclude filter
bool shouldExclude = excludePatterns.Any(p => message.Contains(p, StringComparison.OrdinalIgnoreCase));
if (shouldExclude) continue;
// Apply include filter
if (!string.IsNullOrEmpty(filter) && !message.Contains(filter, StringComparison.OrdinalIgnoreCase))
continue;
var logData = new Dictionary<string, object>
{
["message"] = message,
["type"] = type,
["file"] = file,
["line"] = line,
["column"] = column
};
if (includeStackTrace && !string.IsNullOrEmpty(stackTrace))
{
logData["stackTrace"] = stackTrace;
}
logs.Add(logData);
}
}
}
}
finally
{
// Always call EndGettingEntries to release resources
endGettingEntriesMethod?.Invoke(null, null);
}
}
// Apply groupByMessage if enabled
var outputLogs = logs;
if (groupByMessage && logs.Count > 0)
{
var grouped = logs
.GroupBy(l => (string)l["message"])
.Select(g => {
var first = g.First();
first["count"] = g.Count();
return first;
})
.OrderByDescending(l => (int)l["count"])
.ToList();
outputLogs = grouped;
}
// Generate analysis results
var analysis = new Dictionary<string, object>
{
["totalLogs"] = logs.Count,
["uniqueMessages"] = groupByMessage ? outputLogs.Count : logs.Count,
["errors"] = logs.Count(l => (string)l["type"] == "Error"),
["warnings"] = logs.Count(l => (string)l["type"] == "Warning"),
["messages"] = logs.Count(l => (string)l["type"] == "Log")
};
// Aggregate errors by file (only if file key exists)
var fileErrors = logs.Where(l => l.ContainsKey("file") && !string.IsNullOrEmpty((string)l["file"]))
.GroupBy(l => (string)l["file"])
.Select(g => new { File = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count)
.Take(5)
.ToList();
return JsonConvert.SerializeObject(new
{
success = true,
logs = outputLogs,
analysis = analysis,
topErrorFiles = fileErrors,
filter = logType,
appliedFilters = new {
excludeSynaptic = excludeSynaptic,
filterPattern = filter,
excludePatterns = excludePatterns,
groupByMessage = groupByMessage
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Error analyzing console logs: {e.Message}"
});
}
}
// Object search tools
private string SearchObjects(Dictionary<string, string> parameters)
{
try
{
var searchType = parameters.GetValueOrDefault("searchType", "name");
var query = parameters.GetValueOrDefault("query", "");
var includeInactive = parameters.GetValueOrDefault("includeInactive", "false") == "true";
var exactMatch = parameters.GetValueOrDefault("exactMatch", "false") == "true";
if (string.IsNullOrEmpty(query))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "query parameter is required"
});
}
var results = new List<Dictionary<string, object>>();
IEnumerable<GameObject> allObjects;
if (includeInactive)
{
// Exclude prefabs/assets from search results
allObjects = Resources.FindObjectsOfTypeAll<GameObject>()
.Where(go => !UnityEditor.EditorUtility.IsPersistent(go) && go.scene.IsValid() && go.scene.isLoaded);
}
else
{
allObjects = GameObject.FindObjectsOfType<GameObject>();
}
foreach (var obj in allObjects)
{
bool matches = false;
switch (searchType)
{
case "name":
if (exactMatch)
matches = obj.name == query;
else
matches = obj.name.Contains(query, StringComparison.OrdinalIgnoreCase);
break;
case "tag":
matches = obj.CompareTag(query);
break;
case "layer":
int layerIndex = LayerMask.NameToLayer(query);
if (layerIndex >= 0)
matches = obj.layer == layerIndex;
break;
case "component":
// Search by component name
var components = obj.GetComponents<Component>();
foreach (var comp in components)
{
if (comp != null)
{
var compTypeName = comp.GetType().Name;
if (exactMatch)
matches = compTypeName == query;
else
matches = compTypeName.Contains(query, StringComparison.OrdinalIgnoreCase);
if (matches) break;
}
}
break;
}
if (matches)
{
// Check if object is in scene
bool isSceneObject = !string.IsNullOrEmpty(obj.scene.name);
// Exclude prefabs and editor-only objects
if (!isSceneObject && !includeInactive)
continue;
var objInfo = new Dictionary<string, object>
{
["name"] = obj.name,
["path"] = GetFullPath(obj),
["tag"] = obj.tag,
["layer"] = LayerMask.LayerToName(obj.layer),
["active"] = obj.activeSelf,
["activeInHierarchy"] = obj.activeInHierarchy,
["position"] = new {
x = obj.transform.position.x,
y = obj.transform.position.y,
z = obj.transform.position.z
},
["rotation"] = new {
x = obj.transform.eulerAngles.x,
y = obj.transform.eulerAngles.y,
z = obj.transform.eulerAngles.z
},
["scale"] = new {
x = obj.transform.localScale.x,
y = obj.transform.localScale.y,
z = obj.transform.localScale.z
},
["components"] = obj.GetComponents<Component>()
.Where(c => c != null)
.Select(c => c.GetType().Name)
.ToList(),
["childCount"] = obj.transform.childCount,
["scene"] = obj.scene.name
};
results.Add(objInfo);
// Limit result count
if (results.Count >= 100)
break;
}
}
return JsonConvert.SerializeObject(new
{
success = true,
results = results,
count = results.Count,
searchType = searchType,
query = query,
includeInactive = includeInactive,
exactMatch = exactMatch,
message = results.Count >= 100 ? "Results limited to 100 items" : null
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
// Chat and messaging related tools
private string SendChatResponse(Dictionary<string, string> parameters)
{
try
{
var messageId = parameters.GetValueOrDefault("messageId", "");
var response = parameters.GetValueOrDefault("response", "");
if (string.IsNullOrEmpty(messageId) || string.IsNullOrEmpty(response))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "messageId and response parameters are required"
});
}
// Send response to Unity messaging system
// This can be extended based on actual project implementation
var timestamp = System.DateTime.Now;
// Log message to console
SynLog.Info($"[AI Response] {response}");
return JsonConvert.SerializeObject(new
{
success = true,
message = "Response sent successfully",
messageId = messageId,
response = response,
timestamp = timestamp.ToString("yyyy-MM-dd HH:mm:ss"),
type = "chat_response"
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string CheckMessages(Dictionary<string, string> parameters)
{
try
{
// Check Unity message queue
// In actual implementation, integrate with message queue system
var messages = new List<Dictionary<string, object>>();
// Demo messages (actually retrieved from queue)
// In actual implementation, retrieve from system like NexusMessageQueue
return JsonConvert.SerializeObject(new
{
success = true,
messages = messages,
count = messages.Count,
hasNewMessages = messages.Count > 0,
timestamp = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string SendRealtimeResponse(Dictionary<string, string> parameters)
{
try
{
var sessionId = parameters.GetValueOrDefault("sessionId", "");
var type = parameters.GetValueOrDefault("type", "info");
var content = parameters.GetValueOrDefault("content", "");
var metadata = parameters.GetValueOrDefault("metadata", "{}");
if (string.IsNullOrEmpty(sessionId) || string.IsNullOrEmpty(content))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "sessionId and content parameters are required"
});
}
// Send realtime response
var timestamp = System.DateTime.Now;
// Process based on type
switch (type.ToLower())
{
case "info":
SynLog.Info($"[RT Info] {content}");
break;
case "warning":
SynLog.Warn($"[RT Warning] {content}");
break;
case "error":
Debug.LogError($"[RT Error] {content}");
break;
case "success":
SynLog.Info($"[RT Success] {content}");
break;
}
// Parse metadata
Dictionary<string, object> meta = null;
try
{
meta = JsonConvert.DeserializeObject<Dictionary<string, object>>(metadata);
}
catch
{
meta = new Dictionary<string, object>();
}
return JsonConvert.SerializeObject(new
{
success = true,
message = "Realtime response sent",
sessionId = sessionId,
type = type,
content = content,
metadata = meta,
timestamp = timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff"),
delivered = true
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
private string CheckActiveSessions(Dictionary<string, string> parameters)
{
try
{
// Check active sessions
var sessions = new List<Dictionary<string, object>>();
// Current Unity session information
var currentSession = new Dictionary<string, object>
{
["sessionId"] = System.Guid.NewGuid().ToString(),
["type"] = "unity_editor",
["status"] = "active",
["startTime"] = Application.isPlaying ?
Time.realtimeSinceStartup.ToString() : "editor_mode",
["isPlaying"] = Application.isPlaying,
["isPaused"] = EditorApplication.isPaused,
["scene"] = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name,
["platform"] = Application.platform.ToString(),
["unityVersion"] = Application.unityVersion
};
sessions.Add(currentSession);
// WebSocket session information (if connected)
// Use reflection to access Editor-only classes
try
{
var editorMCPServiceType = System.Type.GetType("NexusAIConnect.NexusEditorMCPService, Assembly-CSharp-Editor");
if (editorMCPServiceType != null)
{
var isConnectedProp = editorMCPServiceType.GetProperty("IsConnected", BindingFlags.Public | BindingFlags.Static);
var getServerUrlMethod = editorMCPServiceType.GetMethod("GetServerUrl", BindingFlags.Public | BindingFlags.Static);
if (isConnectedProp != null && getServerUrlMethod != null)
{
bool isConnected = (bool)isConnectedProp.GetValue(null);
if (isConnected)
{
string serverUrl = (string)getServerUrlMethod.Invoke(null, null);
var wsSession = new Dictionary<string, object>
{
["sessionId"] = "websocket_session",
["type"] = "websocket",
["status"] = "connected",
["serverUrl"] = serverUrl,
["connected"] = true
};
sessions.Add(wsSession);
}
}
}
}
catch (Exception ex)
{
SynLog.Warn($"Failed to get WebSocket session info: {ex.Message}");
}
return JsonConvert.SerializeObject(new
{
success = true,
sessions = sessions,
count = sessions.Count,
activeCount = sessions.Count(s => (string)s["status"] == "active" || (string)s["status"] == "connected"),
timestamp = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
// Helper methods: Get full path of GameObject
private string GetFullPath(GameObject obj)
{
if (obj == null) return "";
string path = obj.name;
Transform parent = obj.transform.parent;
while (parent != null)
{
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
// Helper methods: Create folder if not exists
private void CreateFolderIfNotExists(string folderPath)
{
if (!folderPath.StartsWith("Assets/"))
{
folderPath = "Assets/" + folderPath.TrimStart('/');
}
if (!AssetDatabase.IsValidFolder(folderPath))
{
string[] pathParts = folderPath.Split('/');
string currentPath = pathParts[0]; // "Assets"
for (int i = 1; i < pathParts.Length; i++)
{
string newFolder = pathParts[i];
string nextPath = currentPath + "/" + newFolder;
if (!AssetDatabase.IsValidFolder(nextPath))
{
AssetDatabase.CreateFolder(currentPath, newFolder);
}
currentPath = nextPath;
}
}
}
// Folder management methods
private string CheckFolder(Dictionary<string, string> parameters)
{
try
{
var folderPath = parameters.GetValueOrDefault("folderPath", "");
if (string.IsNullOrEmpty(folderPath))
return JsonConvert.SerializeObject(new { success = false, error = "folderPath parameter is required" });
// Normalize to relative path from Assets folder
if (!folderPath.StartsWith("Assets/"))
{
folderPath = "Assets/" + folderPath.TrimStart('/');
}
bool exists = AssetDatabase.IsValidFolder(folderPath);
string fullPath = System.IO.Path.Combine(Application.dataPath.Replace("/Assets", ""), folderPath);
return JsonConvert.SerializeObject(new
{
success = true,
exists = exists,
folderPath = folderPath,
fullPath = fullPath,
isAssetFolder = folderPath.StartsWith("Assets/")
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string CreateFolder(Dictionary<string, string> parameters)
{
try
{
// Use path parameter as main per API definition, also support folderPath
var folderPath = parameters.GetValueOrDefault("path", "") ??
parameters.GetValueOrDefault("folderPath", "");
if (string.IsNullOrEmpty(folderPath))
return CreateMissingParameterResponse("CreateFolder", "path", parameters);
// Normalize to relative path from Assets folder
if (!folderPath.StartsWith("Assets/"))
{
folderPath = "Assets/" + folderPath.TrimStart('/');
}
// Check if already exists
if (AssetDatabase.IsValidFolder(folderPath))
{
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Folder already exists: {folderPath}",
folderPath = folderPath,
created = false
}, Formatting.Indented);
}
// Create sequentially from parent folder
string[] pathParts = folderPath.Split('/');
string currentPath = pathParts[0]; // "Assets"
for (int i = 1; i < pathParts.Length; i++)
{
string nextPath = currentPath + "/" + pathParts[i];
if (!AssetDatabase.IsValidFolder(nextPath))
{
string guid = AssetDatabase.CreateFolder(currentPath, pathParts[i]);
if (string.IsNullOrEmpty(guid))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Failed to create folder: {nextPath}"
});
}
}
currentPath = nextPath;
}
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Folder created successfully: {folderPath}",
folderPath = folderPath,
created = true
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string ListFolders(Dictionary<string, string> parameters)
{
try
{
var rootPath = parameters.GetValueOrDefault("rootPath", "Assets");
var includeEmpty = bool.Parse(parameters.GetValueOrDefault("includeEmpty", "true"));
if (!rootPath.StartsWith("Assets"))
{
rootPath = "Assets/" + rootPath.TrimStart('/');
}
var folders = new List<Dictionary<string, object>>();
// Get folders using AssetDatabase
string[] allFolders = AssetDatabase.GetSubFolders(rootPath);
foreach (string folder in allFolders)
{
var folderInfo = new Dictionary<string, object>
{
["name"] = System.IO.Path.GetFileName(folder),
["path"] = folder,
["hasSubFolders"] = AssetDatabase.GetSubFolders(folder).Length > 0
};
// Count assets in folder
string[] guids = AssetDatabase.FindAssets("", new[] { folder });
folderInfo["assetCount"] = guids.Length;
if (includeEmpty || guids.Length > 0)
{
folders.Add(folderInfo);
}
}
return JsonConvert.SerializeObject(new
{
success = true,
rootPath = rootPath,
folders = folders,
count = folders.Count
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
// New tool group methods
private string DuplicateGameObject(Dictionary<string, string> parameters)
{
try
{
var target = GetTargetGameObject(parameters);
if (target == null)
{
string targetName = parameters.GetValueOrDefault("target") ??
parameters.GetValueOrDefault("name") ??
parameters.GetValueOrDefault("object") ?? "unknown";
return JsonConvert.SerializeObject(new { success = false, error = $"GameObject '{targetName}' not found" });
}
var newName = parameters.GetValueOrDefault("newName", target.name + " (Copy)");
var duplicateCount = int.Parse(parameters.GetValueOrDefault("count", "1"));
var offsetX = float.Parse(parameters.GetValueOrDefault("offsetX", "1"));
var offsetY = float.Parse(parameters.GetValueOrDefault("offsetY", "0"));
var offsetZ = float.Parse(parameters.GetValueOrDefault("offsetZ", "0"));
var duplicatedObjects = new List<string>();
for (int i = 0; i < duplicateCount; i++)
{
var duplicate = UnityEngine.Object.Instantiate(target);
duplicate.name = duplicateCount > 1 ? $"{newName} ({i + 1})" : newName;
// Apply position offset
duplicate.transform.position = target.transform.position + new Vector3(
offsetX * (i + 1),
offsetY * (i + 1),
offsetZ * (i + 1)
);
duplicatedObjects.Add(duplicate.name);
lastCreatedObject = duplicate;
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Duplicated '{target.name}' {duplicateCount} time(s)",
duplicatedObjects = duplicatedObjects,
lastCreated = lastCreatedObject?.name
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string FindGameObjectsByComponent(Dictionary<string, string> parameters)
{
try
{
var componentType = parameters.GetValueOrDefault("componentType", "");
if (string.IsNullOrEmpty(componentType))
return JsonConvert.SerializeObject(new { success = false, error = "componentType parameter is required" });
var includeInactive = bool.Parse(parameters.GetValueOrDefault("includeInactive", "false"));
var results = new List<Dictionary<string, object>>();
// Get component type
var type = GetComponentType(componentType);
if (type == null)
return JsonConvert.SerializeObject(new { success = false, error = $"Component type '{componentType}' not found" });
// Search from all GameObjects in scene (exclude prefabs/assets)
var allObjects = includeInactive ?
Resources.FindObjectsOfTypeAll<GameObject>().Where(go =>
!UnityEditor.EditorUtility.IsPersistent(go) && go.scene.IsValid() && go.scene.isLoaded) :
UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (var obj in allObjects)
{
if (obj.GetComponent(type) != null)
{
results.Add(new Dictionary<string, object>
{
["name"] = obj.name,
["path"] = GetFullPath(obj),
["active"] = obj.activeInHierarchy,
["position"] = new { x = obj.transform.position.x, y = obj.transform.position.y, z = obj.transform.position.z },
["componentCount"] = obj.GetComponents(type).Length
});
}
}
return JsonConvert.SerializeObject(new
{
success = true,
componentType = componentType,
foundObjects = results,
count = results.Count,
includeInactive = includeInactive
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string CleanupEmptyObjects(Dictionary<string, string> parameters)
{
try
{
var includeInactive = bool.Parse(parameters.GetValueOrDefault("includeInactive", "true"));
var dryRun = bool.Parse(parameters.GetValueOrDefault("dryRun", "false"));
var removedObjects = new List<string>();
var allObjects = includeInactive ?
Resources.FindObjectsOfTypeAll<GameObject>().Where(go =>
!UnityEditor.EditorUtility.IsPersistent(go) && go.scene.IsValid() && go.scene.isLoaded) :
UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (var obj in allObjects.ToArray())
{
// Check empty object conditions
bool isEmpty = obj.GetComponents<Component>().Length <= 1 && // Transform only
obj.transform.childCount == 0; // No child objects
if (isEmpty)
{
removedObjects.Add(GetFullPath(obj));
if (!dryRun)
{
UnityEditor.Undo.DestroyObjectImmediate(obj);
}
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = dryRun ? "Dry run completed - no objects were actually removed" : $"Removed {removedObjects.Count} empty objects",
removedObjects = removedObjects,
count = removedObjects.Count,
dryRun = dryRun
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string GroupGameObjects(Dictionary<string, string> parameters)
{
try
{
// Support multiple parameter names
var names = parameters.GetValueOrDefault("names") ??
parameters.GetValueOrDefault("objects") ??
parameters.GetValueOrDefault("targets") ??
parameters.GetValueOrDefault("gameObjects") ??
"";
var parentName = parameters.GetValueOrDefault("parentName") ??
parameters.GetValueOrDefault("groupName") ??
parameters.GetValueOrDefault("parent") ??
"Group";
var maintainWorldPosition = bool.Parse(parameters.GetValueOrDefault("maintainWorldPosition", "true"));
SynLog.Info($"[GroupGameObjects] names parameter: '{names}'");
SynLog.Info($"[GroupGameObjects] parentName: '{parentName}'");
if (string.IsNullOrEmpty(names))
return JsonConvert.SerializeObject(new {
success = false,
error = "names/objects/targets parameter is required",
availableParams = string.Join(", ", parameters.Keys)
});
string[] gameObjectNames;
// Support JSON array or comma-separated string
if (names.StartsWith("[") && names.EndsWith("]"))
{
try
{
// Parse as JSON array
gameObjectNames = JsonConvert.DeserializeObject<string[]>(names);
SynLog.Info($"[GroupGameObjects] Parsed JSON array: {string.Join(", ", gameObjectNames)}");
}
catch (Exception ex)
{
Debug.LogError($"[GroupGameObjects] Failed to parse JSON array: {ex.Message}");
return JsonConvert.SerializeObject(new {
success = false,
error = $"Invalid JSON array format: {ex.Message}",
receivedValue = names
});
}
}
else
{
// Parse as comma-separated string
gameObjectNames = names.Split(',').Select(n => n.Trim()).ToArray();
SynLog.Info($"[GroupGameObjects] Parsed comma-separated: {string.Join(", ", gameObjectNames)}");
}
var gameObjects = new List<GameObject>();
var notFound = new List<string>();
// Search for objects
foreach (var name in gameObjectNames)
{
// Use GetTargetGameObject logic
var dummyParams = new Dictionary<string, string> { {"target", name} };
var obj = GetTargetGameObject(dummyParams);
if (obj != null)
{
gameObjects.Add(obj);
}
else
{
notFound.Add(name);
}
}
if (gameObjects.Count == 0)
return JsonConvert.SerializeObject(new {
success = false,
error = "No GameObjects found",
notFound = notFound
});
// Create parent object
var parentObject = new GameObject(parentName);
// Calculate center position of group
Vector3 centerPosition = Vector3.zero;
foreach (var obj in gameObjects)
{
centerPosition += obj.transform.position;
}
centerPosition /= gameObjects.Count;
parentObject.transform.position = centerPosition;
// Set objects to parent
foreach (var obj in gameObjects)
{
obj.transform.SetParent(parentObject.transform, maintainWorldPosition);
}
// Set as selected
Selection.activeGameObject = parentObject;
EditorGUIUtility.PingObject(parentObject);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Grouped {gameObjects.Count} objects under '{parentName}'",
groupedObjects = gameObjects.Select(o => o.name).ToArray(),
notFound = notFound,
parentObject = new
{
name = parentObject.name,
position = new {
x = parentObject.transform.position.x,
y = parentObject.transform.position.y,
z = parentObject.transform.position.z
},
childCount = parentObject.transform.childCount
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string RenameAsset(Dictionary<string, string> parameters)
{
try
{
// Use oldPath parameter as main according to API definition, also support assetPath
var assetPath = parameters.GetValueOrDefault("oldPath", "") ??
parameters.GetValueOrDefault("assetPath", "");
var newName = parameters.GetValueOrDefault("newName", "");
if (string.IsNullOrEmpty(assetPath) || string.IsNullOrEmpty(newName))
return CreateMissingParameterResponse("RenameAsset", "oldPath and newName", parameters);
// Add if not a relative path from Assets
if (!assetPath.StartsWith("Assets"))
assetPath = $"Assets/{assetPath}";
var asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
if (asset == null)
return JsonConvert.SerializeObject(new {
success = false,
error = $"Asset not found at path: {assetPath}"
});
// Preserve extension
var extension = System.IO.Path.GetExtension(assetPath);
if (!newName.EndsWith(extension))
newName = newName + extension;
// Execute rename
var errorMessage = AssetDatabase.RenameAsset(assetPath, System.IO.Path.GetFileNameWithoutExtension(newName));
if (!string.IsNullOrEmpty(errorMessage))
return JsonConvert.SerializeObject(new {
success = false,
error = errorMessage
});
AssetDatabase.Refresh();
var newPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(assetPath), newName);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Renamed asset to '{newName}'",
oldPath = assetPath,
newPath = newPath
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string MoveAsset(Dictionary<string, string> parameters)
{
try
{
// Use sourcePath, destinationFolder parameters as main according to API definition, also support legacy parameters
var assetPath = parameters.GetValueOrDefault("sourcePath", "") ??
parameters.GetValueOrDefault("assetPath", "");
var targetFolder = parameters.GetValueOrDefault("destinationFolder", "") ??
parameters.GetValueOrDefault("targetFolder", "");
if (string.IsNullOrEmpty(assetPath) || string.IsNullOrEmpty(targetFolder))
return CreateMissingParameterResponse("MoveAsset", "sourcePath and destinationFolder", parameters);
// Normalize path
if (!assetPath.StartsWith("Assets"))
assetPath = $"Assets/{assetPath}";
if (!targetFolder.StartsWith("Assets"))
targetFolder = $"Assets/{targetFolder}";
// Verify asset exists
if (!AssetDatabase.LoadMainAssetAtPath(assetPath))
return JsonConvert.SerializeObject(new {
success = false,
error = $"Asset not found: {assetPath}"
});
// Create target folder
CreateFolderIfNotExists(targetFolder);
var fileName = System.IO.Path.GetFileName(assetPath);
var newPath = System.IO.Path.Combine(targetFolder, fileName);
// Execute move
var errorMessage = AssetDatabase.MoveAsset(assetPath, newPath);
if (!string.IsNullOrEmpty(errorMessage))
return JsonConvert.SerializeObject(new {
success = false,
error = errorMessage
});
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Moved asset to '{targetFolder}'",
oldPath = assetPath,
newPath = newPath
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string DeleteAsset(Dictionary<string, string> parameters)
{
try
{
var assetPath = parameters.GetValueOrDefault("assetPath", "");
var includeMetaFile = bool.Parse(parameters.GetValueOrDefault("includeMetaFile", "true"));
if (string.IsNullOrEmpty(assetPath))
return JsonConvert.SerializeObject(new { success = false, error = "assetPath parameter is required" });
// Normalize path
if (!assetPath.StartsWith("Assets"))
assetPath = $"Assets/{assetPath}";
// Verify asset exists
var asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
if (asset == null)
return JsonConvert.SerializeObject(new {
success = false,
error = $"Asset not found: {assetPath}"
});
var assetType = asset.GetType().Name;
var isFolder = AssetDatabase.IsValidFolder(assetPath);
// Register to UNDO before deletion
if (asset != null)
{
UnityEditor.Undo.RegisterCompleteObjectUndo(asset, $"Delete Asset {assetPath}");
}
// Execute delete
bool success = AssetDatabase.DeleteAsset(assetPath);
if (!success)
return JsonConvert.SerializeObject(new {
success = false,
error = "Failed to delete asset"
});
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = isFolder ? $"Deleted folder: {assetPath}" : $"Deleted asset: {assetPath}",
deletedPath = assetPath,
assetType = assetType,
isFolder = isFolder
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string PauseScene(Dictionary<string, string> parameters)
{
try
{
var action = parameters.GetValueOrDefault("action", "toggle"); // pause, unpause, toggle
bool newPauseState;
switch (action.ToLower())
{
case "pause":
newPauseState = true;
break;
case "unpause":
case "resume":
newPauseState = false;
break;
case "toggle":
newPauseState = !EditorApplication.isPaused;
break;
default:
return JsonConvert.SerializeObject(new {
success = false,
error = $"Invalid action: {action}. Use 'pause', 'unpause', or 'toggle'"
});
}
// If not in Play Mode
if (!EditorApplication.isPlaying)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Scene is not playing. Cannot pause in Edit Mode",
currentState = "EditMode"
});
}
EditorApplication.isPaused = newPauseState;
return JsonConvert.SerializeObject(new
{
success = true,
message = newPauseState ? "Scene paused" : "Scene resumed",
isPaused = EditorApplication.isPaused,
isPlaying = EditorApplication.isPlaying,
action = action
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
// ===== Advanced features for production =====
private string OptimizeTexturesBatch(Dictionary<string, string> parameters)
{
try
{
var folder = parameters.GetValueOrDefault("folder", "Assets");
var maxTextureSize = int.Parse(parameters.GetValueOrDefault("maxTextureSize", "2048"));
var compressQuality = parameters.GetValueOrDefault("compressQuality", "normal"); // normal, high, low
var generateMipmaps = bool.Parse(parameters.GetValueOrDefault("generateMipmaps", "true"));
var makeReadable = bool.Parse(parameters.GetValueOrDefault("makeReadable", "false"));
var optimizedTextures = new List<Dictionary<string, object>>();
var failedTextures = new List<Dictionary<string, object>>();
long totalSizeBefore = 0;
long totalSizeAfter = 0;
// Search for textures in folder
var textureGUIDs = AssetDatabase.FindAssets("t:Texture2D", new[] { folder });
foreach (var guid in textureGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (texture != null)
{
try
{
// Get file size
var fileInfo = new System.IO.FileInfo(path);
var sizeBefore = fileInfo.Length;
totalSizeBefore += sizeBefore;
// TextureImporter settings
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer != null)
{
// Save settings before changes
var originalSettings = new Dictionary<string, object>
{
["maxTextureSize"] = importer.maxTextureSize,
["textureCompression"] = importer.textureCompression.ToString(),
["mipmapEnabled"] = importer.mipmapEnabled,
["isReadable"] = importer.isReadable
};
// Apply optimization settings
importer.maxTextureSize = maxTextureSize;
importer.mipmapEnabled = generateMipmaps;
importer.isReadable = makeReadable;
// Compression quality settings
switch (compressQuality.ToLower())
{
case "low":
importer.textureCompression = TextureImporterCompression.CompressedLQ;
break;
case "high":
importer.textureCompression = TextureImporterCompression.CompressedHQ;
break;
default:
importer.textureCompression = TextureImporterCompression.Compressed;
break;
}
// Platform-specific settings
var platformSettings = importer.GetDefaultPlatformTextureSettings();
platformSettings.maxTextureSize = maxTextureSize;
platformSettings.format = TextureImporterFormat.Automatic;
platformSettings.textureCompression = importer.textureCompression;
importer.SetPlatformTextureSettings(platformSettings);
// Reimport
importer.SaveAndReimport();
// Size after optimization
fileInfo.Refresh();
var sizeAfter = fileInfo.Length;
totalSizeAfter += sizeAfter;
optimizedTextures.Add(new Dictionary<string, object>
{
["path"] = path,
["name"] = texture.name,
["originalSize"] = sizeBefore,
["optimizedSize"] = sizeAfter,
["reduction"] = sizeBefore - sizeAfter,
["reductionPercent"] = Math.Round((1 - (double)sizeAfter / sizeBefore) * 100, 2),
["originalSettings"] = originalSettings,
["newSettings"] = new
{
maxTextureSize = maxTextureSize,
compression = compressQuality,
mipmaps = generateMipmaps,
readable = makeReadable
}
});
}
}
catch (Exception ex)
{
failedTextures.Add(new Dictionary<string, object>
{
["path"] = path,
["name"] = texture.name,
["error"] = ex.Message
});
}
}
}
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Optimized {optimizedTextures.Count} textures",
optimizedTextures = optimizedTextures,
failedTextures = failedTextures,
summary = new
{
totalTextures = optimizedTextures.Count + failedTextures.Count,
optimizedCount = optimizedTextures.Count,
failedCount = failedTextures.Count,
totalSizeBefore = totalSizeBefore,
totalSizeAfter = totalSizeAfter,
totalReduction = totalSizeBefore - totalSizeAfter,
reductionPercent = totalSizeBefore > 0 ? Math.Round((1 - (double)totalSizeAfter / totalSizeBefore) * 100, 2) : 0
},
settings = new
{
folder = folder,
maxTextureSize = maxTextureSize,
compressQuality = compressQuality,
generateMipmaps = generateMipmaps,
makeReadable = makeReadable
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string AnalyzeDrawCalls(Dictionary<string, string> parameters)
{
try
{
var includeInactive = bool.Parse(parameters.GetValueOrDefault("includeInactive", "false"));
var groupByMaterial = bool.Parse(parameters.GetValueOrDefault("groupByMaterial", "true"));
var groupByShader = bool.Parse(parameters.GetValueOrDefault("groupByShader", "true"));
var rendererInfo = new List<Dictionary<string, object>>();
var materialUsage = new Dictionary<Material, List<string>>();
var shaderUsage = new Dictionary<Shader, List<string>>();
// Get all Renderers in scene (exclude prefabs/assets in memory)
Renderer[] renderers;
if (includeInactive)
{
renderers = Resources.FindObjectsOfTypeAll<Renderer>()
.Where(r => !UnityEditor.EditorUtility.IsPersistent(r) &&
r.gameObject.scene.IsValid() &&
r.gameObject.scene.isLoaded)
.ToArray();
}
else
{
renderers = UnityEngine.Object.FindObjectsOfType<Renderer>();
}
// Collect information for each Renderer
foreach (var renderer in renderers)
{
if (renderer == null) continue;
var materials = renderer.sharedMaterials.Where(m => m != null).ToArray();
var meshFilter = renderer.GetComponent<MeshFilter>();
var mesh = meshFilter != null ? meshFilter.sharedMesh : null;
var info = new Dictionary<string, object>
{
["name"] = renderer.name,
["path"] = GetFullPath(renderer.gameObject),
["type"] = renderer.GetType().Name,
["enabled"] = renderer.enabled,
["materialCount"] = materials.Length,
["materials"] = materials.Select(m => new
{
name = m.name,
shader = m.shader.name,
renderQueue = m.renderQueue,
passCount = m.passCount
}).ToArray()
};
if (mesh != null)
{
info["mesh"] = new
{
name = mesh.name,
vertexCount = mesh.vertexCount,
triangleCount = mesh.triangles.Length / 3,
submeshCount = mesh.subMeshCount
};
}
// If SkinnedMeshRenderer
if (renderer is SkinnedMeshRenderer skinnedRenderer)
{
info["boneCount"] = skinnedRenderer.bones.Length;
info["quality"] = skinnedRenderer.quality.ToString();
}
rendererInfo.Add(info);
// Record Material usage
foreach (var mat in materials)
{
if (!materialUsage.ContainsKey(mat))
materialUsage[mat] = new List<string>();
materialUsage[mat].Add(GetFullPath(renderer.gameObject));
// Record Shader usage
if (!shaderUsage.ContainsKey(mat.shader))
shaderUsage[mat.shader] = new List<string>();
shaderUsage[mat.shader].Add(GetFullPath(renderer.gameObject));
}
}
// Predict Camera draw calls
var cameras = UnityEngine.Object.FindObjectsOfType<Camera>();
var cameraDrawCalls = new List<Dictionary<string, object>>();
foreach (var camera in cameras)
{
cameraDrawCalls.Add(new Dictionary<string, object>
{
["name"] = camera.name,
["enabled"] = camera.enabled,
["cullingMask"] = LayerMaskToString(camera.cullingMask),
["renderingPath"] = camera.renderingPath.ToString(),
["targetDisplay"] = camera.targetDisplay
});
}
// Batching optimization suggestions
var optimizationSuggestions = new List<string>();
// If multiple objects use the same Material
var batchableMaterials = materialUsage.Where(kvp => kvp.Value.Count > 1);
foreach (var kvp in batchableMaterials)
{
optimizationSuggestions.Add($"Material '{kvp.Key.name}' is used by {kvp.Value.Count} objects - consider static/dynamic batching");
}
// If Renderer uses many Materials
var multiMaterialRenderers = rendererInfo.Where(r => (int)r["materialCount"] > 2);
if (multiMaterialRenderers.Any())
{
optimizationSuggestions.Add($"{multiMaterialRenderers.Count()} renderers use more than 2 materials - consider texture atlasing");
}
return JsonConvert.SerializeObject(new
{
success = true,
message = "Draw call analysis completed",
summary = new
{
totalRenderers = rendererInfo.Count,
activeRenderers = rendererInfo.Count(r => (bool)r["enabled"]),
uniqueMaterials = materialUsage.Count,
uniqueShaders = shaderUsage.Count,
estimatedDrawCalls = rendererInfo.Sum(r => (int)r["materialCount"]),
cameraCount = cameraDrawCalls.Count
},
renderers = rendererInfo,
materialUsage = groupByMaterial ? materialUsage.Select(kvp => new
{
material = kvp.Key.name,
shader = kvp.Key.shader.name,
usageCount = kvp.Value.Count,
usedBy = kvp.Value
}) : null,
shaderUsage = groupByShader ? shaderUsage.Select(kvp => new
{
shader = kvp.Key.name,
usageCount = kvp.Value.Count,
usedBy = kvp.Value
}) : null,
cameras = cameraDrawCalls,
optimizationSuggestions = optimizationSuggestions
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string CreateProjectSnapshot(Dictionary<string, string> parameters)
{
try
{
var snapshotName = parameters.GetValueOrDefault("name", $"snapshot_{DateTime.Now:yyyyMMdd_HHmmss}");
var includeScenes = bool.Parse(parameters.GetValueOrDefault("includeScenes", "true"));
var includePrefabs = bool.Parse(parameters.GetValueOrDefault("includePrefabs", "true"));
var includeMaterials = bool.Parse(parameters.GetValueOrDefault("includeMaterials", "true"));
var includeScripts = bool.Parse(parameters.GetValueOrDefault("includeScripts", "true"));
var includeProjectSettings = bool.Parse(parameters.GetValueOrDefault("includeProjectSettings", "true"));
var snapshot = new Dictionary<string, object>
{
["name"] = snapshotName,
["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
["unityVersion"] = Application.unityVersion,
["projectName"] = Application.productName,
["platform"] = Application.platform.ToString()
};
// Scene information
if (includeScenes)
{
var scenes = new List<Dictionary<string, object>>();
var sceneGUIDs = AssetDatabase.FindAssets("t:Scene");
foreach (var guid in sceneGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var sceneName = System.IO.Path.GetFileNameWithoutExtension(path);
scenes.Add(new Dictionary<string, object>
{
["name"] = sceneName,
["path"] = path,
["guid"] = guid,
["isActive"] = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == sceneName
});
}
snapshot["scenes"] = scenes;
}
// Prefab information
if (includePrefabs)
{
var prefabs = new List<Dictionary<string, object>>();
var prefabGUIDs = AssetDatabase.FindAssets("t:Prefab");
foreach (var guid in prefabGUIDs.Take(100)) // Limit if large quantity
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab != null)
{
prefabs.Add(new Dictionary<string, object>
{
["name"] = prefab.name,
["path"] = path,
["guid"] = guid,
["componentCount"] = prefab.GetComponents<Component>().Length,
["childCount"] = prefab.transform.childCount
});
}
}
snapshot["prefabs"] = new
{
count = prefabGUIDs.Length,
samples = prefabs
};
}
// Material information
if (includeMaterials)
{
var materials = new List<Dictionary<string, object>>();
var materialGUIDs = AssetDatabase.FindAssets("t:Material");
foreach (var guid in materialGUIDs.Take(50))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var material = AssetDatabase.LoadAssetAtPath<Material>(path);
if (material != null)
{
materials.Add(new Dictionary<string, object>
{
["name"] = material.name,
["path"] = path,
["shader"] = material.shader.name,
["renderQueue"] = material.renderQueue
});
}
}
snapshot["materials"] = new
{
count = materialGUIDs.Length,
samples = materials
};
}
// Script information
if (includeScripts)
{
var scripts = new List<Dictionary<string, object>>();
var scriptGUIDs = AssetDatabase.FindAssets("t:MonoScript");
foreach (var guid in scriptGUIDs.Take(50))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var script = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
if (script != null)
{
scripts.Add(new Dictionary<string, object>
{
["name"] = script.name,
["path"] = path,
["className"] = script.GetClass()?.FullName ?? "Unknown"
});
}
}
snapshot["scripts"] = new
{
count = scriptGUIDs.Length,
samples = scripts
};
}
// Project settings
if (includeProjectSettings)
{
snapshot["projectSettings"] = new
{
companyName = PlayerSettings.companyName,
productName = PlayerSettings.productName,
applicationIdentifier = PlayerSettings.applicationIdentifier,
defaultInterfaceOrientation = PlayerSettings.defaultInterfaceOrientation.ToString(),
colorSpace = PlayerSettings.colorSpace.ToString(),
apiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(EditorUserBuildSettings.selectedBuildTargetGroup).ToString()
};
}
// Save snapshot in JSON format
var snapshotJson = JsonConvert.SerializeObject(snapshot, Formatting.Indented);
var snapshotPath = $"Assets/Snapshots/{snapshotName}.json";
// Create Snapshots folder
CreateFolderIfNotExists("Assets/Snapshots");
// Save to file
System.IO.File.WriteAllText(snapshotPath, snapshotJson);
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Project snapshot created: {snapshotName}",
snapshotPath = snapshotPath,
snapshotSize = new System.IO.FileInfo(snapshotPath).Length,
contents = new
{
scenes = includeScenes,
prefabs = includePrefabs,
materials = includeMaterials,
scripts = includeScripts,
projectSettings = includeProjectSettings
},
summary = snapshot
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
// Helper methods: Convert LayerMask to string
private string LayerMaskToString(int mask)
{
var layers = new List<string>();
for (int i = 0; i < 32; i++)
{
if ((mask & (1 << i)) != 0)
{
var layerName = LayerMask.LayerToName(i);
if (!string.IsNullOrEmpty(layerName))
layers.Add(layerName);
}
}
return string.Join(", ", layers);
}
private string AnalyzeDependencies(Dictionary<string, string> parameters)
{
try
{
var assetPath = parameters.GetValueOrDefault("assetPath", "");
var maxDepth = int.Parse(parameters.GetValueOrDefault("maxDepth", "5"));
var includeScripts = bool.Parse(parameters.GetValueOrDefault("includeScripts", "true"));
var includeTextures = bool.Parse(parameters.GetValueOrDefault("includeTextures", "true"));
var includeMaterials = bool.Parse(parameters.GetValueOrDefault("includeMaterials", "true"));
var includePrefabs = bool.Parse(parameters.GetValueOrDefault("includePrefabs", "true"));
var dependencies = new Dictionary<string, List<string>>();
var dependencyTree = new Dictionary<string, object>();
var processed = new HashSet<string>();
// Analyze dependencies for specific asset or entire project
if (!string.IsNullOrEmpty(assetPath))
{
AnalyzeAssetDependencies(assetPath, dependencies, dependencyTree, processed, 0, maxDepth);
}
else
{
// Analyze major asset types
if (includeScripts)
{
var scripts = AssetDatabase.FindAssets("t:MonoScript");
foreach (var guid in scripts.Take(50))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
AnalyzeAssetDependencies(path, dependencies, dependencyTree, processed, 0, maxDepth);
}
}
if (includePrefabs)
{
var prefabs = AssetDatabase.FindAssets("t:Prefab");
foreach (var guid in prefabs.Take(30))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
AnalyzeAssetDependencies(path, dependencies, dependencyTree, processed, 0, maxDepth);
}
}
if (includeMaterials)
{
var materials = AssetDatabase.FindAssets("t:Material");
foreach (var guid in materials.Take(30))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
AnalyzeAssetDependencies(path, dependencies, dependencyTree, processed, 0, maxDepth);
}
}
}
// Detect circular dependencies
var circularDependencies = DetectCircularDependencies(dependencies);
// Dependency statistics
var stats = new Dictionary<string, object>
{
["totalAssets"] = dependencies.Count,
["totalDependencies"] = dependencies.Sum(kvp => kvp.Value.Count),
["averageDependencies"] = dependencies.Count > 0 ? dependencies.Average(kvp => kvp.Value.Count) : 0,
["maxDependencies"] = dependencies.Count > 0 ? dependencies.Max(kvp => kvp.Value.Count) : 0,
["assetsWithNoDependencies"] = dependencies.Count(kvp => kvp.Value.Count == 0),
["circularDependencies"] = circularDependencies.Count
};
// Most referenced assets
var referenceCounts = new Dictionary<string, int>();
foreach (var deps in dependencies.Values)
{
foreach (var dep in deps)
{
if (!referenceCounts.ContainsKey(dep))
referenceCounts[dep] = 0;
referenceCounts[dep]++;
}
}
var mostReferenced = referenceCounts
.OrderByDescending(kvp => kvp.Value)
.Take(10)
.Select(kvp => new { asset = kvp.Key, referenceCount = kvp.Value })
.ToList();
return JsonConvert.SerializeObject(new
{
success = true,
message = "Dependency analysis completed",
assetPath = assetPath,
dependencyTree = dependencyTree,
statistics = stats,
mostReferencedAssets = mostReferenced,
circularDependencies = circularDependencies,
settings = new
{
maxDepth = maxDepth,
includeScripts = includeScripts,
includeTextures = includeTextures,
includeMaterials = includeMaterials,
includePrefabs = includePrefabs
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private void AnalyzeAssetDependencies(string assetPath, Dictionary<string, List<string>> dependencies,
Dictionary<string, object> tree, HashSet<string> processed, int currentDepth, int maxDepth)
{
if (currentDepth > maxDepth || processed.Contains(assetPath))
return;
processed.Add(assetPath);
var deps = AssetDatabase.GetDependencies(assetPath, false);
if (!dependencies.ContainsKey(assetPath))
dependencies[assetPath] = new List<string>();
var treeNode = new Dictionary<string, object>
{
["type"] = AssetDatabase.GetMainAssetTypeAtPath(assetPath)?.Name ?? "Unknown",
["dependencies"] = new List<Dictionary<string, object>>()
};
foreach (var dep in deps)
{
if (dep != assetPath)
{
dependencies[assetPath].Add(dep);
var depNode = new Dictionary<string, object>
{
["path"] = dep,
["type"] = AssetDatabase.GetMainAssetTypeAtPath(dep)?.Name ?? "Unknown"
};
((List<Dictionary<string, object>>)treeNode["dependencies"]).Add(depNode);
// Analyze dependencies recursively
AnalyzeAssetDependencies(dep, dependencies, tree, processed, currentDepth + 1, maxDepth);
}
}
tree[assetPath] = treeNode;
}
private List<List<string>> DetectCircularDependencies(Dictionary<string, List<string>> dependencies)
{
var circularDeps = new List<List<string>>();
var visited = new HashSet<string>();
var recursionStack = new HashSet<string>();
var path = new List<string>();
foreach (var asset in dependencies.Keys)
{
if (!visited.Contains(asset))
{
DetectCircularDependenciesUtil(asset, dependencies, visited, recursionStack, path, circularDeps);
}
}
return circularDeps;
}
private bool DetectCircularDependenciesUtil(string asset, Dictionary<string, List<string>> dependencies,
HashSet<string> visited, HashSet<string> recursionStack, List<string> path, List<List<string>> circularDeps)
{
visited.Add(asset);
recursionStack.Add(asset);
path.Add(asset);
if (dependencies.ContainsKey(asset))
{
foreach (var dep in dependencies[asset])
{
if (!visited.Contains(dep))
{
if (DetectCircularDependenciesUtil(dep, dependencies, visited, recursionStack, path, circularDeps))
return true;
}
else if (recursionStack.Contains(dep))
{
// Detect circular dependencies
var cycleStart = path.IndexOf(dep);
var cycle = path.Skip(cycleStart).ToList();
cycle.Add(dep); // Complete the cycle
circularDeps.Add(cycle);
}
}
}
path.RemoveAt(path.Count - 1);
recursionStack.Remove(asset);
return false;
}
private string ExportProjectStructure(Dictionary<string, string> parameters)
{
try
{
var outputFormat = parameters.GetValueOrDefault("format", "json"); // json, csv, tree
var includeFolders = bool.Parse(parameters.GetValueOrDefault("includeFolders", "true"));
var includeFileSize = bool.Parse(parameters.GetValueOrDefault("includeFileSize", "true"));
var maxDepth = int.Parse(parameters.GetValueOrDefault("maxDepth", "10"));
var projectStructure = new Dictionary<string, object>();
var rootPath = "Assets";
// Build project structure recursively
projectStructure = BuildProjectStructure(rootPath, 0, maxDepth, includeFileSize);
// Statistics by file type
var fileStats = new Dictionary<string, int>();
var totalSize = 0L;
CountFileTypes(projectStructure, fileStats, ref totalSize);
var result = new Dictionary<string, object>
{
["projectName"] = PlayerSettings.productName,
["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
["structure"] = projectStructure,
["statistics"] = new
{
fileTypes = fileStats.OrderByDescending(kvp => kvp.Value)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
totalFiles = fileStats.Sum(kvp => kvp.Value),
totalSize = totalSize,
totalSizeMB = Math.Round(totalSize / (1024.0 * 1024.0), 2)
}
};
// Convert according to output format
string output;
string fileName;
switch (outputFormat.ToLower())
{
case "csv":
output = ConvertToCSV(projectStructure);
fileName = $"project_structure_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
break;
case "tree":
output = ConvertToTreeFormat(projectStructure);
fileName = $"project_structure_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
break;
default:
output = JsonConvert.SerializeObject(result, Formatting.Indented);
fileName = $"project_structure_{DateTime.Now:yyyyMMdd_HHmmss}.json";
break;
}
// Save to file
var outputPath = $"Assets/ProjectAnalysis/{fileName}";
CreateFolderIfNotExists("Assets/ProjectAnalysis");
System.IO.File.WriteAllText(outputPath, output);
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Project structure exported as {outputFormat}",
outputPath = outputPath,
fileSize = new System.IO.FileInfo(outputPath).Length,
format = outputFormat,
summary = result["statistics"]
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string ExportPackage(Dictionary<string, string> parameters)
{
#if UNITY_EDITOR
try
{
var assetPaths = parameters.GetValueOrDefault("assetPaths", ""); // Comma-separated asset paths
var outputPath = parameters.GetValueOrDefault("outputPath", ""); // Full output path including .unitypackage
var includeDependencies = parameters.GetValueOrDefault("includeDependencies", "true") == "true";
if (string.IsNullOrEmpty(assetPaths))
{
return JsonConvert.SerializeObject(new { success = false, error = "assetPaths parameter is required" });
}
if (string.IsNullOrEmpty(outputPath))
{
// Generate default output path
var projectName = PlayerSettings.productName;
outputPath = System.IO.Path.Combine(
System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop),
$"{projectName}_{DateTime.Now:yyyyMMdd_HHmmss}.unitypackage"
);
}
// Ensure .unitypackage extension
if (!outputPath.EndsWith(".unitypackage"))
{
outputPath += ".unitypackage";
}
// Parse asset paths (comma-separated)
var paths = assetPaths.Split(',')
.Select(p => p.Trim())
.Where(p => !string.IsNullOrEmpty(p))
.ToArray();
// Verify all paths exist
var invalidPaths = paths.Where(p => !AssetDatabase.IsValidFolder(p) && string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(p))).ToList();
if (invalidPaths.Any())
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Invalid asset paths: {string.Join(", ", invalidPaths)}"
});
}
// Build export options
var exportOptions = ExportPackageOptions.Recurse;
if (includeDependencies)
{
exportOptions |= ExportPackageOptions.IncludeDependencies;
}
// Export package
AssetDatabase.ExportPackage(paths, outputPath, exportOptions);
// Verify export succeeded
if (!System.IO.File.Exists(outputPath))
{
return JsonConvert.SerializeObject(new { success = false, error = "Export failed - output file not created" });
}
var fileInfo = new System.IO.FileInfo(outputPath);
return JsonConvert.SerializeObject(new
{
success = true,
message = "Package exported successfully",
outputPath = outputPath,
fileSize = fileInfo.Length,
fileSizeMB = Math.Round(fileInfo.Length / (1024.0 * 1024.0), 2),
assetPaths = paths,
includeDependencies = includeDependencies
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = $"Export failed: {e.Message}" });
}
#else
return JsonConvert.SerializeObject(new { success = false, error = "This feature is only available in the Unity Editor" });
#endif
}
private Dictionary<string, object> BuildProjectStructure(string path, int currentDepth, int maxDepth, bool includeFileSize)
{
var structure = new Dictionary<string, object>
{
["name"] = System.IO.Path.GetFileName(path),
["path"] = path,
["type"] = AssetDatabase.IsValidFolder(path) ? "folder" : "file"
};
if ((string)structure["type"] == "folder" && currentDepth < maxDepth)
{
var children = new List<Dictionary<string, object>>();
var subFolders = AssetDatabase.GetSubFolders(path);
foreach (var folder in subFolders)
{
children.Add(BuildProjectStructure(folder, currentDepth + 1, maxDepth, includeFileSize));
}
var files = System.IO.Directory.GetFiles(path)
.Where(f => !f.EndsWith(".meta"))
.Select(f => f.Replace('\\', '/'));
foreach (var file in files)
{
var fileInfo = new Dictionary<string, object>
{
["name"] = System.IO.Path.GetFileName(file),
["path"] = file,
["type"] = "file",
["extension"] = System.IO.Path.GetExtension(file)
};
if (includeFileSize)
{
var fi = new System.IO.FileInfo(file);
fileInfo["size"] = fi.Length;
fileInfo["sizeMB"] = Math.Round(fi.Length / (1024.0 * 1024.0), 2);
}
children.Add(fileInfo);
}
structure["children"] = children;
structure["childCount"] = children.Count;
}
return structure;
}
private void CountFileTypes(Dictionary<string, object> structure, Dictionary<string, int> fileStats, ref long totalSize)
{
if ((string)structure["type"] == "file")
{
var ext = structure.ContainsKey("extension") ? (string)structure["extension"] : "";
if (!string.IsNullOrEmpty(ext))
{
if (!fileStats.ContainsKey(ext))
fileStats[ext] = 0;
fileStats[ext]++;
}
if (structure.ContainsKey("size"))
totalSize += (long)structure["size"];
}
else if (structure.ContainsKey("children"))
{
foreach (var child in (List<Dictionary<string, object>>)structure["children"])
{
CountFileTypes(child, fileStats, ref totalSize);
}
}
}
private string ConvertToCSV(Dictionary<string, object> structure)
{
var csv = new System.Text.StringBuilder();
csv.AppendLine("Path,Type,Size(bytes),Extension");
AddToCSV(structure, csv);
return csv.ToString();
}
private void AddToCSV(Dictionary<string, object> structure, System.Text.StringBuilder csv)
{
var path = structure["path"].ToString();
var type = structure["type"].ToString();
var size = structure.ContainsKey("size") ? structure["size"].ToString() : "";
var ext = structure.ContainsKey("extension") ? structure["extension"].ToString() : "";
csv.AppendLine($"\"{path}\",{type},{size},{ext}");
if (structure.ContainsKey("children"))
{
foreach (var child in (List<Dictionary<string, object>>)structure["children"])
{
AddToCSV(child, csv);
}
}
}
private string ConvertToTreeFormat(Dictionary<string, object> structure, string indent = "")
{
var tree = new System.Text.StringBuilder();
tree.AppendLine($"{indent}{structure["name"]}");
if (structure.ContainsKey("children"))
{
var children = (List<Dictionary<string, object>>)structure["children"];
for (int i = 0; i < children.Count; i++)
{
var isLast = i == children.Count - 1;
var childIndent = indent + (isLast ? "└── " : "├── ");
var nextIndent = indent + (isLast ? " " : "│ ");
var childTree = ConvertToTreeFormat(children[i], childIndent);
// Replace only the first occurrence
var index = childTree.IndexOf(childIndent);
if (index >= 0)
{
childTree = childTree.Substring(0, index) + nextIndent + childTree.Substring(index + childIndent.Length);
}
tree.Append(childTree);
}
}
return tree.ToString();
}
private string ValidateNamingConventions(Dictionary<string, string> parameters)
{
try
{
var checkPascalCase = bool.Parse(parameters.GetValueOrDefault("checkPascalCase", "true"));
var checkCamelCase = bool.Parse(parameters.GetValueOrDefault("checkCamelCase", "true"));
var checkSnakeCase = bool.Parse(parameters.GetValueOrDefault("checkSnakeCase", "false"));
var checkPrefixes = bool.Parse(parameters.GetValueOrDefault("checkPrefixes", "true"));
var customRules = parameters.GetValueOrDefault("customRules", "");
var violations = new List<Dictionary<string, object>>();
var statistics = new Dictionary<string, int>
{
["totalAssets"] = 0,
["pascalCaseViolations"] = 0,
["camelCaseViolations"] = 0,
["prefixViolations"] = 0,
["customRuleViolations"] = 0
};
// Check script files
var scripts = AssetDatabase.FindAssets("t:MonoScript");
foreach (var guid in scripts)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var script = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
if (script != null)
{
statistics["totalAssets"]++;
var fileName = System.IO.Path.GetFileNameWithoutExtension(path);
var scriptViolations = new List<string>();
// Check PascalCase (class name)
if (checkPascalCase && !IsPascalCase(fileName))
{
scriptViolations.Add("Class name should be PascalCase");
statistics["pascalCaseViolations"]++;
}
// Check prefix
if (checkPrefixes)
{
if (fileName.StartsWith("I") && fileName.Length > 1 && char.IsUpper(fileName[1]))
{
// Interfaces are allowed
}
else if (fileName.StartsWith("_"))
{
scriptViolations.Add("Script names should not start with underscore");
statistics["prefixViolations"]++;
}
}
if (scriptViolations.Count > 0)
{
violations.Add(new Dictionary<string, object>
{
["path"] = path,
["name"] = fileName,
["type"] = "Script",
["violations"] = scriptViolations
});
}
}
}
// Check prefabs
var prefabs = AssetDatabase.FindAssets("t:Prefab");
foreach (var guid in prefabs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var prefabName = System.IO.Path.GetFileNameWithoutExtension(path);
statistics["totalAssets"]++;
var prefabViolations = new List<string>();
// Check PascalCase
if (checkPascalCase && !IsPascalCase(prefabName))
{
prefabViolations.Add("Prefab name should be PascalCase");
statistics["pascalCaseViolations"]++;
}
if (prefabViolations.Count > 0)
{
violations.Add(new Dictionary<string, object>
{
["path"] = path,
["name"] = prefabName,
["type"] = "Prefab",
["violations"] = prefabViolations
});
}
}
// Recommended fixes
var recommendations = new List<string>();
if (statistics["pascalCaseViolations"] > 0)
recommendations.Add("Use PascalCase for class names and prefabs (e.g., PlayerController, EnemySpawner)");
if (statistics["camelCaseViolations"] > 0)
recommendations.Add("Use camelCase for variables and methods (e.g., playerHealth, moveSpeed)");
if (statistics["prefixViolations"] > 0)
recommendations.Add("Avoid using underscores at the beginning of names");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {violations.Count} naming convention violations",
violations = violations,
statistics = statistics,
recommendations = recommendations,
settings = new
{
checkPascalCase = checkPascalCase,
checkCamelCase = checkCamelCase,
checkSnakeCase = checkSnakeCase,
checkPrefixes = checkPrefixes
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private bool IsPascalCase(string name)
{
if (string.IsNullOrEmpty(name)) return false;
// First character is uppercase
if (!char.IsUpper(name[0])) return false;
// Does not start with digit or underscore
if (char.IsDigit(name[0]) || name[0] == '_') return false;
// No consecutive underscores
if (name.Contains("__")) return false;
return true;
}
private bool IsCamelCase(string name)
{
if (string.IsNullOrEmpty(name)) return false;
// First character is lowercase
if (!char.IsLower(name[0])) return false;
// Does not start with digit or underscore
if (char.IsDigit(name[0]) || name[0] == '_') return false;
return true;
}
private string ExtractAllText(Dictionary<string, string> parameters)
{
try
{
var includeScripts = bool.Parse(parameters.GetValueOrDefault("includeScripts", "true"));
var includeUI = bool.Parse(parameters.GetValueOrDefault("includeUI", "true"));
var includeComments = bool.Parse(parameters.GetValueOrDefault("includeComments", "true"));
var outputFormat = parameters.GetValueOrDefault("format", "json"); // json, txt, csv
var extractedText = new Dictionary<string, List<Dictionary<string, object>>>();
var allTexts = new List<string>();
// Extract text from scripts
if (includeScripts)
{
var scriptTexts = new List<Dictionary<string, object>>();
var scripts = AssetDatabase.FindAssets("t:MonoScript");
foreach (var guid in scripts.Take(100))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var content = System.IO.File.ReadAllText(path);
// Extract string literals
var stringLiterals = ExtractStringLiterals(content);
if (stringLiterals.Count > 0)
{
scriptTexts.Add(new Dictionary<string, object>
{
["path"] = path,
["fileName"] = System.IO.Path.GetFileName(path),
["strings"] = stringLiterals,
["count"] = stringLiterals.Count
});
allTexts.AddRange(stringLiterals);
}
// Extract comments
if (includeComments)
{
var comments = ExtractComments(content);
if (comments.Count > 0)
{
scriptTexts.Add(new Dictionary<string, object>
{
["path"] = path,
["fileName"] = System.IO.Path.GetFileName(path),
["comments"] = comments,
["count"] = comments.Count
});
allTexts.AddRange(comments);
}
}
}
extractedText["scripts"] = scriptTexts;
}
// Extract UI text
if (includeUI)
{
var uiTexts = new List<Dictionary<string, object>>();
// Text component
var textComponents = UnityEngine.Object.FindObjectsOfType<UnityEngine.UI.Text>(true);
foreach (var text in textComponents)
{
if (!string.IsNullOrEmpty(text.text))
{
uiTexts.Add(new Dictionary<string, object>
{
["gameObject"] = text.gameObject.name,
["path"] = GetFullPath(text.gameObject),
["text"] = text.text,
["font"] = text.font != null ? text.font.name : "None",
["fontSize"] = text.fontSize
});
allTexts.Add(text.text);
}
}
// TextMeshPro component (obtained via reflection)
var tmpType = System.Type.GetType("TMPro.TextMeshProUGUI, Unity.TextMeshPro");
if (tmpType != null)
{
var tmpComponents = UnityEngine.Object.FindObjectsOfType(tmpType, true);
foreach (var tmp in tmpComponents)
{
var textProperty = tmpType.GetProperty("text");
if (textProperty != null)
{
var textValue = textProperty.GetValue(tmp) as string;
if (!string.IsNullOrEmpty(textValue))
{
var go = (tmp as Component).gameObject;
uiTexts.Add(new Dictionary<string, object>
{
["gameObject"] = go.name,
["path"] = GetFullPath(go),
["text"] = textValue,
["type"] = "TextMeshPro"
});
allTexts.Add(textValue);
}
}
}
}
extractedText["ui"] = uiTexts;
}
// Statistics
var statistics = new Dictionary<string, object>
{
["totalTexts"] = allTexts.Count,
["uniqueTexts"] = allTexts.Distinct().Count(),
["totalCharacters"] = allTexts.Sum(t => t.Length),
["averageLength"] = allTexts.Count > 0 ? allTexts.Average(t => t.Length) : 0,
["languages"] = DetectLanguages(allTexts)
};
// Save according to output format
string outputPath;
switch (outputFormat.ToLower())
{
case "txt":
var txtContent = string.Join("\n", allTexts.Distinct());
outputPath = $"Assets/ExtractedTexts/all_texts_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
CreateFolderIfNotExists("Assets/ExtractedTexts");
System.IO.File.WriteAllText(outputPath, txtContent);
break;
case "csv":
var csvContent = "Type,Source,Text\n";
foreach (var kvp in extractedText)
{
foreach (var item in kvp.Value)
{
if (item.ContainsKey("strings"))
{
foreach (var str in (List<string>)item["strings"])
{
csvContent += $"\"Script\",\"{item["path"]}\",\"{str.Replace("\"", "\"\"")}\"\n";
}
}
else if (item.ContainsKey("text"))
{
csvContent += $"\"UI\",\"{item["path"]}\",\"{item["text"].ToString().Replace("\"", "\"\"")}\"\n";
}
}
}
outputPath = $"Assets/ExtractedTexts/all_texts_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
CreateFolderIfNotExists("Assets/ExtractedTexts");
System.IO.File.WriteAllText(outputPath, csvContent);
break;
default:
var jsonContent = JsonConvert.SerializeObject(new
{
extractedText = extractedText,
statistics = statistics,
allTexts = allTexts.Distinct().ToList()
}, Formatting.Indented);
outputPath = $"Assets/ExtractedTexts/all_texts_{DateTime.Now:yyyyMMdd_HHmmss}.json";
CreateFolderIfNotExists("Assets/ExtractedTexts");
System.IO.File.WriteAllText(outputPath, jsonContent);
break;
}
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = "Text extraction completed",
outputPath = outputPath,
format = outputFormat,
statistics = statistics,
preview = allTexts.Distinct().Take(10).ToList()
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private List<string> ExtractStringLiterals(string code)
{
var strings = new List<string>();
var pattern = @"""([^""\\]|\\.)*""|'([^'\\]|\\.)*'";
var matches = System.Text.RegularExpressions.Regex.Matches(code, pattern);
foreach (System.Text.RegularExpressions.Match match in matches)
{
var value = match.Value.Trim('"', '\'');
if (!string.IsNullOrWhiteSpace(value) && value.Length > 1)
{
strings.Add(value);
}
}
return strings;
}
private List<string> ExtractComments(string code)
{
var comments = new List<string>();
// Single-line comments
var singleLinePattern = @"//.*$";
var singleLineMatches = System.Text.RegularExpressions.Regex.Matches(code, singleLinePattern, System.Text.RegularExpressions.RegexOptions.Multiline);
foreach (System.Text.RegularExpressions.Match match in singleLineMatches)
{
var comment = match.Value.Substring(2).Trim();
if (!string.IsNullOrWhiteSpace(comment))
comments.Add(comment);
}
// Multi-line comments
var multiLinePattern = @"/\*[\s\S]*?\*/";
var multiLineMatches = System.Text.RegularExpressions.Regex.Matches(code, multiLinePattern);
foreach (System.Text.RegularExpressions.Match match in multiLineMatches)
{
var comment = match.Value.Substring(2, match.Value.Length - 4).Trim();
if (!string.IsNullOrWhiteSpace(comment))
comments.Add(comment);
}
return comments;
}
private Dictionary<string, int> DetectLanguages(List<string> texts)
{
var languages = new Dictionary<string, int>
{
["English"] = 0,
["Japanese"] = 0,
["Numbers"] = 0,
["Other"] = 0
};
foreach (var text in texts)
{
if (System.Text.RegularExpressions.Regex.IsMatch(text, @"[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]"))
languages["Japanese"]++;
else if (System.Text.RegularExpressions.Regex.IsMatch(text, @"^[a-zA-Z\s]+$"))
languages["English"]++;
else if (System.Text.RegularExpressions.Regex.IsMatch(text, @"^\d+$"))
languages["Numbers"]++;
else
languages["Other"]++;
}
return languages;
}
// ===== Additional tools for AI =====
private string BatchRename(Dictionary<string, string> parameters)
{
try
{
var searchPattern = parameters.GetValueOrDefault("searchPattern", "");
var replacePattern = parameters.GetValueOrDefault("replacePattern", "");
var assetType = parameters.GetValueOrDefault("assetType", "all"); // all, prefab, material, texture, script
var useRegex = bool.Parse(parameters.GetValueOrDefault("useRegex", "false"));
var caseSensitive = bool.Parse(parameters.GetValueOrDefault("caseSensitive", "true"));
var dryRun = bool.Parse(parameters.GetValueOrDefault("dryRun", "false"));
if (string.IsNullOrEmpty(searchPattern))
return JsonConvert.SerializeObject(new { success = false, error = "searchPattern is required" });
var renamedAssets = new List<Dictionary<string, object>>();
var failedAssets = new List<Dictionary<string, object>>();
// Filter based on asset type
string filter = assetType.ToLower() switch
{
"prefab" => "t:Prefab",
"material" => "t:Material",
"texture" => "t:Texture2D",
"script" => "t:MonoScript",
_ => ""
};
var guids = AssetDatabase.FindAssets(filter);
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var fileName = System.IO.Path.GetFileNameWithoutExtension(path);
var extension = System.IO.Path.GetExtension(path);
string newName;
if (useRegex)
{
var regexOptions = caseSensitive ?
System.Text.RegularExpressions.RegexOptions.None :
System.Text.RegularExpressions.RegexOptions.IgnoreCase;
newName = System.Text.RegularExpressions.Regex.Replace(
fileName, searchPattern, replacePattern, regexOptions);
}
else
{
if (caseSensitive)
{
newName = fileName.Replace(searchPattern, replacePattern);
}
else
{
// Process for case-insensitive comparison
var regex = new System.Text.RegularExpressions.Regex(
System.Text.RegularExpressions.Regex.Escape(searchPattern),
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
newName = regex.Replace(fileName, replacePattern);
}
}
if (newName != fileName)
{
try
{
if (!dryRun)
{
var errorMessage = AssetDatabase.RenameAsset(path, newName);
if (!string.IsNullOrEmpty(errorMessage))
throw new Exception(errorMessage);
}
renamedAssets.Add(new Dictionary<string, object>
{
["path"] = path,
["oldName"] = fileName,
["newName"] = newName,
["type"] = AssetDatabase.GetMainAssetTypeAtPath(path)?.Name ?? "Unknown"
});
}
catch (Exception ex)
{
failedAssets.Add(new Dictionary<string, object>
{
["path"] = path,
["oldName"] = fileName,
["newName"] = newName,
["error"] = ex.Message
});
}
}
}
if (!dryRun)
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = dryRun ?
$"Dry run: {renamedAssets.Count} assets would be renamed" :
$"Renamed {renamedAssets.Count} assets",
renamedAssets = renamedAssets,
failedAssets = failedAssets,
summary = new
{
totalProcessed = guids.Length,
renamed = renamedAssets.Count,
failed = failedAssets.Count,
unchanged = guids.Length - renamedAssets.Count - failedAssets.Count
},
settings = new
{
searchPattern = searchPattern,
replacePattern = replacePattern,
assetType = assetType,
useRegex = useRegex,
caseSensitive = caseSensitive,
dryRun = dryRun
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string BatchImportSettings(Dictionary<string, string> parameters)
{
try
{
var assetType = parameters.GetValueOrDefault("assetType", "texture"); // texture, model, audio
var folder = parameters.GetValueOrDefault("folder", "Assets");
var recursive = bool.Parse(parameters.GetValueOrDefault("recursive", "true"));
var processedAssets = new List<Dictionary<string, object>>();
var failedAssets = new List<Dictionary<string, object>>();
switch (assetType.ToLower())
{
case "texture":
var textureSettings = new
{
maxTextureSize = int.Parse(parameters.GetValueOrDefault("maxTextureSize", "2048")),
textureCompression = parameters.GetValueOrDefault("compression", "Compressed"),
generateMipmaps = bool.Parse(parameters.GetValueOrDefault("generateMipmaps", "true")),
filterMode = parameters.GetValueOrDefault("filterMode", "Bilinear"),
anisoLevel = int.Parse(parameters.GetValueOrDefault("anisoLevel", "1"))
};
var textureGUIDs = AssetDatabase.FindAssets("t:Texture2D", new[] { folder });
foreach (var guid in textureGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
try
{
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer != null)
{
importer.maxTextureSize = textureSettings.maxTextureSize;
importer.mipmapEnabled = textureSettings.generateMipmaps;
importer.anisoLevel = textureSettings.anisoLevel;
switch (textureSettings.textureCompression)
{
case "CompressedLQ":
importer.textureCompression = TextureImporterCompression.CompressedLQ;
break;
case "CompressedHQ":
importer.textureCompression = TextureImporterCompression.CompressedHQ;
break;
default:
importer.textureCompression = TextureImporterCompression.Compressed;
break;
}
importer.SaveAndReimport();
processedAssets.Add(new Dictionary<string, object>
{
["path"] = path,
["name"] = System.IO.Path.GetFileName(path),
["type"] = "Texture",
["settings"] = textureSettings
});
}
}
catch (Exception ex)
{
failedAssets.Add(new Dictionary<string, object>
{
["path"] = path,
["error"] = ex.Message
});
}
}
break;
case "model":
var modelSettings = new
{
importMaterials = bool.Parse(parameters.GetValueOrDefault("importMaterials", "true")),
importAnimation = bool.Parse(parameters.GetValueOrDefault("importAnimation", "true")),
meshCompression = parameters.GetValueOrDefault("meshCompression", "Off"),
optimizeMesh = bool.Parse(parameters.GetValueOrDefault("optimizeMesh", "true")),
generateColliders = bool.Parse(parameters.GetValueOrDefault("generateColliders", "false"))
};
var modelGUIDs = AssetDatabase.FindAssets("t:Model", new[] { folder });
foreach (var guid in modelGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
try
{
var importer = AssetImporter.GetAtPath(path) as ModelImporter;
if (importer != null)
{
importer.materialImportMode = modelSettings.importMaterials ? ModelImporterMaterialImportMode.ImportViaMaterialDescription : ModelImporterMaterialImportMode.None;
importer.importAnimation = modelSettings.importAnimation;
importer.optimizeMeshPolygons = modelSettings.optimizeMesh;
importer.optimizeMeshVertices = modelSettings.optimizeMesh;
importer.addCollider = modelSettings.generateColliders;
importer.SaveAndReimport();
processedAssets.Add(new Dictionary<string, object>
{
["path"] = path,
["name"] = System.IO.Path.GetFileName(path),
["type"] = "Model",
["settings"] = modelSettings
});
}
}
catch (Exception ex)
{
failedAssets.Add(new Dictionary<string, object>
{
["path"] = path,
["error"] = ex.Message
});
}
}
break;
}
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Updated import settings for {processedAssets.Count} assets",
processedAssets = processedAssets,
failedAssets = failedAssets,
summary = new
{
totalProcessed = processedAssets.Count,
failed = failedAssets.Count,
assetType = assetType
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string BatchPrefabUpdate(Dictionary<string, string> parameters)
{
try
{
var componentType = parameters.GetValueOrDefault("componentType", "");
var propertyName = parameters.GetValueOrDefault("propertyName", "");
var propertyValue = parameters.GetValueOrDefault("propertyValue", "");
var addIfMissing = bool.Parse(parameters.GetValueOrDefault("addIfMissing", "false"));
var folder = parameters.GetValueOrDefault("folder", "Assets");
if (string.IsNullOrEmpty(componentType))
return JsonConvert.SerializeObject(new { success = false, error = "componentType is required" });
var updatedPrefabs = new List<Dictionary<string, object>>();
var failedPrefabs = new List<Dictionary<string, object>>();
var prefabGUIDs = AssetDatabase.FindAssets("t:Prefab", new[] { folder });
var componentTypeObj = GetComponentType(componentType);
if (componentTypeObj == null)
return JsonConvert.SerializeObject(new {
success = false,
error = $"Component type '{componentType}' not found"
});
foreach (var guid in prefabGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
try
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab != null)
{
var modified = false;
var components = prefab.GetComponentsInChildren(componentTypeObj, true);
if (components.Length == 0 && addIfMissing)
{
prefab.AddComponent(componentTypeObj);
components = new[] { prefab.GetComponent(componentTypeObj) };
modified = true;
}
foreach (var comp in components)
{
if (!string.IsNullOrEmpty(propertyName) && !string.IsNullOrEmpty(propertyValue))
{
SetComponentProperty(comp, propertyName, propertyValue);
modified = true;
}
}
if (modified)
{
PrefabUtility.SavePrefabAsset(prefab);
updatedPrefabs.Add(new Dictionary<string, object>
{
["path"] = path,
["name"] = prefab.name,
["componentsUpdated"] = components.Length,
["addedComponent"] = components.Length == 0 && addIfMissing
});
}
}
}
catch (Exception ex)
{
failedPrefabs.Add(new Dictionary<string, object>
{
["path"] = path,
["error"] = ex.Message
});
}
}
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Updated {updatedPrefabs.Count} prefabs",
updatedPrefabs = updatedPrefabs,
failedPrefabs = failedPrefabs,
settings = new
{
componentType = componentType,
propertyName = propertyName,
propertyValue = propertyValue,
addIfMissing = addIfMissing,
folder = folder
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string FindUnusedAssets(Dictionary<string, string> parameters)
{
try
{
var includeTextures = bool.Parse(parameters.GetValueOrDefault("includeTextures", "true"));
var includeMaterials = bool.Parse(parameters.GetValueOrDefault("includeMaterials", "true"));
var includePrefabs = bool.Parse(parameters.GetValueOrDefault("includePrefabs", "true"));
var includeScripts = bool.Parse(parameters.GetValueOrDefault("includeScripts", "false"));
var excludeFolders = parameters.GetValueOrDefault("excludeFolders", "Packages,Editor").Split(',');
var unusedAssets = new Dictionary<string, List<string>>();
var usedAssets = new HashSet<string>();
// First, collect assets used in scene (exclude prefabs/assets in memory)
var sceneObjects = Resources.FindObjectsOfTypeAll<GameObject>()
.Where(go => !UnityEditor.EditorUtility.IsPersistent(go) && go.scene.IsValid() && go.scene.isLoaded);
foreach (var obj in sceneObjects)
{
CollectUsedAssets(obj, usedAssets);
}
// Also collect assets used in prefabs
var prefabGUIDs = AssetDatabase.FindAssets("t:Prefab");
foreach (var guid in prefabGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
usedAssets.Add(path);
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab != null)
{
CollectUsedAssets(prefab, usedAssets);
}
}
// Detect unused for each asset type
if (includeTextures)
{
var textureGUIDs = AssetDatabase.FindAssets("t:Texture2D");
var unusedTextures = new List<string>();
foreach (var guid in textureGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (!usedAssets.Contains(path) && !IsInExcludedFolder(path, excludeFolders))
{
unusedTextures.Add(path);
}
}
unusedAssets["textures"] = unusedTextures;
}
if (includeMaterials)
{
var materialGUIDs = AssetDatabase.FindAssets("t:Material");
var unusedMaterials = new List<string>();
foreach (var guid in materialGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (!usedAssets.Contains(path) && !IsInExcludedFolder(path, excludeFolders))
{
unusedMaterials.Add(path);
}
}
unusedAssets["materials"] = unusedMaterials;
}
// Calculate statistics
long totalSize = 0;
var assetDetails = new List<Dictionary<string, object>>();
foreach (var kvp in unusedAssets)
{
foreach (var path in kvp.Value)
{
var fileInfo = new System.IO.FileInfo(path);
if (fileInfo.Exists)
{
totalSize += fileInfo.Length;
assetDetails.Add(new Dictionary<string, object>
{
["path"] = path,
["name"] = System.IO.Path.GetFileName(path),
["type"] = kvp.Key,
["size"] = fileInfo.Length,
["sizeMB"] = Math.Round(fileInfo.Length / (1024.0 * 1024.0), 2)
});
}
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Found {assetDetails.Count} unused assets",
unusedAssets = unusedAssets,
assetDetails = assetDetails.OrderByDescending(a => (long)a["size"]).ToList(),
summary = new
{
totalUnused = assetDetails.Count,
totalSizeBytes = totalSize,
totalSizeMB = Math.Round(totalSize / (1024.0 * 1024.0), 2),
byType = unusedAssets.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Count)
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private void CollectUsedAssets(GameObject obj, HashSet<string> usedAssets)
{
// Renderer materials
var renderer = obj.GetComponent<Renderer>();
if (renderer != null)
{
foreach (var mat in renderer.sharedMaterials)
{
if (mat != null)
{
var matPath = AssetDatabase.GetAssetPath(mat);
if (!string.IsNullOrEmpty(matPath))
{
usedAssets.Add(matPath);
// Material textures
var shader = mat.shader;
for (int i = 0; i < ShaderUtil.GetPropertyCount(shader); i++)
{
if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv)
{
var propName = ShaderUtil.GetPropertyName(shader, i);
var tex = mat.GetTexture(propName);
if (tex != null)
{
var texPath = AssetDatabase.GetAssetPath(tex);
if (!string.IsNullOrEmpty(texPath))
usedAssets.Add(texPath);
}
}
}
}
}
}
}
// Check child objects recursively
foreach (Transform child in obj.transform)
{
CollectUsedAssets(child.gameObject, usedAssets);
}
}
private bool IsInExcludedFolder(string path, string[] excludeFolders)
{
foreach (var folder in excludeFolders)
{
if (path.StartsWith(folder.Trim() + "/") || path.Contains("/" + folder.Trim() + "/"))
return true;
}
return false;
}
private string EstimateBuildSize(Dictionary<string, string> parameters)
{
try
{
var platform = parameters.GetValueOrDefault("platform", "current");
var includeStreamingAssets = bool.Parse(parameters.GetValueOrDefault("includeStreamingAssets", "true"));
var includeResources = bool.Parse(parameters.GetValueOrDefault("includeResources", "true"));
var buildEstimate = new Dictionary<string, object>();
long totalSize = 0;
// Estimate scene size
var sceneSizes = new List<Dictionary<string, object>>();
var sceneGUIDs = AssetDatabase.FindAssets("t:Scene");
foreach (var guid in sceneGUIDs)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var dependencies = AssetDatabase.GetDependencies(path, true);
long sceneSize = 0;
foreach (var dep in dependencies)
{
var fileInfo = new System.IO.FileInfo(dep);
if (fileInfo.Exists)
sceneSize += fileInfo.Length;
}
sceneSizes.Add(new Dictionary<string, object>
{
["scene"] = System.IO.Path.GetFileNameWithoutExtension(path),
["path"] = path,
["sizeBytes"] = sceneSize,
["sizeMB"] = Math.Round(sceneSize / (1024.0 * 1024.0), 2),
["dependencyCount"] = dependencies.Length
});
totalSize += sceneSize;
}
// Resources folder size
long resourcesSize = 0;
if (includeResources && System.IO.Directory.Exists("Assets/Resources"))
{
resourcesSize = GetFolderSize("Assets/Resources");
totalSize += resourcesSize;
}
// StreamingAssets folder size
long streamingAssetsSize = 0;
if (includeStreamingAssets && System.IO.Directory.Exists("Assets/StreamingAssets"))
{
streamingAssetsSize = GetFolderSize("Assets/StreamingAssets");
totalSize += streamingAssetsSize;
}
// Script size (approximate)
var scriptGUIDs = AssetDatabase.FindAssets("t:MonoScript");
long scriptSize = scriptGUIDs.Length * 5 * 1024; // Average 5KB per compiled script
totalSize += scriptSize;
// Platform-specific overhead
var platformOverhead = platform switch
{
"Android" => 1.2f,
"iOS" => 1.15f,
"WebGL" => 1.3f,
_ => 1.1f
};
var estimatedTotal = (long)(totalSize * platformOverhead);
return JsonConvert.SerializeObject(new
{
success = true,
message = "Build size estimation completed",
estimate = new
{
rawAssetsSize = totalSize,
estimatedBuildSize = estimatedTotal,
estimatedBuildSizeMB = Math.Round(estimatedTotal / (1024.0 * 1024.0), 2),
platformOverheadFactor = platformOverhead,
platform = platform
},
breakdown = new
{
scenes = new
{
count = sceneSizes.Count,
totalSizeMB = Math.Round(sceneSizes.Sum(s => (long)s["sizeBytes"]) / (1024.0 * 1024.0), 2),
details = sceneSizes.OrderByDescending(s => (long)s["sizeBytes"]).ToList()
},
resources = new
{
sizeMB = Math.Round(resourcesSize / (1024.0 * 1024.0), 2),
included = includeResources
},
streamingAssets = new
{
sizeMB = Math.Round(streamingAssetsSize / (1024.0 * 1024.0), 2),
included = includeStreamingAssets
},
scripts = new
{
count = scriptGUIDs.Length,
estimatedSizeMB = Math.Round(scriptSize / (1024.0 * 1024.0), 2)
}
},
recommendations = GetBuildSizeRecommendations(estimatedTotal)
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private long GetFolderSize(string folder)
{
long size = 0;
var files = System.IO.Directory.GetFiles(folder, "*", System.IO.SearchOption.AllDirectories);
foreach (var file in files)
{
if (!file.EndsWith(".meta"))
{
var fileInfo = new System.IO.FileInfo(file);
size += fileInfo.Length;
}
}
return size;
}
private List<string> GetBuildSizeRecommendations(long buildSize)
{
var recommendations = new List<string>();
var sizeMB = buildSize / (1024.0 * 1024.0);
if (sizeMB > 100)
recommendations.Add("Consider texture compression and atlas optimization");
if (sizeMB > 200)
recommendations.Add("Review audio compression settings");
if (sizeMB > 500)
recommendations.Add("Consider using Asset Bundles for dynamic loading");
recommendations.Add("Use sprite atlases to reduce draw calls");
recommendations.Add("Remove unused assets before building");
return recommendations;
}
private string PerformanceReport(Dictionary<string, string> parameters)
{
try
{
var includeRendering = bool.Parse(parameters.GetValueOrDefault("includeRendering", "true"));
var includeMemory = bool.Parse(parameters.GetValueOrDefault("includeMemory", "true"));
var includeTextures = bool.Parse(parameters.GetValueOrDefault("includeTextures", "true"));
var includeAudio = bool.Parse(parameters.GetValueOrDefault("includeAudio", "true"));
var report = new Dictionary<string, object>();
// Rendering statistics
if (includeRendering)
{
var renderers = UnityEngine.Object.FindObjectsOfType<Renderer>();
var cameras = UnityEngine.Object.FindObjectsOfType<Camera>();
var lights = UnityEngine.Object.FindObjectsOfType<Light>();
var materialStats = new Dictionary<string, int>();
var meshStats = new Dictionary<string, int>();
long totalVertices = 0;
long totalTriangles = 0;
int renderersWithMeshFilter = 0;
int renderersWithoutMeshFilter = 0;
var rendererTypes = new Dictionary<string, int>();
foreach (var renderer in renderers)
{
try
{
if (renderer == null || !renderer.enabled)
continue;
// Count renderer type
var rendererType = renderer.GetType().Name;
rendererTypes[rendererType] = rendererTypes.GetValueOrDefault(rendererType, 0) + 1;
// Material statistics
if (renderer.sharedMaterials != null)
{
foreach (var mat in renderer.sharedMaterials)
{
if (mat != null && mat.shader != null)
{
var shader = mat.shader.name;
materialStats[shader] = materialStats.GetValueOrDefault(shader, 0) + 1;
}
}
}
// Safe retrieval of MeshFilter
MeshFilter meshFilter = null;
try
{
meshFilter = renderer.GetComponent<MeshFilter>();
}
catch (MissingComponentException)
{
// Ignore if MeshFilter does not exist
}
if (meshFilter?.sharedMesh != null)
{
renderersWithMeshFilter++;
var mesh = meshFilter.sharedMesh;
totalVertices += mesh.vertexCount;
totalTriangles += mesh.triangles.Length / 3;
var meshName = mesh.name ?? "Unnamed";
meshStats[meshName] = meshStats.GetValueOrDefault(meshName, 0) + 1;
}
else
{
renderersWithoutMeshFilter++;
// Process SkinnedMeshRenderer separately
if (renderer is SkinnedMeshRenderer skinnedRenderer && skinnedRenderer.sharedMesh != null)
{
var mesh = skinnedRenderer.sharedMesh;
totalVertices += mesh.vertexCount;
totalTriangles += mesh.triangles.Length / 3;
var meshName = $"{mesh.name} (Skinned)" ?? "Unnamed (Skinned)";
meshStats[meshName] = meshStats.GetValueOrDefault(meshName, 0) + 1;
}
}
}
catch (Exception ex)
{
SynLog.Warn($"[PerformanceReport] Error processing renderer on {renderer?.name}: {ex.Message}");
continue;
}
}
report["rendering"] = new
{
activeRenderers = renderers.Count(r => r.enabled),
totalVertices = totalVertices,
totalTriangles = totalTriangles,
cameras = cameras.Length,
lights = lights.Length,
renderersWithMeshFilter = renderersWithMeshFilter,
renderersWithoutMeshFilter = renderersWithoutMeshFilter,
skinnedMeshRenderers = renderers.Count(r => r is SkinnedMeshRenderer && r.enabled),
meshFiltersWithoutRenderer = UnityEngine.Object.FindObjectsOfType<MeshFilter>()
.Count(mf => mf.GetComponent<MeshRenderer>() == null),
rendererTypes = rendererTypes.OrderByDescending(kvp => kvp.Value)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
shaderUsage = materialStats.OrderByDescending(kvp => kvp.Value)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
meshUsage = meshStats.OrderByDescending(kvp => kvp.Value)
.Take(10).ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
qualitySettings = new
{
currentLevel = QualitySettings.GetQualityLevel(),
pixelLightCount = QualitySettings.pixelLightCount,
shadows = QualitySettings.shadows.ToString(),
antiAliasing = QualitySettings.antiAliasing
}
};
}
// Memory statistics
if (includeMemory)
{
report["memory"] = new
{
totalAllocatedMemoryMB = Profiler.GetTotalAllocatedMemoryLong() / (1024.0 * 1024.0),
totalReservedMemoryMB = Profiler.GetTotalReservedMemoryLong() / (1024.0 * 1024.0),
monoHeapSizeMB = Profiler.GetMonoHeapSizeLong() / (1024.0 * 1024.0),
monoUsedSizeMB = Profiler.GetMonoUsedSizeLong() / (1024.0 * 1024.0)
};
}
// Texture statistics
if (includeTextures)
{
var textureMemory = 0L;
var textureCount = 0;
var texturesBySize = new Dictionary<string, int>();
var textures = Resources.FindObjectsOfTypeAll<Texture2D>();
foreach (var tex in textures)
{
if (AssetDatabase.Contains(tex))
{
textureCount++;
var size = Profiler.GetRuntimeMemorySizeLong(tex);
textureMemory += size;
var sizeCategory = tex.width + "x" + tex.height;
texturesBySize[sizeCategory] = texturesBySize.GetValueOrDefault(sizeCategory, 0) + 1;
}
}
report["textures"] = new
{
count = textureCount,
totalMemoryMB = textureMemory / (1024.0 * 1024.0),
sizeDistribution = texturesBySize.OrderByDescending(kvp => kvp.Value)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
};
}
// Audio statistics
if (includeAudio)
{
var audioClips = Resources.FindObjectsOfTypeAll<AudioClip>();
var audioMemory = 0L;
var audioStats = new Dictionary<string, object>();
foreach (var clip in audioClips)
{
if (AssetDatabase.Contains(clip))
{
audioMemory += Profiler.GetRuntimeMemorySizeLong(clip);
}
}
report["audio"] = new
{
clipCount = audioClips.Length,
totalMemoryMB = audioMemory / (1024.0 * 1024.0),
audioSourceCount = UnityEngine.Object.FindObjectsOfType<AudioSource>().Length
};
}
// Performance recommendations
var recommendations = new List<string>();
if (includeRendering && report.ContainsKey("rendering"))
{
var rendering = report["rendering"] as Dictionary<string, object>;
if (rendering != null && rendering.ContainsKey("totalTriangles") && (long)rendering["totalTriangles"] > 1000000)
recommendations.Add("High polygon count detected. Consider LOD implementation");
if (rendering != null && rendering.ContainsKey("lights") && (int)rendering["lights"] > 10)
recommendations.Add("Many lights detected. Consider light baking");
}
if (includeTextures && report.ContainsKey("textures"))
{
var textures = report["textures"] as Dictionary<string, object>;
if (textures != null && textures.ContainsKey("totalMemoryMB") && (double)textures["totalMemoryMB"] > 500)
recommendations.Add("High texture memory usage. Consider texture compression");
}
report["recommendations"] = recommendations;
return JsonConvert.SerializeObject(new
{
success = true,
message = "Performance report generated",
report = report,
timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string AutoOrganizeFolders(Dictionary<string, string> parameters)
{
try
{
var rootFolder = parameters.GetValueOrDefault("rootFolder", "Assets");
var createFolders = bool.Parse(parameters.GetValueOrDefault("createFolders", "true"));
var moveAssets = bool.Parse(parameters.GetValueOrDefault("moveAssets", "false"));
var dryRun = bool.Parse(parameters.GetValueOrDefault("dryRun", "true"));
var standardFolders = new Dictionary<string, string[]>
{
["Scripts"] = new[] { ".cs", ".js" },
["Materials"] = new[] { ".mat" },
["Textures"] = new[] { ".png", ".jpg", ".jpeg", ".tga", ".psd", ".tiff" },
["Models"] = new[] { ".fbx", ".obj", ".dae", ".3ds", ".blend" },
["Prefabs"] = new[] { ".prefab" },
["Audio"] = new[] { ".wav", ".mp3", ".ogg", ".aiff" },
["Animations"] = new[] { ".anim", ".controller" },
["Fonts"] = new[] { ".ttf", ".otf" },
["Shaders"] = new[] { ".shader", ".shadergraph" },
["UI"] = new string[] { } // UI elements processed specially
};
var operations = new List<Dictionary<string, object>>();
var createdFolders = new List<string>();
// Create folders
if (createFolders)
{
foreach (var folder in standardFolders.Keys)
{
var folderPath = $"{rootFolder}/{folder}";
if (!AssetDatabase.IsValidFolder(folderPath))
{
if (!dryRun)
{
AssetDatabase.CreateFolder(rootFolder, folder);
}
createdFolders.Add(folderPath);
}
}
}
// Organize assets
if (moveAssets)
{
var files = System.IO.Directory.GetFiles(rootFolder, "*", System.IO.SearchOption.TopDirectoryOnly)
.Where(f => !f.EndsWith(".meta"));
foreach (var file in files)
{
var extension = System.IO.Path.GetExtension(file).ToLower();
var fileName = System.IO.Path.GetFileName(file);
foreach (var kvp in standardFolders)
{
if (kvp.Value.Contains(extension))
{
var targetFolder = $"{rootFolder}/{kvp.Key}";
var newPath = $"{targetFolder}/{fileName}";
operations.Add(new Dictionary<string, object>
{
["operation"] = "move",
["from"] = file.Replace('\\', '/'),
["to"] = newPath,
["folder"] = kvp.Key
});
if (!dryRun && AssetDatabase.IsValidFolder(targetFolder))
{
AssetDatabase.MoveAsset(file.Replace('\\', '/'), newPath);
}
break;
}
}
}
}
if (!dryRun)
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = dryRun ?
"Dry run completed - no changes made" :
$"Organized {operations.Count} assets into {createdFolders.Count} folders",
createdFolders = createdFolders,
operations = operations,
summary = new
{
foldersCreated = createdFolders.Count,
assetsToMove = operations.Count,
dryRun = dryRun
},
standardStructure = standardFolders.Keys.ToList()
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string GenerateLOD(Dictionary<string, string> parameters)
{
try
{
// Use targetObject parameter as main according to API definition, also support target
var targetObject = parameters.GetValueOrDefault("targetObject", "") ??
parameters.GetValueOrDefault("target", "");
var lodLevels = int.Parse(parameters.GetValueOrDefault("lodLevels", "3"));
var qualitySettings = parameters.GetValueOrDefault("qualitySettings", "0.75,0.5,0.25");
if (string.IsNullOrEmpty(targetObject))
return CreateMissingParameterResponse("GenerateLOD", "targetObject", parameters);
var gameObject = GameObject.Find(targetObject);
if (gameObject == null)
return JsonConvert.SerializeObject(new {
success = false,
error = "GameObject not found"
});
var meshFilter = gameObject.GetComponent<MeshFilter>();
if (meshFilter == null)
{
// Automatically add if MeshFilter is missing
meshFilter = gameObject.AddComponent<MeshFilter>();
// Set basic shape mesh
var renderer = gameObject.GetComponent<Renderer>();
if (renderer == null)
{
renderer = gameObject.AddComponent<MeshRenderer>();
}
// Create primitive mesh (use Cube as default)
var tempGO = CreatePrimitiveWithMaterial(PrimitiveType.Cube);
meshFilter.sharedMesh = tempGO.GetComponent<MeshFilter>().sharedMesh;
GameObject.Destroy(tempGO);
SynLog.Info($"[Synaptic] Auto-added MeshFilter with default cube mesh to '{gameObject.name}'");
}
if (meshFilter.sharedMesh == null)
return JsonConvert.SerializeObject(new {
success = false,
error = "MeshFilter has no mesh assigned"
});
// Add or get LODGroup
var lodGroup = gameObject.GetComponent<LODGroup>();
if (lodGroup == null)
lodGroup = gameObject.AddComponent<LODGroup>();
var qualityLevels = qualitySettings.Split(',').Select(float.Parse).ToArray();
var lods = new LOD[lodLevels];
var lodObjects = new List<GameObject>();
// LOD0 is the original object
lods[0] = new LOD(0.6f, new Renderer[] { gameObject.GetComponent<Renderer>() });
// Generate additional LOD levels
for (int i = 1; i < lodLevels; i++)
{
var lodObject = new GameObject($"{gameObject.name}_LOD{i}");
lodObject.transform.SetParent(gameObject.transform);
lodObject.transform.localPosition = Vector3.zero;
lodObject.transform.localRotation = Quaternion.identity;
lodObject.transform.localScale = Vector3.one;
var lodMeshFilter = lodObject.AddComponent<MeshFilter>();
var lodRenderer = lodObject.AddComponent<MeshRenderer>();
// Copy original Renderer settings
var originalRenderer = gameObject.GetComponent<Renderer>();
lodRenderer.sharedMaterials = originalRenderer.sharedMaterials;
// Simple LOD mesh generation (use specialized mesh decimation algorithm in actual implementation)
var quality = i < qualityLevels.Length ? qualityLevels[i] : 0.5f / i;
lodMeshFilter.sharedMesh = meshFilter.sharedMesh; // Should generate simplified mesh here in production
float screenRelativeHeight = i == 1 ? 0.3f : 0.1f / (i - 1);
lods[i] = new LOD(screenRelativeHeight, new Renderer[] { lodRenderer });
lodObjects.Add(lodObject);
}
lodGroup.SetLODs(lods);
lodGroup.RecalculateBounds();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Generated {lodLevels} LOD levels for '{targetObject}'",
target = targetObject,
lodLevels = lodLevels,
lodInfo = lods.Select((lod, i) => new
{
level = i,
screenRelativeHeight = lod.screenRelativeTransitionHeight,
rendererCount = lod.renderers.Length
}).ToList(),
note = "This is a simplified LOD generation. For production, use specialized mesh decimation tools."
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string AutoAtlasTextures(Dictionary<string, string> parameters)
{
try
{
var folder = parameters.GetValueOrDefault("folder", "Assets/Textures");
var atlasName = parameters.GetValueOrDefault("atlasName", "TextureAtlas");
var maxAtlasSize = int.Parse(parameters.GetValueOrDefault("maxAtlasSize", "2048"));
var padding = int.Parse(parameters.GetValueOrDefault("padding", "2"));
// Check folder exists
if (!AssetDatabase.IsValidFolder(folder))
{
// Create folder if it doesn't exist
var folderParts = folder.Split('/');
var parentPath = folderParts[0];
for (int i = 1; i < folderParts.Length; i++)
{
var nextPath = parentPath + "/" + folderParts[i];
if (!AssetDatabase.IsValidFolder(nextPath))
{
AssetDatabase.CreateFolder(parentPath, folderParts[i]);
}
parentPath = nextPath;
}
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Folder '{folder}' did not exist and was created. No textures found to atlas."
});
}
// Assumes Unity 2020 or later Sprite Atlas API
var textureGUIDs = AssetDatabase.FindAssets("t:Texture2D", new[] { folder });
var textures = new List<Texture2D>();
foreach (var guid in textureGUIDs.Take(50)) // Apply limit
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (texture != null && texture.width <= maxAtlasSize && texture.height <= maxAtlasSize)
{
// Check Read/Write enabled
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer != null && !importer.isReadable)
{
importer.isReadable = true;
importer.SaveAndReimport();
}
textures.Add(texture);
}
}
if (textures.Count == 0)
{
// Detailed diagnostics when no textures found
var diagnostics = new Dictionary<string, object>();
diagnostics["searchFolder"] = folder;
diagnostics["totalTexturesInFolder"] = textureGUIDs.Length;
// Analyze reason if textures found but all invalid
if (textureGUIDs.Length > 0)
{
var rejectionReasons = new Dictionary<string, int>();
var tooLarge = 0;
var failedToLoad = 0;
foreach (var guid in textureGUIDs.Take(10)) // Sample check
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (texture == null)
{
failedToLoad++;
}
else if (texture.width > maxAtlasSize || texture.height > maxAtlasSize)
{
tooLarge++;
}
}
if (tooLarge > 0)
rejectionReasons["Textures too large for atlas"] = tooLarge;
if (failedToLoad > 0)
rejectionReasons["Failed to load texture"] = failedToLoad;
diagnostics["rejectionReasons"] = rejectionReasons;
diagnostics["suggestion"] = "Try increasing maxAtlasSize or using a different folder";
}
else
{
// List all files in folder
var allAssets = AssetDatabase.FindAssets("", new[] { folder });
diagnostics["totalAssetsInFolder"] = allAssets.Length;
if (allAssets.Length == 0)
{
diagnostics["note"] = "Folder is empty";
diagnostics["suggestion"] = "Add texture files to the folder first";
}
else
{
diagnostics["note"] = "Folder contains assets but no textures";
diagnostics["suggestion"] = "Ensure files are imported as Texture2D type";
}
}
return JsonConvert.SerializeObject(new {
success = false,
error = "No suitable textures found for atlas creation",
diagnostics = diagnostics,
requirements = new {
maxSize = $"{maxAtlasSize}x{maxAtlasSize}",
formats = new[] { "PNG", "JPG", "TGA", "PSD", "BMP" },
settings = "Texture must be readable (Read/Write Enabled)"
}
}, Formatting.Indented);
}
// Create atlas
var atlas = new Texture2D(maxAtlasSize, maxAtlasSize);
var rects = atlas.PackTextures(textures.ToArray(), padding, maxAtlasSize);
if (rects == null || rects.Length == 0)
return JsonConvert.SerializeObject(new {
success = false,
error = "Failed to pack textures into atlas"
});
// Save atlas
var atlasPath = $"{folder}/{atlasName}.png";
var pngData = atlas.EncodeToPNG();
System.IO.File.WriteAllBytes(atlasPath, pngData);
AssetDatabase.Refresh();
// Atlas settings
var atlasImporter = AssetImporter.GetAtPath(atlasPath) as TextureImporter;
if (atlasImporter != null)
{
atlasImporter.textureType = TextureImporterType.Sprite;
atlasImporter.spriteImportMode = SpriteImportMode.Multiple;
atlasImporter.maxTextureSize = maxAtlasSize;
atlasImporter.SaveAndReimport();
}
// Generate atlas information
var atlasInfo = new List<Dictionary<string, object>>();
for (int i = 0; i < textures.Count; i++)
{
atlasInfo.Add(new Dictionary<string, object>
{
["textureName"] = textures[i].name,
["rect"] = new
{
x = rects[i].x,
y = rects[i].y,
width = rects[i].width,
height = rects[i].height
}
});
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created texture atlas with {textures.Count} textures",
atlasPath = atlasPath,
atlasSize = new { width = atlas.width, height = atlas.height },
textureCount = textures.Count,
textures = atlasInfo,
settings = new
{
maxSize = maxAtlasSize,
padding = padding
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
// ===== Game development specific features =====
private string CreateGameController(Dictionary<string, string> parameters)
{
try
{
var controllerType = parameters.GetValueOrDefault("type", "FirstPerson");
var playerName = parameters.GetValueOrDefault("playerName", "Player");
var includeCamera = bool.Parse(parameters.GetValueOrDefault("includeCamera", "true"));
var movementSpeed = float.Parse(parameters.GetValueOrDefault("movementSpeed", "5"));
var jumpHeight = float.Parse(parameters.GetValueOrDefault("jumpHeight", "3"));
// PlayerGameObjectCreate
var player = new GameObject(playerName);
// Add basic components
var rb = player.AddComponent<Rigidbody>();
rb.constraints = RigidbodyConstraints.FreezeRotation;
var collider = player.AddComponent<CapsuleCollider>();
collider.height = 2f;
collider.radius = 0.5f;
// ControllerGenerate script
string scriptContent = "";
string scriptName = "";
switch (controllerType)
{
case "FirstPerson":
scriptName = "FirstPersonController";
scriptContent = GenerateFirstPersonControllerScript(movementSpeed, jumpHeight);
break;
case "ThirdPerson":
scriptName = "ThirdPersonController";
scriptContent = GenerateThirdPersonControllerScript(movementSpeed, jumpHeight);
break;
case "TopDown":
scriptName = "TopDownController";
scriptContent = GenerateTopDownControllerScript(movementSpeed);
break;
case "Platformer2D":
scriptName = "Platformer2DController";
scriptContent = GeneratePlatformer2DControllerScript(movementSpeed, jumpHeight);
// Adjust components for 2D
GameObject.Destroy(rb);
GameObject.Destroy(collider);
player.AddComponent<Rigidbody2D>();
player.AddComponent<BoxCollider2D>();
break;
}
// ScriptSave
var scriptPath = $"Assets/Scripts/Controllers/{scriptName}.cs";
var scriptDir = System.IO.Path.GetDirectoryName(scriptPath);
if (!System.IO.Directory.Exists(scriptDir))
{
System.IO.Directory.CreateDirectory(scriptDir);
}
System.IO.File.WriteAllText(scriptPath, scriptContent);
AssetDatabase.Refresh();
// CameraSetup
if (includeCamera)
{
GameObject cameraGO = null;
switch (controllerType)
{
case "FirstPerson":
cameraGO = new GameObject("PlayerCamera");
cameraGO.AddComponent<Camera>();
cameraGO.transform.parent = player.transform;
cameraGO.transform.localPosition = new Vector3(0, 1.6f, 0);
break;
case "ThirdPerson":
cameraGO = new GameObject("CameraRig");
var pivot = new GameObject("CameraPivot");
pivot.transform.parent = cameraGO.transform;
var cam = new GameObject("Camera");
cam.AddComponent<Camera>();
cam.transform.parent = pivot.transform;
cam.transform.localPosition = new Vector3(0, 2, -5);
cam.transform.LookAt(pivot.transform);
break;
}
}
Selection.activeGameObject = player;
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created {controllerType} controller",
gameObjectName = playerName,
scriptPath = scriptPath,
components = new[] { "Rigidbody", "Collider", scriptName },
settings = new
{
type = controllerType,
movementSpeed = movementSpeed,
jumpHeight = jumpHeight,
includeCamera = includeCamera
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string GenerateFirstPersonControllerScript(float moveSpeed, float jumpHeight)
{
return @"using UnityEngine;
public class FirstPersonController : MonoBehaviour
{
[Header(""Movement"")]
public float moveSpeed = " + moveSpeed + @"f;
public float jumpHeight = " + jumpHeight + @"f;
public float gravity = -9.81f;
[Header(""Look"")]
public float mouseSensitivity = 2f;
public float lookXLimit = 80f;
CharacterController characterController;
Vector3 moveDirection = Vector3.zero;
float rotationX = 0;
public Camera playerCamera;
void Start()
{
characterController = GetComponent<CharacterController>();
if (!characterController)
characterController = gameObject.AddComponent<CharacterController>();
if (!playerCamera)
playerCamera = GetComponentInChildren<Camera>();
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
void Update()
{
// Movement
Vector3 forward = transform.TransformDirection(Vector3.forward);
Vector3 right = transform.TransformDirection(Vector3.right);
float curSpeedX = moveSpeed * Input.GetAxis(""Vertical"");
float curSpeedY = moveSpeed * Input.GetAxis(""Horizontal"");
float movementDirectionY = moveDirection.y;
moveDirection = (forward * curSpeedX) + (right * curSpeedY);
if (Input.GetButton(""Jump"") && characterController.isGrounded)
{
moveDirection.y = Mathf.Sqrt(jumpHeight * -2.0f * gravity);
}
else
{
moveDirection.y = movementDirectionY;
}
if (!characterController.isGrounded)
{
moveDirection.y += gravity * Time.deltaTime;
}
characterController.Move(moveDirection * Time.deltaTime);
// Camera rotation
rotationX += -Input.GetAxis(""Mouse Y"") * mouseSensitivity;
rotationX = Mathf.Clamp(rotationX, -lookXLimit, lookXLimit);
playerCamera.transform.localRotation = Quaternion.Euler(rotationX, 0, 0);
transform.rotation *= Quaternion.Euler(0, Input.GetAxis(""Mouse X"") * mouseSensitivity, 0);
}
}";
}
private string GenerateThirdPersonControllerScript(float moveSpeed, float jumpHeight)
{
return @"using UnityEngine;
public class ThirdPersonController : MonoBehaviour
{
[Header(""Movement"")]
public float moveSpeed = " + moveSpeed + @"f;
public float jumpHeight = " + jumpHeight + @"f;
public float turnSmoothTime = 0.1f;
[Header(""Camera"")]
public Transform cameraTransform;
float turnSmoothVelocity;
CharacterController controller;
void Start()
{
controller = GetComponent<CharacterController>();
if (!controller)
controller = gameObject.AddComponent<CharacterController>();
if (!cameraTransform && Camera.main)
cameraTransform = Camera.main.transform;
}
void Update()
{
float horizontal = Input.GetAxisRaw(""Horizontal"");
float vertical = Input.GetAxisRaw(""Vertical"");
Vector3 inputVector = new Vector3(horizontal, 0f, vertical);
if (inputVector.magnitude >= 0.1f)
{
Vector3 direction = inputVector.normalized;
float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + cameraTransform.eulerAngles.y;
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, turnSmoothTime);
transform.rotation = Quaternion.Euler(0f, angle, 0f);
Vector3 moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
controller.Move(moveDir.normalized * moveSpeed * Time.deltaTime);
}
if (Input.GetButtonDown(""Jump"") && controller.isGrounded)
{
// Jump logic
}
}
}";
}
private string GenerateTopDownControllerScript(float moveSpeed)
{
return @"using UnityEngine;
public class TopDownController : MonoBehaviour
{
public float moveSpeed = " + moveSpeed + @"f;
public bool rotateTowardsMouse = true;
Rigidbody rb;
Camera mainCamera;
void Start()
{
rb = GetComponent<Rigidbody>();
mainCamera = Camera.main;
}
void Update()
{
float horizontal = Input.GetAxis(""Horizontal"");
float vertical = Input.GetAxis(""Vertical"");
Vector3 inputVector = new Vector3(horizontal, 0, vertical);
Vector3 movement = inputVector.magnitude > 0.1f ? inputVector.normalized * moveSpeed : Vector3.zero;
rb.velocity = new Vector3(movement.x, rb.velocity.y, movement.z);
if (rotateTowardsMouse)
{
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 100f))
{
Vector3 lookDir = hit.point - transform.position;
lookDir.y = 0;
if (lookDir != Vector3.zero)
{
transform.rotation = Quaternion.LookRotation(lookDir);
}
}
}
else if (movement != Vector3.zero)
{
transform.rotation = Quaternion.LookRotation(movement);
}
}
}";
}
private string GeneratePlatformer2DControllerScript(float moveSpeed, float jumpHeight)
{
return @"using UnityEngine;
public class Platformer2DController : MonoBehaviour
{
[Header(""Movement"")]
public float moveSpeed = " + moveSpeed + @"f;
public float jumpForce = " + jumpHeight * 3 + @"f;
[Header(""Ground Check"")]
public Transform groundCheck;
public float groundCheckRadius = 0.2f;
public LayerMask groundLayer;
Rigidbody2D rb;
bool isGrounded;
float moveInput;
void Start()
{
rb = GetComponent<Rigidbody2D>();
// Create ground check if not assigned
if (!groundCheck)
{
GameObject gc = new GameObject(""GroundCheck"");
gc.transform.parent = transform;
gc.transform.localPosition = new Vector3(0, -1f, 0);
groundCheck = gc.transform;
}
}
void Update()
{
moveInput = Input.GetAxis(""Horizontal"");
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
if (Input.GetButtonDown(""Jump"") && isGrounded)
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
}
// Flip sprite based on direction
if (moveInput > 0)
transform.localScale = new Vector3(1, 1, 1);
else if (moveInput < 0)
transform.localScale = new Vector3(-1, 1, 1);
}
void FixedUpdate()
{
rb.velocity = new Vector2(moveInput * moveSpeed, rb.velocity.y);
}
}";
}
private string SetupInputSystem(Dictionary<string, string> parameters)
{
try
{
var template = parameters.GetValueOrDefault("template", "Standard");
var createAsset = bool.Parse(parameters.GetValueOrDefault("createAsset", "true"));
string inputActionsContent = "";
string assetPath = "Assets/Settings/InputActions.inputactions";
switch (template)
{
case "Standard":
inputActionsContent = GenerateStandardInputActions();
break;
case "Mobile":
inputActionsContent = GenerateMobileInputActions();
break;
case "VR":
inputActionsContent = GenerateVRInputActions();
break;
}
if (createAsset && !string.IsNullOrEmpty(inputActionsContent))
{
var dir = System.IO.Path.GetDirectoryName(assetPath);
if (!System.IO.Directory.Exists(dir))
{
System.IO.Directory.CreateDirectory(dir);
}
System.IO.File.WriteAllText(assetPath, inputActionsContent);
AssetDatabase.Refresh();
}
// Input SystemSettingsUpdate
// Update Input System Settings in PlayerSettings
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Set up {template} input system",
assetPath = assetPath,
template = template,
settings = new
{
activeInputHandling = "Both",
createdAsset = createAsset
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string GenerateStandardInputActions()
{
return @"{
""name"": ""InputActions"",
""maps"": [
{
""name"": ""Player"",
""id"": ""f62a4b92-2026-4d06-8d54-5a7a3e9263a3"",
""actions"": [
{
""name"": ""Move"",
""type"": ""Value"",
""id"": ""6b5a16bd-c6db-420e-a1f5-58e4b4c7b2d0"",
""expectedControlType"": ""Vector2"",
""processors"": """",
""interactions"": """"
},
{
""name"": ""Jump"",
""type"": ""Button"",
""id"": ""8c4abdf8-4099-493a-aa1a-129e93b5d395"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """"
},
{
""name"": ""Look"",
""type"": ""Value"",
""id"": ""2690aefc-a4cc-4ddc-85aa-79b633077c7a"",
""expectedControlType"": ""Vector2"",
""processors"": """",
""interactions"": """"
}
],
""bindings"": [
{
""name"": ""WASD"",
""id"": ""00ca640b-d935-4593-8157-c05a1c025fe3"",
""path"": ""2DVector"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Move"",
""isComposite"": true,
""isPartOfComposite"": false
},
{
""name"": ""up"",
""id"": ""e2062cb9-1b15-4a9e-a5dc-cdaf3e82d1a1"",
""path"": ""<Keyboard>/w"",
""interactions"": """",
""processors"": """",
""groups"": ""Keyboard&Mouse"",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""down"",
""id"": ""320838fc-f295-4832-8673-08ff8ec0d75e"",
""path"": ""<Keyboard>/s"",
""interactions"": """",
""processors"": """",
""groups"": ""Keyboard&Mouse"",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""left"",
""id"": ""d2581a9b-1d11-4566-b73e-c5c1882c073a"",
""path"": ""<Keyboard>/a"",
""interactions"": """",
""processors"": """",
""groups"": ""Keyboard&Mouse"",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": ""right"",
""id"": ""fcfe8d35-23dc-4c1c-9cfd-f0cd20926f07"",
""path"": ""<Keyboard>/d"",
""interactions"": """",
""processors"": """",
""groups"": ""Keyboard&Mouse"",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": true
},
{
""name"": """",
""id"": ""1077f913-e8ba-4d5f-beb7-507b4e665d37"",
""path"": ""<Keyboard>/space"",
""interactions"": """",
""processors"": """",
""groups"": ""Keyboard&Mouse"",
""action"": ""Jump"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""8c8e490e-08ff-4d1b-912f-0154797a2b73"",
""path"": ""<Mouse>/delta"",
""interactions"": """",
""processors"": """",
""groups"": ""Keyboard&Mouse"",
""action"": ""Look"",
""isComposite"": false,
""isPartOfComposite"": false
}
]
}
],
""controlSchemes"": [
{
""name"": ""Keyboard&Mouse"",
""bindingGroup"": ""Keyboard&Mouse"",
""devices"": [
{
""devicePath"": ""<Keyboard>"",
""isOptional"": false,
""isOR"": false
},
{
""devicePath"": ""<Mouse>"",
""isOptional"": false,
""isOR"": false
}
]
}
]
}";
}
private string GenerateMobileInputActions()
{
return @"{
""name"": ""MobileInputActions"",
""maps"": [
{
""name"": ""Touch"",
""id"": ""a3f5ec22-78b5-4adb-b4f3-96d5f7d77825"",
""actions"": [
{
""name"": ""PrimaryTouch"",
""type"": ""PassThrough"",
""id"": ""f8b3e0a3-fb86-44da-9c65-8a5c67e5cf8d"",
""expectedControlType"": ""Touch"",
""processors"": """",
""interactions"": """"
},
{
""name"": ""SecondaryTouch"",
""type"": ""PassThrough"",
""id"": ""13b5bbfe-f03f-42e9-9c88-9f87ee72b299"",
""expectedControlType"": ""Touch"",
""processors"": """",
""interactions"": """"
}
],
""bindings"": [
{
""name"": """",
""id"": ""1a7e28fe-784f-4cd6-ba3b-15401a1f3246"",
""path"": ""<Touchscreen>/touch0"",
""interactions"": """",
""processors"": """",
""groups"": ""Touch"",
""action"": ""PrimaryTouch"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""4fca3ef6-1574-40c8-9f5f-3f0db643d1f5"",
""path"": ""<Touchscreen>/touch1"",
""interactions"": """",
""processors"": """",
""groups"": ""Touch"",
""action"": ""SecondaryTouch"",
""isComposite"": false,
""isPartOfComposite"": false
}
]
}
],
""controlSchemes"": [
{
""name"": ""Touch"",
""bindingGroup"": ""Touch"",
""devices"": [
{
""devicePath"": ""<Touchscreen>"",
""isOptional"": false,
""isOR"": false
}
]
}
]
}";
}
private string GenerateVRInputActions()
{
return @"{
""name"": ""VRInputActions"",
""maps"": [
{
""name"": ""VR"",
""id"": ""c95b2d34-7e4c-4f89-89cf-08eff0e4c5ef"",
""actions"": [
{
""name"": ""TriggerLeft"",
""type"": ""Button"",
""id"": ""4c9a2d34-7e4c-4f89-89cf-08eff0e4c5ef"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """"
},
{
""name"": ""TriggerRight"",
""type"": ""Button"",
""id"": ""5c9a2d34-7e4c-4f89-89cf-08eff0e4c5ef"",
""expectedControlType"": ""Button"",
""processors"": """",
""interactions"": """"
},
{
""name"": ""Move"",
""type"": ""Value"",
""id"": ""6c9a2d34-7e4c-4f89-89cf-08eff0e4c5ef"",
""expectedControlType"": ""Vector2"",
""processors"": """",
""interactions"": """"
}
],
""bindings"": [
{
""name"": """",
""id"": ""7c9a2d34-7e4c-4f89-89cf-08eff0e4c5ef"",
""path"": ""<XRController>{LeftHand}/triggerPressed"",
""interactions"": """",
""processors"": """",
""groups"": ""VR"",
""action"": ""TriggerLeft"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""8c9a2d34-7e4c-4f89-89cf-08eff0e4c5ef"",
""path"": ""<XRController>{RightHand}/triggerPressed"",
""interactions"": """",
""processors"": """",
""groups"": ""VR"",
""action"": ""TriggerRight"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""9c9a2d34-7e4c-4f89-89cf-08eff0e4c5ef"",
""path"": ""<XRController>{LeftHand}/primary2DAxis"",
""interactions"": """",
""processors"": """",
""groups"": ""VR"",
""action"": ""Move"",
""isComposite"": false,
""isPartOfComposite"": false
}
]
}
],
""controlSchemes"": [
{
""name"": ""VR"",
""bindingGroup"": ""VR"",
""devices"": [
{
""devicePath"": ""<XRController>{LeftHand}"",
""isOptional"": false,
""isOR"": false
},
{
""devicePath"": ""<XRController>{RightHand}"",
""isOptional"": false,
""isOR"": false
}
]
}
]
}";
}
private string CreateStateMachine(Dictionary<string, string> parameters)
{
try
{
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var stateMachineType = parameters.GetValueOrDefault("type", "Character");
var states = parameters.GetValueOrDefault("states", "Idle,Walk,Run,Jump,Attack").Split(',');
GameObject target = null;
if (!string.IsNullOrEmpty(targetObject))
{
target = GameObject.Find(targetObject);
}
if (target == null)
{
target = new GameObject($"{stateMachineType}StateMachine");
}
// StateMachineGenerate script
var scriptName = $"{stateMachineType}StateMachine";
var scriptContent = GenerateStateMachineScript(scriptName, states);
var scriptPath = $"Assets/Scripts/StateMachines/{scriptName}.cs";
var scriptDir = System.IO.Path.GetDirectoryName(scriptPath);
if (!System.IO.Directory.Exists(scriptDir))
{
System.IO.Directory.CreateDirectory(scriptDir);
}
System.IO.File.WriteAllText(scriptPath, scriptContent);
// Generate script for each state as well
foreach (var state in states)
{
var stateScriptPath = $"Assets/Scripts/StateMachines/States/{stateMachineType}{state}State.cs";
var stateScriptDir = System.IO.Path.GetDirectoryName(stateScriptPath);
if (!System.IO.Directory.Exists(stateScriptDir))
{
System.IO.Directory.CreateDirectory(stateScriptDir);
}
var stateScriptContent = GenerateStateScript($"{stateMachineType}{state}State", state);
System.IO.File.WriteAllText(stateScriptPath, stateScriptContent);
}
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created {stateMachineType} state machine with {states.Length} states",
targetObject = target.name,
scriptPath = scriptPath,
states = states,
type = stateMachineType
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string GenerateStateMachineScript(string className, string[] states)
{
var stateEnums = string.Join(",\n ", states);
var stateCases = "";
foreach (var state in states)
{
stateCases += $@"
case States.{state}:
// {state} state logic
break;
";
}
return $@"using UnityEngine;
using System.Collections;
public class {className} : MonoBehaviour
{{
public enum States
{{
{stateEnums}
}}
public States currentState = States.{states[0]};
private States previousState;
void Start()
{{
StartCoroutine(StateMachineRoutine());
}}
IEnumerator StateMachineRoutine()
{{
while (true)
{{
switch (currentState)
{{{stateCases}
}}
yield return null;
}}
}}
public void ChangeState(States newState)
{{
previousState = currentState;
currentState = newState;
OnStateChange(previousState, currentState);
}}
protected virtual void OnStateChange(States from, States to)
{{
SynLog.Info($""State changed from {{from}} to {{to}}"");
}}
}}";
}
private string GenerateStateScript(string className, string stateName)
{
return $@"using UnityEngine;
public class {className} : MonoBehaviour
{{
public void Enter()
{{
SynLog.Info(""Entering {stateName} state"");
// State enter logic
}}
public void Execute()
{{
// State update logic
}}
public void Exit()
{{
SynLog.Info(""Exiting {stateName} state"");
// State exit logic
}}
}}";
}
private string SetupInventorySystem(Dictionary<string, string> parameters)
{
try
{
var inventorySize = int.Parse(parameters.GetValueOrDefault("size", "20"));
var hasUI = bool.Parse(parameters.GetValueOrDefault("hasUI", "true"));
var stackable = bool.Parse(parameters.GetValueOrDefault("stackable", "true"));
// InventoryManagerCreate
var inventoryManager = new GameObject("InventoryManager");
// Generate Item class
var itemScriptPath = "Assets/Scripts/Inventory/Item.cs";
var itemScriptDir = System.IO.Path.GetDirectoryName(itemScriptPath);
if (!System.IO.Directory.Exists(itemScriptDir))
{
System.IO.Directory.CreateDirectory(itemScriptDir);
}
var itemScript = GenerateItemScript(stackable);
System.IO.File.WriteAllText(itemScriptPath, itemScript);
// InventoryGenerate script
var inventoryScriptPath = "Assets/Scripts/Inventory/Inventory.cs";
var inventoryScript = GenerateInventoryScript(inventorySize, stackable);
System.IO.File.WriteAllText(inventoryScriptPath, inventoryScript);
// UICreate
GameObject inventoryUI = null;
if (hasUI)
{
// CanvasCreate
var canvas = GameObject.Find("Canvas");
if (canvas == null)
{
canvas = new GameObject("Canvas");
canvas.AddComponent<Canvas>().renderMode = RenderMode.ScreenSpaceOverlay;
canvas.AddComponent<UnityEngine.UI.CanvasScaler>();
canvas.AddComponent<UnityEngine.UI.GraphicRaycaster>();
}
// InventoryPanel
inventoryUI = new GameObject("InventoryPanel");
inventoryUI.transform.parent = canvas.transform;
var rect = inventoryUI.AddComponent<RectTransform>();
rect.sizeDelta = new Vector2(400, 400);
rect.anchoredPosition = Vector2.zero;
var image = inventoryUI.AddComponent<UnityEngine.UI.Image>();
image.color = new Color(0.2f, 0.2f, 0.2f, 0.9f);
// GridLayout
var gridContainer = new GameObject("GridContainer");
gridContainer.transform.parent = inventoryUI.transform;
var gridRect = gridContainer.AddComponent<RectTransform>();
gridRect.sizeDelta = new Vector2(380, 380);
gridRect.anchoredPosition = Vector2.zero;
var gridLayout = gridContainer.AddComponent<UnityEngine.UI.GridLayoutGroup>();
gridLayout.cellSize = new Vector2(60, 60);
gridLayout.spacing = new Vector2(5, 5);
gridLayout.padding = new RectOffset(10, 10, 10, 10);
// InventorySlotPrefab
var slotPrefab = new GameObject("SlotPrefab");
slotPrefab.AddComponent<RectTransform>();
var slotImage = slotPrefab.AddComponent<UnityEngine.UI.Image>();
slotImage.color = new Color(0.3f, 0.3f, 0.3f, 1f);
// SlotCreate
for (int i = 0; i < inventorySize; i++)
{
var slot = GameObject.Instantiate(slotPrefab, gridContainer.transform);
slot.name = $"Slot_{i}";
}
GameObject.Destroy(slotPrefab);
inventoryUI.SetActive(false);
}
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = "Set up inventory system",
settings = new
{
size = inventorySize,
hasUI = hasUI,
stackable = stackable
},
scripts = new[]
{
itemScriptPath,
inventoryScriptPath
},
ui = hasUI ? new
{
created = true,
slots = inventorySize
} : null
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string GenerateItemScript(bool stackable)
{
return $@"using UnityEngine;
[System.Serializable]
public class Item
{{
public int id;
public string itemName;
public string description;
public Sprite icon;
public GameObject prefab;
{(stackable ? "public int maxStack = 99;" : "")}
public ItemType type;
public enum ItemType
{{
Weapon,
Armor,
Consumable,
Material,
KeyItem
}}
public Item(int id, string name, string desc, ItemType type)
{{
this.id = id;
this.itemName = name;
this.description = desc;
this.type = type;
}}
public virtual void Use()
{{
SynLog.Info($""Using {{itemName}}"");
}}
public virtual string GetTooltip()
{{
return $""{{itemName}}\\n{{description}}"";
}}
}}
[System.Serializable]
public class ItemStack
{{
public Item item;
public int count;
public ItemStack(Item item, int count = 1)
{{
this.item = item;
this.count = count;
}}
}}";
}
private string GenerateInventoryScript(int size, bool stackable)
{
return $@"using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class Inventory : MonoBehaviour
{{
[SerializeField] private int maxSlots = {size};
[SerializeField] private List<ItemStack> items = new List<ItemStack>();
public delegate void OnInventoryChanged();
public event OnInventoryChanged onInventoryChangedCallback;
private void Start()
{{
// Initialize empty slots
for (int i = 0; i < maxSlots; i++)
{{
items.Add(null);
}}
}}
public bool AddItem(Item item, int count = 1)
{{
if (item == null) return false;
{(stackable ? @"// Check for existing stack
var existingStack = items.FirstOrDefault(s => s != null && s.item.id == item.id && s.count < s.item.maxStack);
if (existingStack != null)
{
int spaceLeft = existingStack.item.maxStack - existingStack.count;
int toAdd = Mathf.Min(count, spaceLeft);
existingStack.count += toAdd;
count -= toAdd;
if (count <= 0)
{
onInventoryChangedCallback?.Invoke();
return true;
}
}" : "")}
// Find empty slot
for (int i = 0; i < maxSlots; i++)
{{
if (items[i] == null)
{{
items[i] = new ItemStack(item, count);
onInventoryChangedCallback?.Invoke();
return true;
}}
}}
SynLog.Info(""Inventory full!"");
return false;
}}
public bool RemoveItem(Item item, int count = 1)
{{
var stack = items.FirstOrDefault(s => s != null && s.item.id == item.id);
if (stack != null)
{{
stack.count -= count;
if (stack.count <= 0)
{{
items[items.IndexOf(stack)] = null;
}}
onInventoryChangedCallback?.Invoke();
return true;
}}
return false;
}}
public ItemStack GetItemAt(int index)
{{
if (index >= 0 && index < items.Count)
return items[index];
return null;
}}
public void SwapItems(int index1, int index2)
{{
if (index1 >= 0 && index1 < items.Count && index2 >= 0 && index2 < items.Count)
{{
var temp = items[index1];
items[index1] = items[index2];
items[index2] = temp;
onInventoryChangedCallback?.Invoke();
}}
}}
public int GetItemCount(Item item)
{{
return items.Where(s => s != null && s.item.id == item.id).Sum(s => s.count);
}}
public bool HasItem(Item item, int count = 1)
{{
return GetItemCount(item) >= count;
}}
public List<ItemStack> GetAllItems()
{{
return items.Where(s => s != null).ToList();
}}
}}";
}
// ===== Prototyping features =====
private string CreateGameTemplate(Dictionary<string, string> parameters)
{
try
{
var genre = parameters.GetValueOrDefault("genre", "FPS");
var gameName = parameters.GetValueOrDefault("name", $"{genre}Prototype");
var includeUI = bool.Parse(parameters.GetValueOrDefault("includeUI", "true"));
var includeAudio = bool.Parse(parameters.GetValueOrDefault("includeAudio", "true"));
// RootGameObjectCreate
var gameRoot = new GameObject($"{gameName}_Game");
var createdObjects = new List<GameObject>();
createdObjects.Add(gameRoot);
switch (genre)
{
case "FPS":
createdObjects.AddRange(CreateFPSTemplate(gameRoot));
break;
case "Platformer":
createdObjects.AddRange(CreatePlatformerTemplate(gameRoot));
break;
case "RPG":
createdObjects.AddRange(CreateRPGTemplate(gameRoot));
break;
case "Puzzle":
createdObjects.AddRange(CreatePuzzleTemplate(gameRoot));
break;
case "Racing":
createdObjects.AddRange(CreateRacingTemplate(gameRoot));
break;
case "Strategy":
createdObjects.AddRange(CreateStrategyTemplate(gameRoot));
break;
}
// UICreate
if (includeUI)
{
CreateGenericUI(genre);
}
// AudioSettings
if (includeAudio)
{
SetupGenericAudio(gameRoot);
}
// SceneSettings
RenderSettings.fog = true;
RenderSettings.fogMode = FogMode.Exponential;
RenderSettings.fogDensity = 0.01f;
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created {genre} game template",
gameName = gameName,
genre = genre,
objectsCreated = createdObjects.Select(o => o.name).ToList(),
settings = new
{
includeUI = includeUI,
includeAudio = includeAudio
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
// ===== Professional Game Templates =====
// Based on industry-standard game architecture patterns
private List<GameObject> CreateFPSTemplate(GameObject parent)
{
var objects = new List<GameObject>();
// ===== Player Setup =====
var player = new GameObject("FPSPlayer");
player.transform.parent = parent.transform;
player.transform.position = new Vector3(0, 0, -8);
player.tag = "Player";
player.layer = LayerMask.NameToLayer("Default");
// Character Controller with realistic FPS settings
var controller = player.AddComponent<CharacterController>();
controller.height = 1.8f;
controller.radius = 0.4f;
controller.center = new Vector3(0, 0.9f, 0);
controller.skinWidth = 0.08f;
controller.stepOffset = 0.3f;
controller.slopeLimit = 45f;
objects.Add(player);
// Camera Rig (for smooth camera movement)
var cameraRig = new GameObject("CameraRig");
cameraRig.transform.parent = player.transform;
cameraRig.transform.localPosition = new Vector3(0, 1.6f, 0); // Eye height
objects.Add(cameraRig);
// Main Camera
var cameraObj = new GameObject("MainCamera");
cameraObj.transform.parent = cameraRig.transform;
cameraObj.transform.localPosition = Vector3.zero;
cameraObj.tag = "MainCamera";
var cam = cameraObj.AddComponent<Camera>();
cam.fieldOfView = 70f; // Standard FPS FOV
cam.nearClipPlane = 0.1f;
cam.farClipPlane = 500f;
cameraObj.AddComponent<AudioListener>();
objects.Add(cameraObj);
// Weapon System
var weaponHolder = new GameObject("WeaponHolder");
weaponHolder.transform.parent = cameraObj.transform;
weaponHolder.transform.localPosition = new Vector3(0.25f, -0.2f, 0.4f);
objects.Add(weaponHolder);
// Primary Weapon (Rifle placeholder)
var rifle = CreateWeaponModel("Rifle", weaponHolder.transform);
rifle.transform.localScale = new Vector3(0.08f, 0.08f, 0.6f);
rifle.transform.localPosition = Vector3.zero;
objects.Add(rifle);
// Muzzle Flash Point
var muzzle = new GameObject("MuzzlePoint");
muzzle.transform.parent = rifle.transform;
muzzle.transform.localPosition = new Vector3(0, 0, 0.5f);
objects.Add(muzzle);
// ===== Level Architecture =====
var level = new GameObject("Level");
level.transform.parent = parent.transform;
objects.Add(level);
// Ground with material
var ground = CreatePrimitiveWithMaterial(PrimitiveType.Plane, new Color(0.3f, 0.35f, 0.3f));
ground.name = "Ground";
ground.transform.parent = level.transform;
ground.transform.localScale = new Vector3(15, 1, 15);
ground.isStatic = true;
objects.Add(ground);
// Create room structure with cover
CreateFPSRoomStructure(level.transform, objects);
// ===== Cover Objects =====
var covers = new GameObject("CoverObjects");
covers.transform.parent = level.transform;
objects.Add(covers);
// Create various cover types
CreateCoverObject(covers.transform, "Crate_1", new Vector3(-3, 0.5f, 2), new Vector3(1, 1, 1), objects);
CreateCoverObject(covers.transform, "Crate_2", new Vector3(4, 0.5f, -1), new Vector3(1.5f, 1, 1), objects);
CreateCoverObject(covers.transform, "Barrier_1", new Vector3(0, 0.6f, 5), new Vector3(3, 1.2f, 0.3f), objects);
CreateCoverObject(covers.transform, "Pillar_1", new Vector3(-6, 1.5f, 0), new Vector3(0.8f, 3, 0.8f), objects);
CreateCoverObject(covers.transform, "Pillar_2", new Vector3(6, 1.5f, 0), new Vector3(0.8f, 3, 0.8f), objects);
// ===== Spawn System =====
var spawnSystem = new GameObject("SpawnSystem");
spawnSystem.transform.parent = parent.transform;
objects.Add(spawnSystem);
// Player spawn points
var playerSpawns = new GameObject("PlayerSpawns");
playerSpawns.transform.parent = spawnSystem.transform;
CreateSpawnPoint(playerSpawns.transform, "PlayerSpawn_1", new Vector3(-10, 0, -10), objects);
CreateSpawnPoint(playerSpawns.transform, "PlayerSpawn_2", new Vector3(10, 0, -10), objects);
objects.Add(playerSpawns);
// Enemy spawn points
var enemySpawns = new GameObject("EnemySpawns");
enemySpawns.transform.parent = spawnSystem.transform;
CreateSpawnPoint(enemySpawns.transform, "EnemySpawn_1", new Vector3(-8, 0, 8), objects);
CreateSpawnPoint(enemySpawns.transform, "EnemySpawn_2", new Vector3(8, 0, 8), objects);
CreateSpawnPoint(enemySpawns.transform, "EnemySpawn_3", new Vector3(0, 0, 10), objects);
objects.Add(enemySpawns);
// ===== Pickups =====
var pickups = new GameObject("Pickups");
pickups.transform.parent = parent.transform;
objects.Add(pickups);
CreatePickup(pickups.transform, "HealthPack", new Vector3(-5, 0.3f, 5), Color.green, objects);
CreatePickup(pickups.transform, "AmmoPack", new Vector3(5, 0.3f, 5), Color.yellow, objects);
CreatePickup(pickups.transform, "ArmorPack", new Vector3(0, 0.3f, -5), Color.blue, objects);
// ===== Lighting =====
SetupFPSLighting(parent.transform);
return objects;
}
private void CreateFPSRoomStructure(Transform parent, List<GameObject> objects)
{
float roomSize = 15f;
float wallHeight = 4f;
float wallThickness = 0.5f;
// Walls
var walls = new GameObject("Walls");
walls.transform.parent = parent;
walls.transform.position = Vector3.zero;
objects.Add(walls);
// North wall with openings
CreateWallSegment(walls.transform, "NorthWall_L", new Vector3(-10, wallHeight / 2, roomSize), new Vector3(5, wallHeight, wallThickness), objects);
CreateWallSegment(walls.transform, "NorthWall_R", new Vector3(10, wallHeight / 2, roomSize), new Vector3(5, wallHeight, wallThickness), objects);
// South wall
CreateWallSegment(walls.transform, "SouthWall", new Vector3(0, wallHeight / 2, -roomSize), new Vector3(roomSize * 2, wallHeight, wallThickness), objects);
// East/West walls with windows
CreateWallSegment(walls.transform, "EastWall_Lower", new Vector3(roomSize, 1f, 0), new Vector3(wallThickness, 2f, roomSize * 2), objects);
CreateWallSegment(walls.transform, "EastWall_Upper", new Vector3(roomSize, 3.5f, 0), new Vector3(wallThickness, 1f, roomSize * 2), objects);
CreateWallSegment(walls.transform, "WestWall_Lower", new Vector3(-roomSize, 1f, 0), new Vector3(wallThickness, 2f, roomSize * 2), objects);
CreateWallSegment(walls.transform, "WestWall_Upper", new Vector3(-roomSize, 3.5f, 0), new Vector3(wallThickness, 1f, roomSize * 2), objects);
}
private void CreateWallSegment(Transform parent, string name, Vector3 position, Vector3 scale, List<GameObject> objects)
{
var wall = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.6f, 0.6f, 0.65f));
wall.name = name;
wall.transform.parent = parent;
wall.transform.position = position;
wall.transform.localScale = scale;
wall.isStatic = true;
objects.Add(wall);
}
private void CreateCoverObject(Transform parent, string name, Vector3 position, Vector3 scale, List<GameObject> objects)
{
var cover = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.4f, 0.35f, 0.3f));
cover.name = name;
cover.transform.parent = parent;
cover.transform.position = position;
cover.transform.localScale = scale;
cover.tag = "Cover";
objects.Add(cover);
}
private void CreateSpawnPoint(Transform parent, string name, Vector3 position, List<GameObject> objects)
{
var spawn = new GameObject(name);
spawn.transform.parent = parent;
spawn.transform.position = position;
// Visual indicator (editor only in real implementation)
var indicator = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, Color.cyan);
indicator.name = "SpawnIndicator";
indicator.transform.parent = spawn.transform;
indicator.transform.localPosition = new Vector3(0, 0.5f, 0);
indicator.transform.localScale = new Vector3(0.3f, 0.3f, 0.3f);
indicator.GetComponent<Collider>().enabled = false;
objects.Add(spawn);
}
private void CreatePickup(Transform parent, string name, Vector3 position, Color color, List<GameObject> objects)
{
var pickup = new GameObject(name);
pickup.transform.parent = parent;
pickup.transform.position = position;
pickup.tag = "Pickup";
var visual = CreatePrimitiveWithMaterial(PrimitiveType.Cube, color);
visual.name = "Visual";
visual.transform.parent = pickup.transform;
visual.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
// Make trigger
var collider = visual.GetComponent<Collider>();
if (collider != null)
{
collider.isTrigger = true;
}
// Add slight hover animation position
pickup.transform.position += Vector3.up * 0.1f;
objects.Add(pickup);
}
private GameObject CreateWeaponModel(string name, Transform parent)
{
var weapon = new GameObject(name);
weapon.transform.parent = parent;
// Body
var body = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.2f, 0.2f, 0.25f));
body.name = "Body";
body.transform.parent = weapon.transform;
body.transform.localPosition = Vector3.zero;
body.transform.localScale = new Vector3(0.06f, 0.1f, 0.5f);
body.GetComponent<Collider>().enabled = false;
// Handle
var handle = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.15f, 0.12f, 0.1f));
handle.name = "Handle";
handle.transform.parent = weapon.transform;
handle.transform.localPosition = new Vector3(0, -0.08f, -0.1f);
handle.transform.localScale = new Vector3(0.04f, 0.12f, 0.08f);
handle.GetComponent<Collider>().enabled = false;
return weapon;
}
private void SetupFPSLighting(Transform parent)
{
// Main directional light
var sunLight = GameObject.Find("Directional Light");
if (sunLight == null)
{
sunLight = new GameObject("Directional Light");
var light = sunLight.AddComponent<Light>();
light.type = LightType.Directional;
}
var sun = sunLight.GetComponent<Light>();
sun.color = new Color(1f, 0.95f, 0.85f);
sun.intensity = 1.0f;
sun.shadows = LightShadows.Soft;
sun.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
// Ambient
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.5f, 0.55f, 0.6f);
RenderSettings.ambientEquatorColor = new Color(0.4f, 0.42f, 0.45f);
RenderSettings.ambientGroundColor = new Color(0.25f, 0.25f, 0.3f);
}
private List<GameObject> CreatePlatformerTemplate(GameObject parent)
{
var objects = new List<GameObject>();
// ===== Player Setup =====
var player = new GameObject("PlatformerPlayer");
player.transform.parent = parent.transform;
player.transform.position = new Vector3(-8, 2, 0);
player.tag = "Player";
// Visual representation
var playerVisual = CreatePrimitiveWithMaterial(PrimitiveType.Capsule, new Color(0.2f, 0.6f, 1f));
playerVisual.name = "PlayerVisual";
playerVisual.transform.parent = player.transform;
playerVisual.transform.localPosition = Vector3.zero;
playerVisual.transform.localScale = new Vector3(0.8f, 1f, 0.8f);
GameObject.DestroyImmediate(playerVisual.GetComponent<CapsuleCollider>());
// 2D Physics
var rb2d = player.AddComponent<Rigidbody2D>();
rb2d.gravityScale = 3f;
rb2d.freezeRotation = true;
rb2d.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
var playerCollider = player.AddComponent<BoxCollider2D>();
playerCollider.size = new Vector2(0.8f, 2f);
objects.Add(player);
// ===== Camera Setup =====
var cameraObj = new GameObject("MainCamera");
cameraObj.tag = "MainCamera";
var cam = cameraObj.AddComponent<Camera>();
cam.orthographic = true;
cam.orthographicSize = 8f;
cam.transform.position = new Vector3(0, 3, -10);
cam.backgroundColor = new Color(0.4f, 0.6f, 0.9f);
cameraObj.AddComponent<AudioListener>();
objects.Add(cameraObj);
// ===== Level Structure =====
var level = new GameObject("Level");
level.transform.parent = parent.transform;
objects.Add(level);
// Ground platform (long base)
CreatePlatform2D(level.transform, "Ground", new Vector3(0, -2, 0), new Vector3(50, 1, 1), new Color(0.4f, 0.3f, 0.2f), objects);
// Floating platforms - progressive difficulty layout
// Starting area (easy jumps)
CreatePlatform2D(level.transform, "Platform_1", new Vector3(-5, 0, 0), new Vector3(4, 0.5f, 1), new Color(0.3f, 0.7f, 0.3f), objects);
CreatePlatform2D(level.transform, "Platform_2", new Vector3(-1, 2, 0), new Vector3(3, 0.5f, 1), new Color(0.3f, 0.7f, 0.3f), objects);
CreatePlatform2D(level.transform, "Platform_3", new Vector3(3, 3.5f, 0), new Vector3(3, 0.5f, 1), new Color(0.3f, 0.7f, 0.3f), objects);
// Middle section (medium difficulty)
CreatePlatform2D(level.transform, "Platform_4", new Vector3(7, 2, 0), new Vector3(2, 0.5f, 1), new Color(0.7f, 0.7f, 0.3f), objects);
CreatePlatform2D(level.transform, "Platform_5", new Vector3(10, 4, 0), new Vector3(2, 0.5f, 1), new Color(0.7f, 0.7f, 0.3f), objects);
CreatePlatform2D(level.transform, "Platform_6", new Vector3(13, 2.5f, 0), new Vector3(2.5f, 0.5f, 1), new Color(0.7f, 0.7f, 0.3f), objects);
// Hard section (small platforms, bigger gaps)
CreatePlatform2D(level.transform, "Platform_7", new Vector3(17, 5, 0), new Vector3(1.5f, 0.5f, 1), new Color(0.7f, 0.3f, 0.3f), objects);
CreatePlatform2D(level.transform, "Platform_8", new Vector3(20, 6.5f, 0), new Vector3(1.5f, 0.5f, 1), new Color(0.7f, 0.3f, 0.3f), objects);
CreatePlatform2D(level.transform, "Platform_9", new Vector3(23, 5, 0), new Vector3(1.5f, 0.5f, 1), new Color(0.7f, 0.3f, 0.3f), objects);
// Moving platform placeholder
var movingPlatform = CreatePlatform2D(level.transform, "MovingPlatform", new Vector3(15, 1, 0), new Vector3(3, 0.5f, 1), new Color(0.5f, 0.3f, 0.7f), objects);
// ===== Hazards =====
var hazards = new GameObject("Hazards");
hazards.transform.parent = parent.transform;
objects.Add(hazards);
// Spikes
CreateHazard2D(hazards.transform, "Spikes_1", new Vector3(5, -1.3f, 0), new Vector3(2, 0.5f, 1), objects);
CreateHazard2D(hazards.transform, "Spikes_2", new Vector3(12, -1.3f, 0), new Vector3(3, 0.5f, 1), objects);
// Pit (death zone)
var deathZone = new GameObject("DeathZone");
deathZone.transform.parent = hazards.transform;
deathZone.transform.position = new Vector3(0, -5, 0);
var deathCollider = deathZone.AddComponent<BoxCollider2D>();
deathCollider.size = new Vector2(100, 2);
deathCollider.isTrigger = true;
objects.Add(deathZone);
// ===== Collectibles =====
var collectibles = new GameObject("Collectibles");
collectibles.transform.parent = parent.transform;
objects.Add(collectibles);
// Coins along the path
Vector3[] coinPositions = {
new Vector3(-5, 1.5f, 0), new Vector3(-3, 1.5f, 0),
new Vector3(0, 3.5f, 0), new Vector3(2, 3.5f, 0),
new Vector3(5, 5f, 0), new Vector3(7, 3.5f, 0),
new Vector3(10, 5.5f, 0), new Vector3(13, 4f, 0),
new Vector3(17, 6.5f, 0), new Vector3(20, 8f, 0),
new Vector3(23, 6.5f, 0)
};
for (int i = 0; i < coinPositions.Length; i++)
{
CreateCoin2D(collectibles.transform, $"Coin_{i}", coinPositions[i], objects);
}
// Power-up
CreatePowerUp2D(collectibles.transform, "DoubleJump", new Vector3(10, 6, 0), new Color(0.9f, 0.3f, 0.9f), objects);
CreatePowerUp2D(collectibles.transform, "SpeedBoost", new Vector3(18, 7, 0), new Color(0.3f, 0.9f, 0.9f), objects);
// ===== Goal =====
var goal = new GameObject("Goal");
goal.transform.parent = parent.transform;
goal.transform.position = new Vector3(25, 0, 0);
var goalVisual = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(1f, 0.8f, 0.2f));
goalVisual.name = "GoalFlag";
goalVisual.transform.parent = goal.transform;
goalVisual.transform.localScale = new Vector3(0.5f, 3f, 0.5f);
GameObject.DestroyImmediate(goalVisual.GetComponent<BoxCollider>());
var goalTrigger = goal.AddComponent<BoxCollider2D>();
goalTrigger.size = new Vector2(2, 4);
goalTrigger.isTrigger = true;
objects.Add(goal);
// ===== Background Elements =====
CreatePlatformerBackground(parent.transform, objects);
// ===== Lighting =====
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.8f, 0.85f, 0.9f);
return objects;
}
private GameObject CreatePlatform2D(Transform parent, string name, Vector3 position, Vector3 scale, Color color, List<GameObject> objects)
{
var platform = CreatePrimitiveWithMaterial(PrimitiveType.Cube, color);
platform.name = name;
platform.transform.parent = parent;
platform.transform.position = position;
platform.transform.localScale = scale;
platform.isStatic = true;
// Replace 3D collider with 2D
GameObject.DestroyImmediate(platform.GetComponent<BoxCollider>());
var collider2d = platform.AddComponent<BoxCollider2D>();
collider2d.size = new Vector2(1, 1);
objects.Add(platform);
return platform;
}
private void CreateHazard2D(Transform parent, string name, Vector3 position, Vector3 scale, List<GameObject> objects)
{
var hazard = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.8f, 0.2f, 0.2f));
hazard.name = name;
hazard.transform.parent = parent;
hazard.transform.position = position;
hazard.transform.localScale = scale;
hazard.tag = "Hazard";
GameObject.DestroyImmediate(hazard.GetComponent<BoxCollider>());
var collider2d = hazard.AddComponent<BoxCollider2D>();
collider2d.isTrigger = true;
objects.Add(hazard);
}
private void CreateCoin2D(Transform parent, string name, Vector3 position, List<GameObject> objects)
{
var coin = new GameObject(name);
coin.transform.parent = parent;
coin.transform.position = position;
coin.tag = "Collectible";
var visual = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, new Color(1f, 0.85f, 0.2f));
visual.transform.parent = coin.transform;
visual.transform.localScale = new Vector3(0.5f, 0.05f, 0.5f);
visual.transform.localRotation = Quaternion.Euler(90, 0, 0);
GameObject.DestroyImmediate(visual.GetComponent<CapsuleCollider>());
var collider2d = coin.AddComponent<CircleCollider2D>();
collider2d.radius = 0.3f;
collider2d.isTrigger = true;
objects.Add(coin);
}
private void CreatePowerUp2D(Transform parent, string name, Vector3 position, Color color, List<GameObject> objects)
{
var powerup = new GameObject(name);
powerup.transform.parent = parent;
powerup.transform.position = position;
powerup.tag = "PowerUp";
var visual = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, color);
visual.transform.parent = powerup.transform;
visual.transform.localScale = new Vector3(0.6f, 0.6f, 0.6f);
GameObject.DestroyImmediate(visual.GetComponent<SphereCollider>());
var collider2d = powerup.AddComponent<CircleCollider2D>();
collider2d.radius = 0.4f;
collider2d.isTrigger = true;
objects.Add(powerup);
}
private void CreatePlatformerBackground(Transform parent, List<GameObject> objects)
{
var bg = new GameObject("Background");
bg.transform.parent = parent;
objects.Add(bg);
// Sky gradient (far background)
var sky = CreatePrimitiveWithMaterial(PrimitiveType.Quad, new Color(0.5f, 0.7f, 0.95f));
sky.name = "Sky";
sky.transform.parent = bg.transform;
sky.transform.position = new Vector3(10, 5, 20);
sky.transform.localScale = new Vector3(80, 30, 1);
sky.GetComponent<MeshCollider>().enabled = false;
// Mountains (parallax layer 1)
for (int i = 0; i < 5; i++)
{
var mountain = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.4f, 0.5f, 0.6f));
mountain.name = $"Mountain_{i}";
mountain.transform.parent = bg.transform;
mountain.transform.position = new Vector3(i * 15 - 20, 2, 10);
mountain.transform.localScale = new Vector3(8, 8 + Random.Range(0, 4), 1);
mountain.transform.rotation = Quaternion.Euler(0, 0, 45);
mountain.GetComponent<BoxCollider>().enabled = false;
}
// Trees (parallax layer 2)
for (int i = 0; i < 10; i++)
{
var tree = new GameObject($"Tree_{i}");
tree.transform.parent = bg.transform;
tree.transform.position = new Vector3(i * 8 - 30, -0.5f, 5);
var trunk = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.4f, 0.25f, 0.15f));
trunk.transform.parent = tree.transform;
trunk.transform.localScale = new Vector3(0.5f, 2f, 0.5f);
trunk.GetComponent<BoxCollider>().enabled = false;
var leaves = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, new Color(0.2f, 0.5f, 0.2f));
leaves.transform.parent = tree.transform;
leaves.transform.localPosition = new Vector3(0, 1.5f, 0);
leaves.transform.localScale = new Vector3(2f, 2f, 2f);
leaves.GetComponent<SphereCollider>().enabled = false;
}
}
private List<GameObject> CreateRPGTemplate(GameObject parent)
{
var objects = new List<GameObject>();
// ===== Player Character =====
var player = new GameObject("RPGHero");
player.transform.parent = parent.transform;
player.transform.position = new Vector3(0, 0, 0);
player.tag = "Player";
// Player visual (humanoid placeholder)
var playerBody = CreatePrimitiveWithMaterial(PrimitiveType.Capsule, new Color(0.3f, 0.5f, 0.8f));
playerBody.name = "Body";
playerBody.transform.parent = player.transform;
playerBody.transform.localScale = new Vector3(0.8f, 1f, 0.8f);
playerBody.transform.localPosition = new Vector3(0, 1f, 0);
var controller = player.AddComponent<CharacterController>();
controller.height = 2f;
controller.radius = 0.4f;
controller.center = new Vector3(0, 1f, 0);
objects.Add(player);
// ===== Camera Rig (Isometric/Third Person) =====
var cameraRig = new GameObject("CameraRig");
cameraRig.transform.parent = parent.transform;
cameraRig.transform.position = player.transform.position;
objects.Add(cameraRig);
var cameraPivot = new GameObject("CameraPivot");
cameraPivot.transform.parent = cameraRig.transform;
cameraPivot.transform.localRotation = Quaternion.Euler(45f, 45f, 0f);
objects.Add(cameraPivot);
var cameraObj = new GameObject("MainCamera");
cameraObj.transform.parent = cameraPivot.transform;
cameraObj.transform.localPosition = new Vector3(0, 0, -15f);
cameraObj.tag = "MainCamera";
var cam = cameraObj.AddComponent<Camera>();
cam.fieldOfView = 50f;
cameraObj.AddComponent<AudioListener>();
objects.Add(cameraObj);
// ===== World Environment =====
var world = new GameObject("World");
world.transform.parent = parent.transform;
objects.Add(world);
// Main terrain
var terrain = CreatePrimitiveWithMaterial(PrimitiveType.Plane, new Color(0.35f, 0.55f, 0.25f));
terrain.name = "Terrain";
terrain.transform.parent = world.transform;
terrain.transform.localScale = new Vector3(10, 1, 10);
terrain.isStatic = true;
objects.Add(terrain);
// ===== Town =====
var town = new GameObject("Town");
town.transform.parent = world.transform;
town.transform.position = new Vector3(0, 0, 10);
objects.Add(town);
// Town square
var townSquare = CreatePrimitiveWithMaterial(PrimitiveType.Plane, new Color(0.6f, 0.55f, 0.45f));
townSquare.name = "TownSquare";
townSquare.transform.parent = town.transform;
townSquare.transform.localScale = new Vector3(2, 1, 2);
objects.Add(townSquare);
// Buildings with purpose
CreateRPGBuilding(town.transform, "Inn", new Vector3(-8, 0, 15), new Color(0.7f, 0.5f, 0.3f), objects);
CreateRPGBuilding(town.transform, "Blacksmith", new Vector3(8, 0, 15), new Color(0.4f, 0.4f, 0.45f), objects);
CreateRPGBuilding(town.transform, "MagicShop", new Vector3(-8, 0, 5), new Color(0.5f, 0.3f, 0.6f), objects);
CreateRPGBuilding(town.transform, "GeneralStore", new Vector3(8, 0, 5), new Color(0.6f, 0.6f, 0.5f), objects);
CreateRPGBuilding(town.transform, "TownHall", new Vector3(0, 0, 20), new Color(0.5f, 0.5f, 0.6f), objects, 1.5f);
// Town fountain
var fountain = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, new Color(0.5f, 0.5f, 0.55f));
fountain.name = "Fountain";
fountain.transform.parent = town.transform;
fountain.transform.position = new Vector3(0, 0.5f, 10);
fountain.transform.localScale = new Vector3(3, 0.5f, 3);
objects.Add(fountain);
// ===== NPCs =====
var npcs = new GameObject("NPCs");
npcs.transform.parent = parent.transform;
objects.Add(npcs);
CreateRPGNPC(npcs.transform, "QuestGiver_Elder", new Vector3(0, 0, 18), new Color(0.6f, 0.4f, 0.3f), "!", objects);
CreateRPGNPC(npcs.transform, "Merchant", new Vector3(7, 0, 6), new Color(0.4f, 0.6f, 0.4f), "$", objects);
CreateRPGNPC(npcs.transform, "Blacksmith_NPC", new Vector3(9, 0, 14), new Color(0.5f, 0.4f, 0.3f), "", objects);
CreateRPGNPC(npcs.transform, "Innkeeper", new Vector3(-7, 0, 14), new Color(0.6f, 0.5f, 0.4f), "", objects);
CreateRPGNPC(npcs.transform, "Mage", new Vector3(-7, 0, 6), new Color(0.4f, 0.3f, 0.6f), "", objects);
CreateRPGNPC(npcs.transform, "Guard_1", new Vector3(-3, 0, 25), new Color(0.5f, 0.5f, 0.6f), "", objects);
CreateRPGNPC(npcs.transform, "Guard_2", new Vector3(3, 0, 25), new Color(0.5f, 0.5f, 0.6f), "", objects);
// ===== Enemies in wilderness =====
var enemies = new GameObject("Enemies");
enemies.transform.parent = parent.transform;
objects.Add(enemies);
CreateRPGEnemy(enemies.transform, "Goblin_1", new Vector3(-20, 0, -15), new Color(0.4f, 0.6f, 0.3f), 0.8f, objects);
CreateRPGEnemy(enemies.transform, "Goblin_2", new Vector3(-22, 0, -18), new Color(0.4f, 0.6f, 0.3f), 0.8f, objects);
CreateRPGEnemy(enemies.transform, "Wolf_1", new Vector3(25, 0, -10), new Color(0.5f, 0.5f, 0.5f), 0.7f, objects);
CreateRPGEnemy(enemies.transform, "Bandit_1", new Vector3(30, 0, 0), new Color(0.5f, 0.3f, 0.3f), 1f, objects);
CreateRPGEnemy(enemies.transform, "Skeleton_1", new Vector3(-30, 0, 5), new Color(0.9f, 0.9f, 0.85f), 1f, objects);
// ===== Treasure Chests =====
var treasures = new GameObject("Treasures");
treasures.transform.parent = parent.transform;
objects.Add(treasures);
CreateTreasureChest(treasures.transform, "Chest_1", new Vector3(-25, 0, -20), objects);
CreateTreasureChest(treasures.transform, "Chest_2", new Vector3(35, 0, 5), objects);
CreateTreasureChest(treasures.transform, "Chest_3", new Vector3(0, 0, -35), objects);
// ===== Dungeon Entrance =====
var dungeonEntrance = new GameObject("DungeonEntrance");
dungeonEntrance.transform.parent = world.transform;
dungeonEntrance.transform.position = new Vector3(-35, 0, -25);
var dungeonDoor = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.3f, 0.25f, 0.2f));
dungeonDoor.name = "DungeonDoor";
dungeonDoor.transform.parent = dungeonEntrance.transform;
dungeonDoor.transform.localScale = new Vector3(4, 5, 1);
dungeonDoor.transform.localPosition = new Vector3(0, 2.5f, 0);
var dungeonFrame = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.4f, 0.4f, 0.45f));
dungeonFrame.transform.parent = dungeonEntrance.transform;
dungeonFrame.transform.localScale = new Vector3(6, 6, 0.5f);
dungeonFrame.transform.localPosition = new Vector3(0, 3f, 0.3f);
objects.Add(dungeonEntrance);
// ===== Environment Props =====
CreateRPGEnvironment(world.transform, objects);
// ===== Lighting =====
SetupRPGLighting();
return objects;
}
private void CreateRPGBuilding(Transform parent, string name, Vector3 position, Color wallColor, List<GameObject> objects, float sizeMultiplier = 1f)
{
var building = new GameObject(name);
building.transform.parent = parent;
building.transform.position = position;
float baseWidth = 5f * sizeMultiplier;
float baseHeight = 4f * sizeMultiplier;
float baseDepth = 5f * sizeMultiplier;
// Walls
var walls = CreatePrimitiveWithMaterial(PrimitiveType.Cube, wallColor);
walls.name = "Walls";
walls.transform.parent = building.transform;
walls.transform.localPosition = new Vector3(0, baseHeight / 2, 0);
walls.transform.localScale = new Vector3(baseWidth, baseHeight, baseDepth);
walls.isStatic = true;
// Roof
var roof = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.5f, 0.25f, 0.15f));
roof.name = "Roof";
roof.transform.parent = building.transform;
roof.transform.localPosition = new Vector3(0, baseHeight + 0.5f, 0);
roof.transform.localScale = new Vector3(baseWidth + 1, 1f, baseDepth + 1);
roof.transform.localRotation = Quaternion.Euler(0, 0, 0);
roof.isStatic = true;
// Door
var door = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.4f, 0.25f, 0.15f));
door.name = "Door";
door.transform.parent = building.transform;
door.transform.localPosition = new Vector3(0, 1f, -baseDepth / 2 - 0.05f);
door.transform.localScale = new Vector3(1.2f, 2f, 0.1f);
// Interaction trigger
var trigger = new GameObject("InteractionTrigger");
trigger.transform.parent = building.transform;
trigger.transform.localPosition = new Vector3(0, 1f, -baseDepth / 2 - 1f);
var col = trigger.AddComponent<BoxCollider>();
col.size = new Vector3(2, 2, 2);
col.isTrigger = true;
objects.Add(building);
}
private void CreateRPGNPC(Transform parent, string name, Vector3 position, Color color, string questIndicator, List<GameObject> objects)
{
var npc = new GameObject(name);
npc.transform.parent = parent;
npc.transform.position = position;
npc.tag = "NPC";
// Body
var body = CreatePrimitiveWithMaterial(PrimitiveType.Capsule, color);
body.name = "Body";
body.transform.parent = npc.transform;
body.transform.localPosition = new Vector3(0, 1f, 0);
body.transform.localScale = new Vector3(0.7f, 1f, 0.7f);
// Interaction collider
var interactCol = npc.AddComponent<SphereCollider>();
interactCol.radius = 2f;
interactCol.isTrigger = true;
interactCol.center = new Vector3(0, 1f, 0);
// Quest indicator if has quest
if (!string.IsNullOrEmpty(questIndicator))
{
var indicator = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, Color.yellow);
indicator.name = "QuestIndicator";
indicator.transform.parent = npc.transform;
indicator.transform.localPosition = new Vector3(0, 2.5f, 0);
indicator.transform.localScale = new Vector3(0.3f, 0.3f, 0.3f);
indicator.GetComponent<SphereCollider>().enabled = false;
}
objects.Add(npc);
}
private void CreateRPGEnemy(Transform parent, string name, Vector3 position, Color color, float scale, List<GameObject> objects)
{
var enemy = new GameObject(name);
enemy.transform.parent = parent;
enemy.transform.position = position;
enemy.tag = "Enemy";
var body = CreatePrimitiveWithMaterial(PrimitiveType.Capsule, color);
body.name = "Body";
body.transform.parent = enemy.transform;
body.transform.localPosition = new Vector3(0, scale, 0);
body.transform.localScale = new Vector3(0.6f * scale, scale, 0.6f * scale);
// Combat collider
var col = enemy.AddComponent<CapsuleCollider>();
col.height = 2f * scale;
col.radius = 0.4f * scale;
col.center = new Vector3(0, scale, 0);
// Aggro range
var aggroTrigger = new GameObject("AggroRange");
aggroTrigger.transform.parent = enemy.transform;
var aggroCol = aggroTrigger.AddComponent<SphereCollider>();
aggroCol.radius = 8f;
aggroCol.isTrigger = true;
objects.Add(enemy);
}
private void CreateTreasureChest(Transform parent, string name, Vector3 position, List<GameObject> objects)
{
var chest = new GameObject(name);
chest.transform.parent = parent;
chest.transform.position = position;
chest.tag = "Interactable";
var chestBase = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.5f, 0.35f, 0.2f));
chestBase.name = "ChestBase";
chestBase.transform.parent = chest.transform;
chestBase.transform.localPosition = new Vector3(0, 0.3f, 0);
chestBase.transform.localScale = new Vector3(1f, 0.6f, 0.7f);
var chestLid = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.55f, 0.4f, 0.25f));
chestLid.name = "ChestLid";
chestLid.transform.parent = chest.transform;
chestLid.transform.localPosition = new Vector3(0, 0.65f, 0);
chestLid.transform.localScale = new Vector3(1.05f, 0.2f, 0.75f);
var trigger = chest.AddComponent<BoxCollider>();
trigger.size = new Vector3(1.5f, 1f, 1f);
trigger.center = new Vector3(0, 0.5f, 0);
trigger.isTrigger = true;
objects.Add(chest);
}
private void CreateRPGEnvironment(Transform parent, List<GameObject> objects)
{
var environment = new GameObject("Environment");
environment.transform.parent = parent;
objects.Add(environment);
// Trees around the map
Vector3[] treePositions = {
new Vector3(-15, 0, -5), new Vector3(-18, 0, 0), new Vector3(-12, 0, -10),
new Vector3(15, 0, -8), new Vector3(20, 0, -5), new Vector3(18, 0, 5),
new Vector3(-20, 0, 20), new Vector3(-15, 0, 25), new Vector3(20, 0, 20),
new Vector3(-35, 0, 0), new Vector3(-40, 0, -10), new Vector3(35, 0, -15)
};
foreach (var pos in treePositions)
{
CreateRPGTree(environment.transform, pos, objects);
}
// Rocks
Vector3[] rockPositions = {
new Vector3(-25, 0, -10), new Vector3(28, 0, 8), new Vector3(-10, 0, -25),
new Vector3(5, 0, -30), new Vector3(-30, 0, 15)
};
foreach (var pos in rockPositions)
{
var rock = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, new Color(0.5f, 0.5f, 0.55f));
rock.name = "Rock";
rock.transform.parent = environment.transform;
rock.transform.position = pos;
rock.transform.localScale = new Vector3(Random.Range(1.5f, 3f), Random.Range(1f, 2f), Random.Range(1.5f, 3f));
rock.isStatic = true;
}
// Water area
var water = CreatePrimitiveWithMaterial(PrimitiveType.Plane, new Color(0.2f, 0.4f, 0.7f, 0.8f));
water.name = "Lake";
water.transform.parent = environment.transform;
water.transform.position = new Vector3(25, -0.1f, 25);
water.transform.localScale = new Vector3(1.5f, 1, 1.5f);
}
private void CreateRPGTree(Transform parent, Vector3 position, List<GameObject> objects)
{
var tree = new GameObject("Tree");
tree.transform.parent = parent;
tree.transform.position = position;
var trunk = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, new Color(0.4f, 0.3f, 0.2f));
trunk.name = "Trunk";
trunk.transform.parent = tree.transform;
trunk.transform.localPosition = new Vector3(0, 1.5f, 0);
trunk.transform.localScale = new Vector3(0.5f, 1.5f, 0.5f);
var leaves = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, new Color(0.2f, 0.5f, 0.2f));
leaves.name = "Leaves";
leaves.transform.parent = tree.transform;
leaves.transform.localPosition = new Vector3(0, 4f, 0);
leaves.transform.localScale = new Vector3(3f, 3f, 3f);
tree.isStatic = true;
}
private void SetupRPGLighting()
{
var sun = GameObject.Find("Directional Light");
if (sun == null)
{
sun = new GameObject("Directional Light");
sun.AddComponent<Light>().type = LightType.Directional;
}
var light = sun.GetComponent<Light>();
light.color = new Color(1f, 0.95f, 0.8f);
light.intensity = 1.1f;
light.shadows = LightShadows.Soft;
light.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.6f, 0.7f, 0.8f);
RenderSettings.ambientEquatorColor = new Color(0.5f, 0.55f, 0.5f);
RenderSettings.ambientGroundColor = new Color(0.3f, 0.35f, 0.3f);
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.7f, 0.75f, 0.8f);
RenderSettings.fogMode = FogMode.Linear;
RenderSettings.fogStartDistance = 30f;
RenderSettings.fogEndDistance = 100f;
}
private List<GameObject> CreatePuzzleTemplate(GameObject parent)
{
var objects = new List<GameObject>();
// ===== Camera Setup (Top-down) =====
var cameraObj = new GameObject("MainCamera");
cameraObj.tag = "MainCamera";
var cam = cameraObj.AddComponent<Camera>();
cam.orthographic = true;
cam.orthographicSize = 6f;
cam.transform.position = new Vector3(0, 15, 0);
cam.transform.rotation = Quaternion.Euler(90, 0, 0);
cam.backgroundColor = new Color(0.15f, 0.15f, 0.2f);
cameraObj.AddComponent<AudioListener>();
objects.Add(cameraObj);
// ===== Game Board =====
var board = new GameObject("GameBoard");
board.transform.parent = parent.transform;
objects.Add(board);
int gridSize = 8;
float tileSize = 1f;
float spacing = 0.05f;
// Board background
var boardBg = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.25f, 0.2f, 0.15f));
boardBg.name = "BoardBackground";
boardBg.transform.parent = board.transform;
boardBg.transform.position = new Vector3(0, -0.1f, 0);
boardBg.transform.localScale = new Vector3(gridSize * (tileSize + spacing) + 0.5f, 0.15f, gridSize * (tileSize + spacing) + 0.5f);
objects.Add(boardBg);
// Create grid tiles
for (int x = 0; x < gridSize; x++)
{
for (int y = 0; y < gridSize; y++)
{
float xPos = (x - gridSize / 2f + 0.5f) * (tileSize + spacing);
float zPos = (y - gridSize / 2f + 0.5f) * (tileSize + spacing);
// Checkerboard pattern with slight variation
Color tileColor = (x + y) % 2 == 0
? new Color(0.9f, 0.85f, 0.75f)
: new Color(0.4f, 0.35f, 0.3f);
var tile = CreatePrimitiveWithMaterial(PrimitiveType.Cube, tileColor);
tile.name = $"Tile_{x}_{y}";
tile.transform.parent = board.transform;
tile.transform.position = new Vector3(xPos, 0, zPos);
tile.transform.localScale = new Vector3(tileSize, 0.1f, tileSize);
tile.isStatic = true;
// Add tile data component placeholder
tile.AddComponent<BoxCollider>().isTrigger = true;
objects.Add(tile);
}
}
// ===== Puzzle Pieces =====
var pieces = new GameObject("PuzzlePieces");
pieces.transform.parent = parent.transform;
objects.Add(pieces);
// Different piece types with distinct shapes
CreatePuzzlePiece(pieces.transform, "RedPiece", new Vector3(-2.5f, 0.3f, -2.5f), Color.red, PrimitiveType.Sphere, objects);
CreatePuzzlePiece(pieces.transform, "BluePiece", new Vector3(2.5f, 0.3f, -2.5f), Color.blue, PrimitiveType.Cube, objects);
CreatePuzzlePiece(pieces.transform, "GreenPiece", new Vector3(-2.5f, 0.3f, 2.5f), Color.green, PrimitiveType.Cylinder, objects);
CreatePuzzlePiece(pieces.transform, "YellowPiece", new Vector3(2.5f, 0.3f, 2.5f), Color.yellow, PrimitiveType.Capsule, objects);
// Special pieces
CreatePuzzlePiece(pieces.transform, "KingPiece", new Vector3(0, 0.4f, 0), new Color(1f, 0.85f, 0.3f), PrimitiveType.Sphere, objects, 0.7f);
CreatePuzzlePiece(pieces.transform, "QueenPiece", new Vector3(1, 0.4f, 0), new Color(0.9f, 0.9f, 0.95f), PrimitiveType.Capsule, objects, 0.6f);
// ===== UI Elements Container =====
var uiElements = new GameObject("UIElements");
uiElements.transform.parent = parent.transform;
objects.Add(uiElements);
// Score area (visual placeholder)
var scoreArea = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.2f, 0.2f, 0.25f));
scoreArea.name = "ScoreArea";
scoreArea.transform.parent = uiElements.transform;
scoreArea.transform.position = new Vector3(-6, 0, 0);
scoreArea.transform.localScale = new Vector3(2, 0.1f, 6);
objects.Add(scoreArea);
// Timer area
var timerArea = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.2f, 0.2f, 0.25f));
timerArea.name = "TimerArea";
timerArea.transform.parent = uiElements.transform;
timerArea.transform.position = new Vector3(6, 0, 0);
timerArea.transform.localScale = new Vector3(2, 0.1f, 6);
objects.Add(timerArea);
// ===== Lighting =====
SetupPuzzleLighting();
return objects;
}
private void CreatePuzzlePiece(Transform parent, string name, Vector3 position, Color color, PrimitiveType shape, List<GameObject> objects, float scale = 0.5f)
{
var piece = new GameObject(name);
piece.transform.parent = parent;
piece.transform.position = position;
piece.tag = "PuzzlePiece";
var visual = CreatePrimitiveWithMaterial(shape, color);
visual.name = "Visual";
visual.transform.parent = piece.transform;
visual.transform.localScale = Vector3.one * scale;
// Make draggable
var col = piece.AddComponent<SphereCollider>();
col.radius = scale * 0.6f;
objects.Add(piece);
}
private void SetupPuzzleLighting()
{
var light = GameObject.Find("Directional Light");
if (light == null)
{
light = new GameObject("Directional Light");
light.AddComponent<Light>().type = LightType.Directional;
}
var l = light.GetComponent<Light>();
l.color = Color.white;
l.intensity = 1f;
l.shadows = LightShadows.Soft;
l.transform.rotation = Quaternion.Euler(50, -30, 0);
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.5f, 0.5f, 0.55f);
}
private List<GameObject> CreateRacingTemplate(GameObject parent)
{
var objects = new List<GameObject>();
// ===== Player Vehicle =====
var playerCar = new GameObject("PlayerCar");
playerCar.transform.parent = parent.transform;
playerCar.transform.position = new Vector3(0, 0.5f, -45);
playerCar.tag = "Player";
// Car body
var carBody = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.8f, 0.2f, 0.2f));
carBody.name = "Body";
carBody.transform.parent = playerCar.transform;
carBody.transform.localScale = new Vector3(2, 0.6f, 4);
carBody.transform.localPosition = new Vector3(0, 0.3f, 0);
// Car roof
var carRoof = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.7f, 0.15f, 0.15f));
carRoof.name = "Roof";
carRoof.transform.parent = playerCar.transform;
carRoof.transform.localScale = new Vector3(1.8f, 0.5f, 2f);
carRoof.transform.localPosition = new Vector3(0, 0.8f, -0.3f);
// Wheels
CreateCarWheel(playerCar.transform, "WheelFL", new Vector3(-0.9f, 0, 1.2f), objects);
CreateCarWheel(playerCar.transform, "WheelFR", new Vector3(0.9f, 0, 1.2f), objects);
CreateCarWheel(playerCar.transform, "WheelRL", new Vector3(-0.9f, 0, -1.2f), objects);
CreateCarWheel(playerCar.transform, "WheelRR", new Vector3(0.9f, 0, -1.2f), objects);
// Physics
var rb = playerCar.AddComponent<Rigidbody>();
rb.mass = 1500f;
#if UNITY_2023_3_OR_NEWER
rb.linearDamping = 0.5f;
rb.angularDamping = 2f;
#else
rb.drag = 0.5f;
rb.angularDrag = 2f;
#endif
rb.centerOfMass = new Vector3(0, -0.2f, 0);
var carCollider = playerCar.AddComponent<BoxCollider>();
carCollider.size = new Vector3(2, 1, 4);
carCollider.center = new Vector3(0, 0.5f, 0);
objects.Add(playerCar);
// ===== Camera Setup =====
var cameraRig = new GameObject("CameraRig");
cameraRig.transform.parent = parent.transform;
objects.Add(cameraRig);
var cameraObj = new GameObject("MainCamera");
cameraObj.transform.parent = cameraRig.transform;
cameraObj.transform.localPosition = new Vector3(0, 5, -12);
cameraObj.transform.localRotation = Quaternion.Euler(15, 0, 0);
cameraObj.tag = "MainCamera";
var cam = cameraObj.AddComponent<Camera>();
cam.fieldOfView = 60f;
cameraObj.AddComponent<AudioListener>();
objects.Add(cameraObj);
// ===== Race Track =====
var track = new GameObject("RaceTrack");
track.transform.parent = parent.transform;
objects.Add(track);
// Create oval track
CreateOvalTrack(track.transform, 50f, 30f, 12f, objects);
// Start/Finish line
var startLine = CreatePrimitiveWithMaterial(PrimitiveType.Cube, Color.white);
startLine.name = "StartFinishLine";
startLine.transform.parent = track.transform;
startLine.transform.position = new Vector3(0, 0.01f, -45);
startLine.transform.localScale = new Vector3(12, 0.02f, 2);
objects.Add(startLine);
// Checkered pattern on start line
for (int i = 0; i < 6; i++)
{
var checker = CreatePrimitiveWithMaterial(PrimitiveType.Cube, i % 2 == 0 ? Color.black : Color.white);
checker.transform.parent = startLine.transform;
checker.transform.localPosition = new Vector3(-0.42f + i * 0.167f, 0.1f, 0);
checker.transform.localScale = new Vector3(0.15f, 0.5f, 0.8f);
}
// ===== Barriers =====
CreateTrackBarriers(track.transform, 50f, 30f, 12f, objects);
// ===== Checkpoints =====
var checkpoints = new GameObject("Checkpoints");
checkpoints.transform.parent = parent.transform;
objects.Add(checkpoints);
CreateCheckpoint(checkpoints.transform, "Checkpoint_1", new Vector3(50, 0, 0), 0, objects);
CreateCheckpoint(checkpoints.transform, "Checkpoint_2", new Vector3(0, 0, 30), 90, objects);
CreateCheckpoint(checkpoints.transform, "Checkpoint_3", new Vector3(-50, 0, 0), 0, objects);
// ===== AI Opponents =====
var opponents = new GameObject("Opponents");
opponents.transform.parent = parent.transform;
objects.Add(opponents);
CreateAICar(opponents.transform, "Opponent_1", new Vector3(3, 0.5f, -43), new Color(0.2f, 0.2f, 0.8f), objects);
CreateAICar(opponents.transform, "Opponent_2", new Vector3(-3, 0.5f, -43), new Color(0.2f, 0.8f, 0.2f), objects);
CreateAICar(opponents.transform, "Opponent_3", new Vector3(3, 0.5f, -40), new Color(0.8f, 0.8f, 0.2f), objects);
// ===== Environment =====
CreateRacingEnvironment(parent.transform, objects);
// ===== Lighting =====
SetupRacingLighting();
return objects;
}
private void CreateCarWheel(Transform parent, string name, Vector3 position, List<GameObject> objects)
{
var wheel = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, new Color(0.2f, 0.2f, 0.2f));
wheel.name = name;
wheel.transform.parent = parent;
wheel.transform.localPosition = position;
wheel.transform.localRotation = Quaternion.Euler(0, 0, 90);
wheel.transform.localScale = new Vector3(0.4f, 0.2f, 0.4f);
wheel.GetComponent<CapsuleCollider>().enabled = false;
}
private void CreateOvalTrack(Transform parent, float radiusX, float radiusZ, float width, List<GameObject> objects)
{
int segments = 40;
// Track surface
var trackSurface = new GameObject("TrackSurface");
trackSurface.transform.parent = parent;
objects.Add(trackSurface);
for (int i = 0; i < segments; i++)
{
float angle = (float)i / segments * Mathf.PI * 2;
float nextAngle = (float)(i + 1) / segments * Mathf.PI * 2;
Vector3 pos = new Vector3(Mathf.Sin(angle) * radiusX, 0, Mathf.Cos(angle) * radiusZ);
Vector3 nextPos = new Vector3(Mathf.Sin(nextAngle) * radiusX, 0, Mathf.Cos(nextAngle) * radiusZ);
var segment = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.3f, 0.3f, 0.35f));
segment.name = $"TrackSegment_{i}";
segment.transform.parent = trackSurface.transform;
segment.transform.position = (pos + nextPos) / 2;
segment.transform.LookAt(nextPos);
float segmentLength = Vector3.Distance(pos, nextPos) + 0.5f;
segment.transform.localScale = new Vector3(width, 0.1f, segmentLength);
segment.isStatic = true;
objects.Add(segment);
}
// Center grass
var centerGrass = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, new Color(0.3f, 0.5f, 0.25f));
centerGrass.name = "CenterGrass";
centerGrass.transform.parent = parent;
centerGrass.transform.position = Vector3.zero;
centerGrass.transform.localScale = new Vector3((radiusX - width / 2) * 2, 0.05f, (radiusZ - width / 2) * 2);
objects.Add(centerGrass);
}
private void CreateTrackBarriers(Transform parent, float radiusX, float radiusZ, float width, List<GameObject> objects)
{
var barriers = new GameObject("Barriers");
barriers.transform.parent = parent;
objects.Add(barriers);
int segments = 20;
// Outer barriers
for (int i = 0; i < segments; i++)
{
float angle = (float)i / segments * Mathf.PI * 2;
float nextAngle = (float)(i + 1) / segments * Mathf.PI * 2;
Vector3 pos = new Vector3(Mathf.Sin(angle) * (radiusX + width / 2 + 0.5f), 0.5f, Mathf.Cos(angle) * (radiusZ + width / 2 + 0.5f));
Vector3 nextPos = new Vector3(Mathf.Sin(nextAngle) * (radiusX + width / 2 + 0.5f), 0.5f, Mathf.Cos(nextAngle) * (radiusZ + width / 2 + 0.5f));
var barrier = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.9f, 0.9f, 0.9f));
barrier.name = $"OuterBarrier_{i}";
barrier.transform.parent = barriers.transform;
barrier.transform.position = (pos + nextPos) / 2;
barrier.transform.LookAt(new Vector3(nextPos.x, barrier.transform.position.y, nextPos.z));
barrier.transform.localScale = new Vector3(0.3f, 1f, Vector3.Distance(pos, nextPos) + 0.3f);
barrier.isStatic = true;
}
// Inner barriers
for (int i = 0; i < segments; i++)
{
float angle = (float)i / segments * Mathf.PI * 2;
float nextAngle = (float)(i + 1) / segments * Mathf.PI * 2;
Vector3 pos = new Vector3(Mathf.Sin(angle) * (radiusX - width / 2 - 0.5f), 0.5f, Mathf.Cos(angle) * (radiusZ - width / 2 - 0.5f));
Vector3 nextPos = new Vector3(Mathf.Sin(nextAngle) * (radiusX - width / 2 - 0.5f), 0.5f, Mathf.Cos(nextAngle) * (radiusZ - width / 2 - 0.5f));
var barrier = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.8f, 0.3f, 0.3f));
barrier.name = $"InnerBarrier_{i}";
barrier.transform.parent = barriers.transform;
barrier.transform.position = (pos + nextPos) / 2;
barrier.transform.LookAt(new Vector3(nextPos.x, barrier.transform.position.y, nextPos.z));
barrier.transform.localScale = new Vector3(0.3f, 0.8f, Vector3.Distance(pos, nextPos) + 0.3f);
barrier.isStatic = true;
}
}
private void CreateCheckpoint(Transform parent, string name, Vector3 position, float rotationY, List<GameObject> objects)
{
var checkpoint = new GameObject(name);
checkpoint.transform.parent = parent;
checkpoint.transform.position = position;
checkpoint.transform.rotation = Quaternion.Euler(0, rotationY, 0);
checkpoint.tag = "Checkpoint";
// Gate posts
var leftPost = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.9f, 0.7f, 0.2f));
leftPost.transform.parent = checkpoint.transform;
leftPost.transform.localPosition = new Vector3(-7, 3, 0);
leftPost.transform.localScale = new Vector3(0.5f, 6, 0.5f);
var rightPost = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.9f, 0.7f, 0.2f));
rightPost.transform.parent = checkpoint.transform;
rightPost.transform.localPosition = new Vector3(7, 3, 0);
rightPost.transform.localScale = new Vector3(0.5f, 6, 0.5f);
var topBar = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.9f, 0.7f, 0.2f));
topBar.transform.parent = checkpoint.transform;
topBar.transform.localPosition = new Vector3(0, 6, 0);
topBar.transform.localScale = new Vector3(14, 0.5f, 0.5f);
// Trigger
var trigger = checkpoint.AddComponent<BoxCollider>();
trigger.size = new Vector3(14, 6, 2);
trigger.center = new Vector3(0, 3, 0);
trigger.isTrigger = true;
objects.Add(checkpoint);
}
private void CreateAICar(Transform parent, string name, Vector3 position, Color color, List<GameObject> objects)
{
var car = new GameObject(name);
car.transform.parent = parent;
car.transform.position = position;
car.tag = "AI";
var body = CreatePrimitiveWithMaterial(PrimitiveType.Cube, color);
body.name = "Body";
body.transform.parent = car.transform;
body.transform.localScale = new Vector3(2, 0.6f, 4);
body.transform.localPosition = new Vector3(0, 0.3f, 0);
var roof = CreatePrimitiveWithMaterial(PrimitiveType.Cube, color * 0.8f);
roof.name = "Roof";
roof.transform.parent = car.transform;
roof.transform.localScale = new Vector3(1.8f, 0.5f, 2f);
roof.transform.localPosition = new Vector3(0, 0.8f, -0.3f);
CreateCarWheel(car.transform, "WheelFL", new Vector3(-0.9f, 0, 1.2f), objects);
CreateCarWheel(car.transform, "WheelFR", new Vector3(0.9f, 0, 1.2f), objects);
CreateCarWheel(car.transform, "WheelRL", new Vector3(-0.9f, 0, -1.2f), objects);
CreateCarWheel(car.transform, "WheelRR", new Vector3(0.9f, 0, -1.2f), objects);
var rb = car.AddComponent<Rigidbody>();
rb.mass = 1500f;
objects.Add(car);
}
private void CreateRacingEnvironment(Transform parent, List<GameObject> objects)
{
var env = new GameObject("Environment");
env.transform.parent = parent;
objects.Add(env);
// Ground
var ground = CreatePrimitiveWithMaterial(PrimitiveType.Plane, new Color(0.35f, 0.5f, 0.25f));
ground.name = "Ground";
ground.transform.parent = env.transform;
ground.transform.localScale = new Vector3(20, 1, 15);
ground.isStatic = true;
objects.Add(ground);
// Grandstands
var grandstand = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.5f, 0.5f, 0.55f));
grandstand.name = "Grandstand";
grandstand.transform.parent = env.transform;
grandstand.transform.position = new Vector3(0, 3, -60);
grandstand.transform.localScale = new Vector3(30, 6, 5);
objects.Add(grandstand);
}
private void SetupRacingLighting()
{
var sun = GameObject.Find("Directional Light");
if (sun == null)
{
sun = new GameObject("Directional Light");
sun.AddComponent<Light>().type = LightType.Directional;
}
var light = sun.GetComponent<Light>();
light.color = new Color(1f, 0.98f, 0.9f);
light.intensity = 1.2f;
light.shadows = LightShadows.Soft;
light.transform.rotation = Quaternion.Euler(45, -45, 0);
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.5f, 0.6f, 0.8f);
RenderSettings.ambientEquatorColor = new Color(0.4f, 0.45f, 0.5f);
RenderSettings.ambientGroundColor = new Color(0.3f, 0.35f, 0.3f);
}
private List<GameObject> CreateStrategyTemplate(GameObject parent)
{
var objects = new List<GameObject>();
// ===== Camera Setup =====
var cameraRig = new GameObject("CameraRig");
cameraRig.transform.parent = parent.transform;
cameraRig.transform.position = new Vector3(0, 20, -10);
objects.Add(cameraRig);
var cameraObj = new GameObject("MainCamera");
cameraObj.transform.parent = cameraRig.transform;
cameraObj.transform.localPosition = Vector3.zero;
cameraObj.transform.localRotation = Quaternion.Euler(60, 0, 0);
cameraObj.tag = "MainCamera";
var cam = cameraObj.AddComponent<Camera>();
cam.fieldOfView = 50f;
cameraObj.AddComponent<AudioListener>();
objects.Add(cameraObj);
// ===== Game Board (Hex Grid) =====
var board = new GameObject("StrategyBoard");
board.transform.parent = parent.transform;
objects.Add(board);
int mapWidth = 12;
int mapHeight = 10;
float hexSize = 1f;
float hexWidth = hexSize * 2f;
float hexHeight = Mathf.Sqrt(3f) * hexSize;
for (int x = 0; x < mapWidth; x++)
{
for (int y = 0; y < mapHeight; y++)
{
float xPos = x * hexWidth * 0.75f;
float zPos = y * hexHeight + (x % 2) * hexHeight * 0.5f;
// Terrain type based on position/noise
float noise = Mathf.PerlinNoise(x * 0.3f, y * 0.3f);
Color tileColor;
string terrainType;
if (noise < 0.3f)
{
tileColor = new Color(0.2f, 0.4f, 0.7f); // Water
terrainType = "Water";
}
else if (noise < 0.5f)
{
tileColor = new Color(0.35f, 0.55f, 0.3f); // Plains
terrainType = "Plains";
}
else if (noise < 0.7f)
{
tileColor = new Color(0.25f, 0.45f, 0.2f); // Forest
terrainType = "Forest";
}
else
{
tileColor = new Color(0.5f, 0.5f, 0.55f); // Mountain
terrainType = "Mountain";
}
var tile = CreateHexTile(board.transform, $"Hex_{x}_{y}", new Vector3(xPos, 0, zPos), hexSize, tileColor, terrainType, objects);
// Add terrain features
if (terrainType == "Forest" && Random.value > 0.5f)
{
CreateStrategyTree(tile.transform, Vector3.up * 0.1f);
}
else if (terrainType == "Mountain" && Random.value > 0.6f)
{
var rock = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, new Color(0.45f, 0.45f, 0.5f));
rock.transform.parent = tile.transform;
rock.transform.localPosition = Vector3.up * 0.3f;
rock.transform.localScale = new Vector3(0.5f, 0.4f, 0.5f);
rock.GetComponent<SphereCollider>().enabled = false;
}
}
}
// ===== Player Units =====
var playerUnits = new GameObject("PlayerUnits");
playerUnits.transform.parent = parent.transform;
objects.Add(playerUnits);
CreateStrategyUnit(playerUnits.transform, "Infantry_1", new Vector3(1.5f, 0.3f, 2), new Color(0.2f, 0.4f, 0.8f), "Infantry", objects);
CreateStrategyUnit(playerUnits.transform, "Infantry_2", new Vector3(0, 0.3f, 2.8f), new Color(0.2f, 0.4f, 0.8f), "Infantry", objects);
CreateStrategyUnit(playerUnits.transform, "Cavalry_1", new Vector3(1.5f, 0.3f, 4.5f), new Color(0.3f, 0.5f, 0.9f), "Cavalry", objects);
CreateStrategyUnit(playerUnits.transform, "Archer_1", new Vector3(0, 0.3f, 1), new Color(0.2f, 0.6f, 0.3f), "Archer", objects);
// ===== Enemy Units =====
var enemyUnits = new GameObject("EnemyUnits");
enemyUnits.transform.parent = parent.transform;
objects.Add(enemyUnits);
CreateStrategyUnit(enemyUnits.transform, "EnemyInfantry_1", new Vector3(10, 0.3f, 12), new Color(0.8f, 0.2f, 0.2f), "Infantry", objects);
CreateStrategyUnit(enemyUnits.transform, "EnemyInfantry_2", new Vector3(11.5f, 0.3f, 11), new Color(0.8f, 0.2f, 0.2f), "Infantry", objects);
CreateStrategyUnit(enemyUnits.transform, "EnemyCavalry_1", new Vector3(10, 0.3f, 14), new Color(0.9f, 0.3f, 0.3f), "Cavalry", objects);
// ===== Buildings =====
var buildings = new GameObject("Buildings");
buildings.transform.parent = parent.transform;
objects.Add(buildings);
CreateStrategyBuilding(buildings.transform, "PlayerCastle", new Vector3(0, 0, 0), new Color(0.3f, 0.4f, 0.7f), "Castle", objects);
CreateStrategyBuilding(buildings.transform, "EnemyCastle", new Vector3(12, 0, 14), new Color(0.7f, 0.3f, 0.3f), "Castle", objects);
CreateStrategyBuilding(buildings.transform, "Village_1", new Vector3(6, 0, 5), new Color(0.6f, 0.5f, 0.4f), "Village", objects);
CreateStrategyBuilding(buildings.transform, "Village_2", new Vector3(4, 0, 10), new Color(0.6f, 0.5f, 0.4f), "Village", objects);
// ===== Resources =====
var resources = new GameObject("Resources");
resources.transform.parent = parent.transform;
objects.Add(resources);
CreateResourceNode(resources.transform, "GoldMine_1", new Vector3(3, 0, 3), new Color(1f, 0.85f, 0.2f), objects);
CreateResourceNode(resources.transform, "GoldMine_2", new Vector3(9, 0, 11), new Color(1f, 0.85f, 0.2f), objects);
CreateResourceNode(resources.transform, "Forest_1", new Vector3(7.5f, 0, 8), new Color(0.3f, 0.5f, 0.25f), objects);
// ===== Lighting =====
SetupStrategyLighting();
return objects;
}
private GameObject CreateHexTile(Transform parent, string name, Vector3 position, float size, Color color, string terrainType, List<GameObject> objects)
{
var tile = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, color);
tile.name = name;
tile.transform.parent = parent;
tile.transform.position = position;
tile.transform.localScale = new Vector3(size * 1.8f, 0.1f, size * 1.8f);
tile.isStatic = true;
// Store terrain type (would use a component in real implementation)
tile.tag = terrainType == "Water" ? "Water" : "Terrain";
objects.Add(tile);
return tile;
}
private void CreateStrategyTree(Transform parent, Vector3 offset)
{
var tree = new GameObject("Tree");
tree.transform.parent = parent;
tree.transform.localPosition = offset;
var trunk = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, new Color(0.4f, 0.3f, 0.2f));
trunk.transform.parent = tree.transform;
trunk.transform.localPosition = new Vector3(0, 0.2f, 0);
trunk.transform.localScale = new Vector3(0.1f, 0.2f, 0.1f);
trunk.GetComponent<CapsuleCollider>().enabled = false;
var leaves = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, new Color(0.2f, 0.45f, 0.2f));
leaves.transform.parent = tree.transform;
leaves.transform.localPosition = new Vector3(0, 0.5f, 0);
leaves.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f);
leaves.GetComponent<SphereCollider>().enabled = false;
}
private void CreateStrategyUnit(Transform parent, string name, Vector3 position, Color color, string unitType, List<GameObject> objects)
{
var unit = new GameObject(name);
unit.transform.parent = parent;
unit.transform.position = position;
unit.tag = parent.name.Contains("Enemy") ? "Enemy" : "PlayerUnit";
// Unit body based on type
float height = unitType == "Cavalry" ? 0.6f : 0.4f;
float width = unitType == "Infantry" ? 0.3f : 0.25f;
var body = CreatePrimitiveWithMaterial(PrimitiveType.Capsule, color);
body.name = "Body";
body.transform.parent = unit.transform;
body.transform.localPosition = new Vector3(0, height / 2, 0);
body.transform.localScale = new Vector3(width, height / 2, width);
// Selection indicator
var selectionRing = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, new Color(1f, 1f, 1f, 0.5f));
selectionRing.name = "SelectionRing";
selectionRing.transform.parent = unit.transform;
selectionRing.transform.localPosition = new Vector3(0, 0.01f, 0);
selectionRing.transform.localScale = new Vector3(0.5f, 0.01f, 0.5f);
selectionRing.GetComponent<CapsuleCollider>().enabled = false;
selectionRing.SetActive(false);
// Unit collider for selection
var col = unit.AddComponent<CapsuleCollider>();
col.height = height;
col.radius = width;
col.center = new Vector3(0, height / 2, 0);
objects.Add(unit);
}
private void CreateStrategyBuilding(Transform parent, string name, Vector3 position, Color color, string buildingType, List<GameObject> objects)
{
var building = new GameObject(name);
building.transform.parent = parent;
building.transform.position = position;
float size = buildingType == "Castle" ? 1.5f : 0.8f;
float height = buildingType == "Castle" ? 2f : 1f;
var baseObj = CreatePrimitiveWithMaterial(PrimitiveType.Cube, color);
baseObj.name = "Base";
baseObj.transform.parent = building.transform;
baseObj.transform.localPosition = new Vector3(0, height / 2, 0);
baseObj.transform.localScale = new Vector3(size, height, size);
if (buildingType == "Castle")
{
// Towers
Vector3[] towerOffsets = { new Vector3(-0.6f, 0, -0.6f), new Vector3(0.6f, 0, -0.6f), new Vector3(-0.6f, 0, 0.6f), new Vector3(0.6f, 0, 0.6f) };
foreach (var offset in towerOffsets)
{
var tower = CreatePrimitiveWithMaterial(PrimitiveType.Cylinder, color * 0.9f);
tower.transform.parent = building.transform;
tower.transform.localPosition = offset + new Vector3(0, 1.5f, 0);
tower.transform.localScale = new Vector3(0.3f, 0.8f, 0.3f);
}
}
// Roof
var roof = CreatePrimitiveWithMaterial(PrimitiveType.Cube, color * 0.7f);
roof.name = "Roof";
roof.transform.parent = building.transform;
roof.transform.localPosition = new Vector3(0, height + 0.2f, 0);
roof.transform.localScale = new Vector3(size * 1.1f, 0.3f, size * 1.1f);
building.isStatic = true;
objects.Add(building);
}
private void CreateResourceNode(Transform parent, string name, Vector3 position, Color color, List<GameObject> objects)
{
var resource = new GameObject(name);
resource.transform.parent = parent;
resource.transform.position = position;
resource.tag = "Resource";
var visual = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, color);
visual.name = "Visual";
visual.transform.parent = resource.transform;
visual.transform.localPosition = new Vector3(0, 0.2f, 0);
visual.transform.localScale = new Vector3(0.4f, 0.3f, 0.4f);
// Glow effect indicator
var glow = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, color * 1.5f);
glow.name = "Glow";
glow.transform.parent = resource.transform;
glow.transform.localPosition = new Vector3(0, 0.2f, 0);
glow.transform.localScale = new Vector3(0.5f, 0.4f, 0.5f);
glow.GetComponent<SphereCollider>().enabled = false;
var col = resource.AddComponent<SphereCollider>();
col.radius = 0.5f;
col.center = new Vector3(0, 0.2f, 0);
objects.Add(resource);
}
private void SetupStrategyLighting()
{
var sun = GameObject.Find("Directional Light");
if (sun == null)
{
sun = new GameObject("Directional Light");
sun.AddComponent<Light>().type = LightType.Directional;
}
var light = sun.GetComponent<Light>();
light.color = new Color(1f, 0.95f, 0.85f);
light.intensity = 1f;
light.shadows = LightShadows.Soft;
light.transform.rotation = Quaternion.Euler(50, -30, 0);
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Trilight;
RenderSettings.ambientSkyColor = new Color(0.55f, 0.6f, 0.7f);
RenderSettings.ambientEquatorColor = new Color(0.45f, 0.5f, 0.45f);
RenderSettings.ambientGroundColor = new Color(0.3f, 0.35f, 0.3f);
RenderSettings.fog = true;
RenderSettings.fogColor = new Color(0.7f, 0.75f, 0.8f);
RenderSettings.fogMode = FogMode.Linear;
RenderSettings.fogStartDistance = 20f;
RenderSettings.fogEndDistance = 80f;
}
private void CreateGenericUI(string genre)
{
// CanvasCreate
var canvas = GameObject.Find("Canvas");
if (canvas == null)
{
canvas = new GameObject("Canvas");
canvas.AddComponent<Canvas>().renderMode = RenderMode.ScreenSpaceOverlay;
canvas.AddComponent<UnityEngine.UI.CanvasScaler>();
canvas.AddComponent<UnityEngine.UI.GraphicRaycaster>();
// EventSystemalso Create
if (!GameObject.Find("EventSystem"))
{
var eventSystem = new GameObject("EventSystem");
eventSystem.AddComponent<UnityEngine.EventSystems.EventSystem>();
eventSystem.AddComponent<UnityEngine.EventSystems.StandaloneInputModule>();
}
}
// HUD Panel
var hudPanel = new GameObject("HUD");
hudPanel.transform.parent = canvas.transform;
var hudRect = hudPanel.AddComponent<RectTransform>();
hudRect.anchorMin = Vector2.zero;
hudRect.anchorMax = Vector2.one;
hudRect.sizeDelta = Vector2.zero;
hudRect.anchoredPosition = Vector2.zero;
// By genreUIElement
switch (genre)
{
case "FPS":
CreateFPSUI(hudPanel);
break;
case "RPG":
CreateRPGUI(hudPanel);
break;
case "Platformer":
CreatePlatformerUI(hudPanel);
break;
case "Racing":
CreateRacingUI(hudPanel);
break;
case "Puzzle":
CreatePuzzleUI(hudPanel);
break;
case "Strategy":
CreateStrategyUI(hudPanel);
break;
}
}
private void CreateFPSUI(GameObject parent)
{
// Health bar
var healthBar = new GameObject("HealthBar");
healthBar.transform.parent = parent.transform;
var healthRect = healthBar.AddComponent<RectTransform>();
healthRect.anchorMin = new Vector2(0, 0);
healthRect.anchorMax = new Vector2(0, 0);
healthRect.sizeDelta = new Vector2(200, 30);
healthRect.anchoredPosition = new Vector2(110, 50);
var healthBG = healthBar.AddComponent<UnityEngine.UI.Image>();
healthBG.color = new Color(0.2f, 0.2f, 0.2f, 0.8f);
var healthFill = new GameObject("Fill");
healthFill.transform.parent = healthBar.transform;
var fillRect = healthFill.AddComponent<RectTransform>();
fillRect.anchorMin = Vector2.zero;
fillRect.anchorMax = new Vector2(0.8f, 1);
fillRect.sizeDelta = Vector2.zero;
fillRect.anchoredPosition = Vector2.zero;
var fillImage = healthFill.AddComponent<UnityEngine.UI.Image>();
fillImage.color = Color.red;
// Ammo counter
var ammoText = new GameObject("AmmoCounter");
ammoText.transform.parent = parent.transform;
var ammoRect = ammoText.AddComponent<RectTransform>();
ammoRect.anchorMin = new Vector2(1, 0);
ammoRect.anchorMax = new Vector2(1, 0);
ammoRect.sizeDelta = new Vector2(150, 50);
ammoRect.anchoredPosition = new Vector2(-85, 50);
var text = ammoText.AddComponent<UnityEngine.UI.Text>();
text.text = "30 / 120";
text.font = Font.CreateDynamicFontFromOSFont("Arial", 24);
text.fontSize = 24;
text.color = Color.white;
text.alignment = TextAnchor.MiddleRight;
// Crosshair
var crosshair = new GameObject("Crosshair");
crosshair.transform.parent = parent.transform;
var crossRect = crosshair.AddComponent<RectTransform>();
crossRect.anchorMin = new Vector2(0.5f, 0.5f);
crossRect.anchorMax = new Vector2(0.5f, 0.5f);
crossRect.sizeDelta = new Vector2(50, 50);
crossRect.anchoredPosition = Vector2.zero;
var crossImage = crosshair.AddComponent<UnityEngine.UI.Image>();
crossImage.color = new Color(1, 1, 1, 0.5f);
crossImage.sprite = null; // CrosshairSpriteSettings
}
private void CreateRPGUI(GameObject parent)
{
// Status bar
var statusBar = new GameObject("StatusBar");
statusBar.transform.parent = parent.transform;
var statusRect = statusBar.AddComponent<RectTransform>();
statusRect.anchorMin = new Vector2(0, 1);
statusRect.anchorMax = new Vector2(0, 1);
statusRect.sizeDelta = new Vector2(250, 100);
statusRect.anchoredPosition = new Vector2(135, -60);
var bg = statusBar.AddComponent<UnityEngine.UI.Image>();
bg.color = new Color(0.1f, 0.1f, 0.1f, 0.9f);
// HP/MP bars
string[] barTypes = { "HP", "MP" };
Color[] barColors = { Color.green, Color.blue };
for (int i = 0; i < barTypes.Length; i++)
{
var bar = new GameObject($"{barTypes[i]}Bar");
bar.transform.parent = statusBar.transform;
var barRect = bar.AddComponent<RectTransform>();
barRect.anchorMin = new Vector2(0.1f, 0.6f - i * 0.4f);
barRect.anchorMax = new Vector2(0.9f, 0.8f - i * 0.4f);
barRect.sizeDelta = Vector2.zero;
var barBG = bar.AddComponent<UnityEngine.UI.Image>();
barBG.color = Color.gray;
var fill = new GameObject("Fill");
fill.transform.parent = bar.transform;
var fillRect = fill.AddComponent<RectTransform>();
fillRect.anchorMin = Vector2.zero;
fillRect.anchorMax = new Vector2(0.8f, 1);
fillRect.sizeDelta = Vector2.zero;
var fillImage = fill.AddComponent<UnityEngine.UI.Image>();
fillImage.color = barColors[i];
}
// Quest log
var questLog = new GameObject("QuestLog");
questLog.transform.parent = parent.transform;
var questRect = questLog.AddComponent<RectTransform>();
questRect.anchorMin = new Vector2(1, 1);
questRect.anchorMax = new Vector2(1, 1);
questRect.sizeDelta = new Vector2(300, 150);
questRect.anchoredPosition = new Vector2(-160, -85);
var questBG = questLog.AddComponent<UnityEngine.UI.Image>();
questBG.color = new Color(0.2f, 0.2f, 0.2f, 0.8f);
var questText = new GameObject("QuestText");
questText.transform.parent = questLog.transform;
var textRect = questText.AddComponent<RectTransform>();
textRect.anchorMin = Vector2.zero;
textRect.anchorMax = Vector2.one;
textRect.sizeDelta = new Vector2(-20, -20);
textRect.anchoredPosition = Vector2.zero;
var text = questText.AddComponent<UnityEngine.UI.Text>();
text.text = "Current Quest:\n- Talk to the Village Elder\n- Collect 10 herbs";
text.font = Font.CreateDynamicFontFromOSFont("Arial", 14);
text.fontSize = 14;
text.color = Color.white;
}
private void CreatePlatformerUI(GameObject parent)
{
// Score
var scoreText = new GameObject("Score");
scoreText.transform.parent = parent.transform;
var scoreRect = scoreText.AddComponent<RectTransform>();
scoreRect.anchorMin = new Vector2(0, 1);
scoreRect.anchorMax = new Vector2(0, 1);
scoreRect.sizeDelta = new Vector2(200, 50);
scoreRect.anchoredPosition = new Vector2(110, -35);
var score = scoreText.AddComponent<UnityEngine.UI.Text>();
score.text = "Score: 0";
score.font = Font.CreateDynamicFontFromOSFont("Arial", 28);
score.fontSize = 28;
score.color = Color.white;
score.alignment = TextAnchor.MiddleLeft;
// Lives
var livesContainer = new GameObject("Lives");
livesContainer.transform.parent = parent.transform;
var livesRect = livesContainer.AddComponent<RectTransform>();
livesRect.anchorMin = new Vector2(1, 1);
livesRect.anchorMax = new Vector2(1, 1);
livesRect.sizeDelta = new Vector2(150, 50);
livesRect.anchoredPosition = new Vector2(-85, -35);
for (int i = 0; i < 3; i++)
{
var life = new GameObject($"Life_{i}");
life.transform.parent = livesContainer.transform;
var lifeRect = life.AddComponent<RectTransform>();
lifeRect.anchorMin = new Vector2(0, 0.5f);
lifeRect.anchorMax = new Vector2(0, 0.5f);
lifeRect.sizeDelta = new Vector2(30, 30);
lifeRect.anchoredPosition = new Vector2(35 + i * 40, 0);
var lifeImage = life.AddComponent<UnityEngine.UI.Image>();
lifeImage.color = Color.red;
}
}
private void CreateRacingUI(GameObject parent)
{
// Speedometer
var speedometer = new GameObject("Speedometer");
speedometer.transform.parent = parent.transform;
var speedRect = speedometer.AddComponent<RectTransform>();
speedRect.anchorMin = new Vector2(1, 0);
speedRect.anchorMax = new Vector2(1, 0);
speedRect.sizeDelta = new Vector2(200, 200);
speedRect.anchoredPosition = new Vector2(-110, 110);
var speedBG = speedometer.AddComponent<UnityEngine.UI.Image>();
speedBG.color = new Color(0.1f, 0.1f, 0.1f, 0.8f);
var speedText = new GameObject("SpeedText");
speedText.transform.parent = speedometer.transform;
var textRect = speedText.AddComponent<RectTransform>();
textRect.anchorMin = new Vector2(0.5f, 0.5f);
textRect.anchorMax = new Vector2(0.5f, 0.5f);
textRect.sizeDelta = new Vector2(180, 60);
textRect.anchoredPosition = Vector2.zero;
var text = speedText.AddComponent<UnityEngine.UI.Text>();
text.text = "0 km/h";
text.font = Font.CreateDynamicFontFromOSFont("Arial", 32);
text.fontSize = 32;
text.color = Color.white;
text.alignment = TextAnchor.MiddleCenter;
// Lap time
var lapTime = new GameObject("LapTime");
lapTime.transform.parent = parent.transform;
var lapRect = lapTime.AddComponent<RectTransform>();
lapRect.anchorMin = new Vector2(0.5f, 1);
lapRect.anchorMax = new Vector2(0.5f, 1);
lapRect.sizeDelta = new Vector2(300, 60);
lapRect.anchoredPosition = new Vector2(0, -40);
var lapText = lapTime.AddComponent<UnityEngine.UI.Text>();
lapText.text = "Lap 1/3 - 00:00.000";
lapText.font = Font.CreateDynamicFontFromOSFont("Arial", 24);
lapText.fontSize = 24;
lapText.color = Color.white;
lapText.alignment = TextAnchor.MiddleCenter;
// Position
var position = new GameObject("Position");
position.transform.parent = parent.transform;
var posRect = position.AddComponent<RectTransform>();
posRect.anchorMin = new Vector2(0, 1);
posRect.anchorMax = new Vector2(0, 1);
posRect.sizeDelta = new Vector2(150, 100);
posRect.anchoredPosition = new Vector2(85, -60);
var posBG = position.AddComponent<UnityEngine.UI.Image>();
posBG.color = new Color(0.8f, 0.8f, 0.8f, 0.9f);
var posText = new GameObject("PosText");
posText.transform.parent = position.transform;
var posTextRect = posText.AddComponent<RectTransform>();
posTextRect.anchorMin = Vector2.zero;
posTextRect.anchorMax = Vector2.one;
posTextRect.sizeDelta = Vector2.zero;
var positionText = posText.AddComponent<UnityEngine.UI.Text>();
positionText.text = "1st";
positionText.font = Font.CreateDynamicFontFromOSFont("Arial", 48);
positionText.fontSize = 48;
positionText.color = Color.black;
positionText.alignment = TextAnchor.MiddleCenter;
}
private void CreatePuzzleUI(GameObject parent)
{
// Timer
var timer = new GameObject("Timer");
timer.transform.parent = parent.transform;
var timerRect = timer.AddComponent<RectTransform>();
timerRect.anchorMin = new Vector2(0.5f, 1);
timerRect.anchorMax = new Vector2(0.5f, 1);
timerRect.sizeDelta = new Vector2(200, 60);
timerRect.anchoredPosition = new Vector2(0, -40);
var timerBG = timer.AddComponent<UnityEngine.UI.Image>();
timerBG.color = new Color(0.2f, 0.2f, 0.2f, 0.8f);
var timerText = new GameObject("TimerText");
timerText.transform.parent = timer.transform;
var textRect = timerText.AddComponent<RectTransform>();
textRect.anchorMin = Vector2.zero;
textRect.anchorMax = Vector2.one;
textRect.sizeDelta = Vector2.zero;
var text = timerText.AddComponent<UnityEngine.UI.Text>();
text.text = "00:00";
text.font = Font.CreateDynamicFontFromOSFont("Arial", 32);
text.fontSize = 32;
text.color = Color.white;
text.alignment = TextAnchor.MiddleCenter;
// Move counter
var moves = new GameObject("Moves");
moves.transform.parent = parent.transform;
var movesRect = moves.AddComponent<RectTransform>();
movesRect.anchorMin = new Vector2(0, 1);
movesRect.anchorMax = new Vector2(0, 1);
movesRect.sizeDelta = new Vector2(150, 50);
movesRect.anchoredPosition = new Vector2(85, -35);
var movesText = moves.AddComponent<UnityEngine.UI.Text>();
movesText.text = "Moves: 0";
movesText.font = Font.CreateDynamicFontFromOSFont("Arial", 20);
movesText.fontSize = 20;
movesText.color = Color.white;
// Hint button
var hintButton = new GameObject("HintButton");
hintButton.transform.parent = parent.transform;
var hintRect = hintButton.AddComponent<RectTransform>();
hintRect.anchorMin = new Vector2(1, 1);
hintRect.anchorMax = new Vector2(1, 1);
hintRect.sizeDelta = new Vector2(100, 40);
hintRect.anchoredPosition = new Vector2(-60, -30);
var button = hintButton.AddComponent<UnityEngine.UI.Button>();
var buttonImage = hintButton.AddComponent<UnityEngine.UI.Image>();
buttonImage.color = Color.yellow;
var buttonText = new GameObject("Text");
buttonText.transform.parent = hintButton.transform;
var btnTextRect = buttonText.AddComponent<RectTransform>();
btnTextRect.anchorMin = Vector2.zero;
btnTextRect.anchorMax = Vector2.one;
btnTextRect.sizeDelta = Vector2.zero;
var btnText = buttonText.AddComponent<UnityEngine.UI.Text>();
btnText.text = "Hint";
btnText.font = Font.CreateDynamicFontFromOSFont("Arial", 16);
btnText.fontSize = 16;
btnText.color = Color.black;
btnText.alignment = TextAnchor.MiddleCenter;
}
private void CreateStrategyUI(GameObject parent)
{
// Resource panel
var resourcePanel = new GameObject("ResourcePanel");
resourcePanel.transform.parent = parent.transform;
var resRect = resourcePanel.AddComponent<RectTransform>();
resRect.anchorMin = new Vector2(0, 1);
resRect.anchorMax = new Vector2(1, 1);
resRect.sizeDelta = new Vector2(0, 80);
resRect.anchoredPosition = new Vector2(0, -40);
var resBG = resourcePanel.AddComponent<UnityEngine.UI.Image>();
resBG.color = new Color(0.1f, 0.1f, 0.1f, 0.9f);
// Resource display
string[] resources = { "Gold: 1000", "Food: 500", "Wood: 300", "Stone: 200" };
for (int i = 0; i < resources.Length; i++)
{
var resource = new GameObject($"Resource_{i}");
resource.transform.parent = resourcePanel.transform;
var resourceRect = resource.AddComponent<RectTransform>();
resourceRect.anchorMin = new Vector2(0.1f + i * 0.2f, 0.5f);
resourceRect.anchorMax = new Vector2(0.1f + i * 0.2f, 0.5f);
resourceRect.sizeDelta = new Vector2(150, 40);
resourceRect.anchoredPosition = Vector2.zero;
var text = resource.AddComponent<UnityEngine.UI.Text>();
text.text = resources[i];
text.font = Font.CreateDynamicFontFromOSFont("Arial", 18);
text.fontSize = 18;
text.color = Color.white;
text.alignment = TextAnchor.MiddleLeft;
}
// Minimap
var minimap = new GameObject("Minimap");
minimap.transform.parent = parent.transform;
var mapRect = minimap.AddComponent<RectTransform>();
mapRect.anchorMin = new Vector2(1, 0);
mapRect.anchorMax = new Vector2(1, 0);
mapRect.sizeDelta = new Vector2(200, 200);
mapRect.anchoredPosition = new Vector2(-110, 110);
var mapBG = minimap.AddComponent<UnityEngine.UI.Image>();
mapBG.color = new Color(0.2f, 0.2f, 0.2f, 0.8f);
var mapBorder = new GameObject("Border");
mapBorder.transform.parent = minimap.transform;
var borderRect = mapBorder.AddComponent<RectTransform>();
borderRect.anchorMin = Vector2.zero;
borderRect.anchorMax = Vector2.one;
borderRect.sizeDelta = new Vector2(10, 10);
var outline = mapBorder.AddComponent<UnityEngine.UI.Outline>();
outline.effectColor = Color.white;
outline.effectDistance = new Vector2(2, 2);
// ActionButton
var actionPanel = new GameObject("ActionPanel");
actionPanel.transform.parent = parent.transform;
var actionRect = actionPanel.AddComponent<RectTransform>();
actionRect.anchorMin = new Vector2(0.5f, 0);
actionRect.anchorMax = new Vector2(0.5f, 0);
actionRect.sizeDelta = new Vector2(400, 100);
actionRect.anchoredPosition = new Vector2(0, 60);
string[] actions = { "Build", "Train", "Research", "Diplomacy" };
for (int i = 0; i < actions.Length; i++)
{
var actionButton = new GameObject($"Action_{actions[i]}");
actionButton.transform.parent = actionPanel.transform;
var btnRect = actionButton.AddComponent<RectTransform>();
btnRect.anchorMin = new Vector2(0.1f + i * 0.225f, 0.5f);
btnRect.anchorMax = new Vector2(0.1f + i * 0.225f, 0.5f);
btnRect.sizeDelta = new Vector2(80, 80);
btnRect.anchoredPosition = Vector2.zero;
var btn = actionButton.AddComponent<UnityEngine.UI.Button>();
var btnImage = actionButton.AddComponent<UnityEngine.UI.Image>();
btnImage.color = new Color(0.3f, 0.3f, 0.3f);
var btnText = new GameObject("Text");
btnText.transform.parent = actionButton.transform;
var textRect = btnText.AddComponent<RectTransform>();
textRect.anchorMin = Vector2.zero;
textRect.anchorMax = Vector2.one;
textRect.sizeDelta = Vector2.zero;
var text = btnText.AddComponent<UnityEngine.UI.Text>();
text.text = actions[i];
text.font = Font.CreateDynamicFontFromOSFont("Arial", 14);
text.fontSize = 14;
text.color = Color.white;
text.alignment = TextAnchor.MiddleCenter;
}
}
private void SetupGenericAudio(GameObject parent)
{
// AudioManager
var audioManager = new GameObject("AudioManager");
audioManager.transform.parent = parent.transform;
// BGM
var bgmSource = audioManager.AddComponent<AudioSource>();
bgmSource.loop = true;
bgmSource.volume = 0.3f;
bgmSource.playOnAwake = true;
// SFX pool
var sfxPool = new GameObject("SFXPool");
sfxPool.transform.parent = audioManager.transform;
for (int i = 0; i < 5; i++)
{
var sfxSource = new GameObject($"SFXSource_{i}");
sfxSource.transform.parent = sfxPool.transform;
var source = sfxSource.AddComponent<AudioSource>();
source.playOnAwake = false;
}
}
private string QuickPrototype(Dictionary<string, string> parameters)
{
try
{
var elements = parameters.GetValueOrDefault("elements", "player,enemies,collectibles,obstacles");
var worldSize = float.Parse(parameters.GetValueOrDefault("worldSize", "20"));
var playerType = parameters.GetValueOrDefault("playerType", "Capsule");
var elementsList = elements.Split(',').Select(e => e.Trim()).ToList();
var createdObjects = new List<string>();
// WorldEnvironment
var world = new GameObject("PrototypeWorld");
createdObjects.Add(world.name);
// Ground
var ground = CreatePrimitiveWithMaterial(PrimitiveType.Plane);
ground.name = "Ground";
ground.transform.parent = world.transform;
ground.transform.localScale = new Vector3(worldSize / 10, 1, worldSize / 10);
var groundMat = CreateRenderPipelineCompatibleMaterial();
groundMat.color = new Color(0.5f, 0.5f, 0.5f);
ground.GetComponent<Renderer>().material = groundMat;
createdObjects.Add(ground.name);
// PlayerCreate
if (elementsList.Contains("player"))
{
var player = CreatePrototypePlayer(playerType);
player.transform.parent = world.transform;
player.transform.position = Vector3.up;
createdObjects.Add(player.name);
}
// EnemyCreate
if (elementsList.Contains("enemies"))
{
var enemies = new GameObject("Enemies");
enemies.transform.parent = world.transform;
for (int i = 0; i < 5; i++)
{
var enemy = CreatePrimitiveWithMaterial(PrimitiveType.Cube, Color.red);
enemy.name = $"Enemy_{i}";
enemy.transform.parent = enemies.transform;
enemy.transform.position = new Vector3(
Random.Range(-worldSize/2, worldSize/2),
0.5f,
Random.Range(-worldSize/2, worldSize/2)
);
enemy.AddComponent<Rigidbody>();
}
createdObjects.Add("Enemies (5)");
}
// Collectibles
if (elementsList.Contains("collectibles"))
{
var collectibles = new GameObject("Collectibles");
collectibles.transform.parent = world.transform;
for (int i = 0; i < 10; i++)
{
var item = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, Color.yellow);
item.name = $"Collectible_{i}";
item.transform.parent = collectibles.transform;
item.transform.position = new Vector3(
Random.Range(-worldSize/2, worldSize/2),
0.5f,
Random.Range(-worldSize/2, worldSize/2)
);
item.transform.localScale = Vector3.one * 0.5f;
var collider = item.GetComponent<SphereCollider>();
collider.isTrigger = true;
}
createdObjects.Add("Collectibles (10)");
}
// Obstacles
if (elementsList.Contains("obstacles"))
{
var obstacles = new GameObject("Obstacles");
obstacles.transform.parent = world.transform;
for (int i = 0; i < 8; i++)
{
var obstacle = CreatePrimitiveWithMaterial(PrimitiveType.Cube, new Color(0.3f, 0.3f, 0.3f));
obstacle.name = $"Obstacle_{i}";
obstacle.transform.parent = obstacles.transform;
obstacle.transform.position = new Vector3(
Random.Range(-worldSize/2, worldSize/2),
Random.Range(0.5f, 2f),
Random.Range(-worldSize/2, worldSize/2)
);
obstacle.transform.localScale = new Vector3(
Random.Range(1f, 3f),
Random.Range(1f, 4f),
Random.Range(1f, 3f)
);
}
createdObjects.Add("Obstacles (8)");
}
// Basic lighting settings
var lighting = GameObject.Find("Directional Light");
if (lighting == null)
{
lighting = new GameObject("Directional Light");
var light = lighting.AddComponent<Light>();
light.type = LightType.Directional;
light.intensity = 1.5f;
light.shadows = LightShadows.Soft;
}
lighting.transform.rotation = Quaternion.Euler(45, -30, 0);
// CameraSettings
var mainCamera = Camera.main ?? new GameObject("Main Camera").AddComponent<Camera>();
mainCamera.transform.position = new Vector3(0, 10, -10);
mainCamera.transform.LookAt(Vector3.zero);
return JsonConvert.SerializeObject(new
{
success = true,
message = "Created quick prototype",
worldSize = worldSize,
elements = elementsList,
objectsCreated = createdObjects,
cameraPosition = mainCamera.transform.position.ToString()
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private GameObject CreatePrototypePlayer(string type)
{
GameObject player = null;
switch (type)
{
case "Capsule":
player = CreatePrimitiveWithMaterial(PrimitiveType.Capsule, Color.blue);
break;
case "Cube":
player = CreatePrimitiveWithMaterial(PrimitiveType.Cube, Color.blue);
break;
case "Sphere":
player = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, Color.blue);
break;
default:
player = CreatePrimitiveWithMaterial(PrimitiveType.Capsule, Color.blue);
break;
}
player.name = "Player";
// BasicMovementComponent
var rb = player.AddComponent<Rigidbody>();
rb.constraints = RigidbodyConstraints.FreezeRotation;
// Simple movement script
var moveScript = @"
using UnityEngine;
using SynapticAIPro;
public class SimpleMove : MonoBehaviour {
public float speed = 5f;
Rigidbody rb;
void Start() {
rb = GetComponent<Rigidbody>();
}
void Update() {
float h = Input.GetAxis(""Horizontal"");
float v = Input.GetAxis(""Vertical"");
Vector3 movement = new Vector3(h, 0, v) * speed * Time.deltaTime;
rb.MovePosition(transform.position + movement);
}
}";
// ScriptFileCreate
var scriptPath = "Assets/Scripts/SimpleMove.cs";
var scriptDir = System.IO.Path.GetDirectoryName(scriptPath);
if (!System.IO.Directory.Exists(scriptDir))
{
System.IO.Directory.CreateDirectory(scriptDir);
}
System.IO.File.WriteAllText(scriptPath, moveScript);
AssetDatabase.Refresh();
return player;
}
#region AI and machine learning related methods
/// <summary>
/// Setup ML Agent (reinforcement learning)
/// </summary>
private string SetupMLAgent(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "MLAgent");
string agentType = parameters.GetValueOrDefault("agentType", "Basic");
int vectorObservationSize = Convert.ToInt32(parameters.GetValueOrDefault("vectorObservationSize", "8"));
bool useVisualObservation = Convert.ToBoolean(parameters.GetValueOrDefault("useVisualObservation", "false"));
// AgentObjectCreate
var agent = new GameObject(agentName);
var agentScript = GenerateMLAgentScript(agentName, agentType, vectorObservationSize, useVisualObservation);
// ScriptSave
var scriptPath = $"Assets/Scripts/AI/ML/{agentName}.cs";
var scriptDir = System.IO.Path.GetDirectoryName(scriptPath);
if (!System.IO.Directory.Exists(scriptDir))
{
System.IO.Directory.CreateDirectory(scriptDir);
}
System.IO.File.WriteAllText(scriptPath, agentScript);
// AgentEnvironmentSettings
var environment = new GameObject($"{agentName}Environment");
var platform = CreatePrimitiveWithMaterial(PrimitiveType.Cube);
platform.name = "Platform";
platform.transform.parent = environment.transform;
platform.transform.localScale = new Vector3(10, 0.5f, 10);
platform.transform.position = Vector3.zero;
// GoalSettings
var goal = CreatePrimitiveWithMaterial(PrimitiveType.Sphere, Color.green);
goal.name = "Goal";
goal.transform.parent = environment.transform;
goal.transform.position = new Vector3(Random.Range(-4f, 4f), 1f, Random.Range(-4f, 4f));
agent.transform.parent = environment.transform;
agent.transform.position = new Vector3(0, 1, 0);
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"ML Agent '{agentName}' setup completed\\nAgent Type: {agentType}\\nObservation Size: {vectorObservationSize}\\nVisual Observation: {useVisualObservation}\\nScript: {scriptPath}"
}
}
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Error setting up ML Agent: {e.Message}"
}
}
});
}
}
/// <summary>
/// Create neural network system
/// </summary>
private string CreateNeuralNetwork(Dictionary<string, string> parameters)
{
try
{
string networkName = parameters.GetValueOrDefault("networkName", "NeuralNetwork");
string networkType = parameters.GetValueOrDefault("networkType", "Feedforward");
int inputSize = Convert.ToInt32(parameters.GetValueOrDefault("inputSize", "4"));
int hiddenSize = Convert.ToInt32(parameters.GetValueOrDefault("hiddenSize", "8"));
int outputSize = Convert.ToInt32(parameters.GetValueOrDefault("outputSize", "2"));
var networkScript = GenerateNeuralNetworkScript(networkName, networkType, inputSize, hiddenSize, outputSize);
// ScriptSave
var scriptPath = $"Assets/Scripts/AI/Neural/{networkName}.cs";
var scriptDir = System.IO.Path.GetDirectoryName(scriptPath);
if (!System.IO.Directory.Exists(scriptDir))
{
System.IO.Directory.CreateDirectory(scriptDir);
}
System.IO.File.WriteAllText(scriptPath, networkScript);
// Create test object
var testObject = new GameObject($"{networkName}Test");
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Neural Network '{networkName}' created\\nType: {networkType}\\nArchitecture: {inputSize} → {hiddenSize} → {outputSize}\\nScript: {scriptPath}"
}
}
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Error creating neural network: {e.Message}"
}
}
});
}
}
/// <summary>
/// Setup behavior tree system
/// </summary>
private string SetupBehaviorTree(Dictionary<string, string> parameters)
{
try
{
string treeName = parameters.GetValueOrDefault("treeName", "BehaviorTree");
string aiType = parameters.GetValueOrDefault("aiType", "Enemy");
bool includePatrol = Convert.ToBoolean(parameters.GetValueOrDefault("includePatrol", "true"));
bool includeChase = Convert.ToBoolean(parameters.GetValueOrDefault("includeChase", "true"));
bool includeAttack = Convert.ToBoolean(parameters.GetValueOrDefault("includeAttack", "true"));
var treeScript = GenerateBehaviorTreeScript(treeName, aiType, includePatrol, includeChase, includeAttack);
// ScriptSave
var scriptPath = $"Assets/Scripts/AI/BehaviorTree/{treeName}.cs";
var scriptDir = System.IO.Path.GetDirectoryName(scriptPath);
if (!System.IO.Directory.Exists(scriptDir))
{
System.IO.Directory.CreateDirectory(scriptDir);
}
System.IO.File.WriteAllText(scriptPath, treeScript);
// Create AI object
var aiObject = new GameObject($"{treeName}AI");
aiObject.AddComponent<CapsuleCollider>();
aiObject.AddComponent<Rigidbody>();
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Behavior Tree '{treeName}' setup completed\\nAI Type: {aiType}\\nPatrol: {includePatrol}, Chase: {includeChase}, Attack: {includeAttack}\\nScript: {scriptPath}"
}
}
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Error setting up behavior tree: {e.Message}"
}
}
});
}
}
// ===== GOAP AI System Methods =====
private string CreateGoapAgent(Dictionary<string, string> parameters)
{
try
{
// Parameter validation
if (parameters == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No parameters provided" });
}
string name = parameters.GetValueOrDefault("name", "GOAPAgent");
string agentType = parameters.GetValueOrDefault("agentType", "Generic");
// Check for duplicate names
if (GameObject.Find(name) != null)
{
name = $"{name}_{Random.Range(1000, 9999)}";
SynLog.Warn($"[CreateGoapAgent] Name conflict resolved, using: {name}");
}
Vector3 position;
try
{
position = ParseVector3(parameters.GetValueOrDefault("position", "0,0,0"));
}
catch (Exception ex)
{
SynLog.Warn($"[CreateGoapAgent] Failed to parse position, using origin: {ex.Message}");
position = Vector3.zero;
}
// Create GOAP agent GameObject
GameObject agent = null;
try
{
agent = CreatePrimitiveWithMaterial(PrimitiveType.Capsule);
agent.name = name;
agent.transform.position = position;
// Add Rigidbody (for GOAP movement)
var rb = agent.AddComponent<Rigidbody>();
rb.useGravity = true;
rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
// Tag for GOAP agent identification
agent.tag = "Untagged"; // Can be changed to GOAPAgent tag later
}
catch (Exception ex)
{
Debug.LogError($"[CreateGoapAgent] Failed to create agent GameObject: {ex.Message}");
return JsonConvert.SerializeObject(new { success = false, error = $"Failed to create agent: {ex.Message}" });
}
// Add actual GOAP Agent component
GOAPAgent goapAgentComponent = null;
try
{
goapAgentComponent = agent.AddComponent<GOAPAgent>();
// Initialize basic world state based on agent type
if (goapAgentComponent != null)
{
// Set initial world state
goapAgentComponent.SetWorldState("health", 100f);
goapAgentComponent.SetWorldState("has_weapon", true);
goapAgentComponent.SetWorldState("is_alive", true);
goapAgentComponent.SetWorldState("stamina", 100f);
SynLog.Info($"[GOAP] Added GOAPAgent component to '{name}'");
}
}
catch (Exception ex)
{
SynLog.Warn($"[CreateGoapAgent] Failed to add GOAPAgent component: {ex.Message}");
}
// Save metadata (serialization safe)
var metadata = new Dictionary<string, object>
{
["agentType"] = agentType,
["capabilities"] = parameters.GetValueOrDefault("capabilities", "[]"),
["createdAt"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
["goapVersion"] = "v3",
["position"] = new Dictionary<string, float>
{
["x"] = position.x,
["y"] = position.y,
["z"] = position.z
}
};
lastCreatedObject = agent;
createdObjects.Add(agent);
SynLog.Info($"[GOAP] Created agent '{name}' at position {position} with type '{agentType}'");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created GOAP agent: {name}",
agentId = agent.GetInstanceID(),
agentName = name,
position = new { x = position.x, y = position.y, z = position.z },
metadata = metadata,
components = new string[] { "Rigidbody", "CapsuleCollider", "Transform" }
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[CreateGoapAgent] Unexpected error: {e.Message}\nStackTrace: {e.StackTrace}");
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message,
stackTrace = e.StackTrace,
details = "Failed to create GOAP agent"
}, Formatting.Indented);
}
}
private string DefineGoapGoal(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
string goalName = parameters.GetValueOrDefault("goalName", "DefaultGoal");
string description = parameters.GetValueOrDefault("description", "");
int priority = int.Parse(parameters.GetValueOrDefault("priority", "50"));
string conditions = parameters.GetValueOrDefault("conditions", "");
// Search for agent
var agent = GameObject.Find(agentName);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found"
});
}
// Get or add GOAPAgent component
var goapAgent = agent.GetComponent<GOAPAgent>();
if (goapAgent == null)
{
goapAgent = agent.AddComponent<GOAPAgent>();
}
// Create GOAPGoal and add as component
var goalComponent = agent.AddComponent<GOAPGoalComponent>();
// Create the goal object
var goal = new GOAPGoal(goalName, priority);
goal.IsActive = true;
// Parse conditions and add to goal
var parsedConditions = ParseGoalConditions(conditions);
foreach (var cond in parsedConditions)
{
goal.AddCondition(cond.Key, cond.Value);
}
// Add goal to agent
goapAgent.AddGoal(goal);
SynLog.Info($"[GOAP] Defined goal '{goalName}' (priority: {priority}) for agent '{agentName}'");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Goal '{goalName}' defined for agent '{agentName}'",
goal = new
{
name = goalName,
description = description,
priority = priority,
conditions = parsedConditions,
createdAt = DateTime.Now
}
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private Dictionary<string, object> ParseGoalConditions(string conditions)
{
var result = new Dictionary<string, object>();
if (string.IsNullOrWhiteSpace(conditions)) return result;
var lower = conditions.ToLower();
// Parse common conditions from natural language
if (lower.Contains("enemy") && (lower.Contains("dead") || lower.Contains("eliminated") || lower.Contains("killed")))
{
result["enemy_eliminated"] = true;
}
if (lower.Contains("health") && lower.Contains("full"))
{
result["health_full"] = true;
}
if (lower.Contains("safe") || lower.Contains("no danger"))
{
result["is_safe"] = true;
}
if (lower.Contains("has") && lower.Contains("item"))
{
result["has_item"] = true;
}
if (lower.Contains("at") && lower.Contains("destination"))
{
result["at_destination"] = true;
}
if (lower.Contains("resource") && lower.Contains("collected"))
{
result["resources_collected"] = true;
}
return result;
}
private string CreateGoapAction(Dictionary<string, string> parameters)
{
try
{
// Parameter validation
if (parameters == null || parameters.Count == 0)
{
return JsonConvert.SerializeObject(new { success = false, error = "No parameters provided" });
}
string agentName = parameters.GetValueOrDefault("agentName", "");
if (string.IsNullOrWhiteSpace(agentName))
{
return JsonConvert.SerializeObject(new { success = false, error = "Agent name is required" });
}
string actionName = parameters.GetValueOrDefault("actionName", "DefaultAction");
string description = parameters.GetValueOrDefault("description", "");
string preconditionsStr = parameters.GetValueOrDefault("preconditions", "[]");
string effectsStr = parameters.GetValueOrDefault("effects", "[]");
float cost;
if (!float.TryParse(parameters.GetValueOrDefault("cost", "1"), out cost) || cost < 0)
{
cost = 1.0f;
SynLog.Warn($"[CreateGoapAction] Invalid cost value, using default: {cost}");
}
var agent = GameObject.Find(agentName);
if (agent == null)
{
SynLog.Warn($"[CreateGoapAction] Agent '{agentName}' not found in scene");
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found in scene. Available objects: {GetSceneObjectNames()}"
});
}
// JSON parse (safe)
string[] preconditions = null;
string[] effects = null;
try
{
if (!string.IsNullOrWhiteSpace(preconditionsStr) && preconditionsStr != "[]")
{
preconditions = JsonConvert.DeserializeObject<string[]>(preconditionsStr);
}
else
{
preconditions = new string[0];
}
}
catch (Exception ex)
{
SynLog.Warn($"[CreateGoapAction] Failed to parse preconditions JSON: {ex.Message}");
preconditions = new string[0];
}
try
{
if (!string.IsNullOrWhiteSpace(effectsStr) && effectsStr != "[]")
{
effects = JsonConvert.DeserializeObject<string[]>(effectsStr);
}
else
{
effects = new string[0];
}
}
catch (Exception ex)
{
SynLog.Warn($"[CreateGoapAction] Failed to parse effects JSON: {ex.Message}");
effects = new string[0];
}
var actionData = new Dictionary<string, object>
{
["name"] = actionName,
["description"] = description,
["preconditions"] = preconditions ?? new string[0],
["effects"] = effects ?? new string[0],
["cost"] = cost,
["createdAt"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
["agentName"] = agentName
};
SynLog.Info($"[GOAP] Created action '{actionName}' for agent '{agentName}' with {preconditions?.Length ?? 0} preconditions and {effects?.Length ?? 0} effects");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Action '{actionName}' created for agent '{agentName}' successfully",
action = actionData,
agentInfo = new
{
name = agentName,
position = agent.transform.position,
active = agent.activeInHierarchy
}
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[CreateGoapAction] Unexpected error: {e.Message}\nStackTrace: {e.StackTrace}");
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message,
stackTrace = e.StackTrace,
details = "Failed to create GOAP action"
}, Formatting.Indented);
}
}
private string DefineBehaviorLanguage(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
string behavior = parameters.GetValueOrDefault("behavior", "");
string gameContext = parameters.GetValueOrDefault("gameContext", "Generic");
string difficulty = parameters.GetValueOrDefault("difficulty", "normal");
var agent = GameObject.Find(agentName);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found"
});
}
// Get or add GOAPAgent component
var goapAgent = agent.GetComponent<GOAPAgent>();
if (goapAgent == null)
{
goapAgent = agent.AddComponent<GOAPAgent>();
}
// Parse natural language and generate GOAP components
var behaviorComponents = ParseNaturalLanguageBehavior(behavior, gameContext, difficulty);
// Apply parsed goals to agent
var goalsData = behaviorComponents["goals"] as List<object>;
if (goalsData != null)
{
foreach (var goalData in goalsData)
{
var goalDict = goalData as dynamic;
if (goalDict != null)
{
try
{
string goalName = goalDict.name?.ToString() ?? "Goal";
int priority = 50;
if (goalDict.priority != null)
{
int.TryParse(goalDict.priority.ToString(), out priority);
}
var goal = new GOAPGoal(goalName, priority);
goal.IsActive = true;
goapAgent.AddGoal(goal);
}
catch { }
}
}
}
// Apply parsed actions to agent
var actionsData = behaviorComponents["actions"] as List<object>;
if (actionsData != null)
{
foreach (var actionData in actionsData)
{
try
{
var actionDict = actionData as dynamic;
if (actionDict != null)
{
string actionName = actionDict.name?.ToString() ?? "Action";
float actionCost = 1f;
if (actionDict.cost != null)
{
float.TryParse(actionDict.cost.ToString(), out actionCost);
}
var preconditions = actionDict.preconditions as string[] ?? new string[0];
var effects = actionDict.effects as string[] ?? new string[0];
// Create dynamic action
var dynamicAction = GOAPActionFactory.CreateFromBehaviorData(
agent,
actionName,
actionCost,
preconditions,
effects,
1f
);
goapAgent.AddAction(dynamicAction);
}
}
catch (Exception ex)
{
SynLog.Warn($"[GOAP] Failed to create action: {ex.Message}");
}
}
}
// Apply world state
var worldStateData = behaviorComponents["worldState"] as Dictionary<string, object>;
if (worldStateData != null)
{
foreach (var kvp in worldStateData)
{
goapAgent.SetWorldState(kvp.Key, kvp.Value);
}
}
// Generated behavior pattern for response
var generatedBehavior = new
{
goals = behaviorComponents["goals"],
actions = behaviorComponents["actions"],
worldState = behaviorComponents["worldState"],
sensors = behaviorComponents["sensors"],
originalDescription = behavior,
context = gameContext,
difficulty = difficulty,
appliedToAgent = true
};
SynLog.Info($"[GOAP] Defined behavior for agent '{agentName}' from natural language: {behavior}");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Behavior defined and applied to agent '{agentName}'",
behavior = generatedBehavior,
agentComponents = new
{
hasGOAPAgent = true,
goalCount = goalsData?.Count ?? 0,
actionCount = actionsData?.Count ?? 0
}
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string GenerateGoapActionSet(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
string agentRole = parameters.GetValueOrDefault("agentRole", "generic");
string environment = parameters.GetValueOrDefault("environment", "");
bool includeDefaults = bool.Parse(parameters.GetValueOrDefault("includeDefaults", "true"));
var agent = GameObject.Find(agentName);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found"
});
}
// Generate role-based action set
var actionSet = GenerateRoleBasedActions(agentRole, environment, includeDefaults);
SynLog.Info($"[GOAP] Generated action set for {agentRole} agent '{agentName}'");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Generated action set for agent '{agentName}'",
role = agentRole,
environment = environment,
actions = actionSet
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string SetupGoapWorldState(Dictionary<string, string> parameters)
{
try
{
// Parameter validation
if (parameters == null || parameters.Count == 0)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "No parameters provided"
});
}
string agentName = parameters.GetValueOrDefault("agentName", "");
if (string.IsNullOrWhiteSpace(agentName))
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Agent name is required"
});
}
string worldStateStr = parameters.GetValueOrDefault("worldState", "{}");
string sensorsStr = parameters.GetValueOrDefault("sensors", "[]");
float updateFrequency;
if (!float.TryParse(parameters.GetValueOrDefault("updateFrequency", "0.5"), out updateFrequency))
{
updateFrequency = 0.5f;
SynLog.Warn($"[SetupGoapWorldState] Invalid updateFrequency, using default: {updateFrequency}");
}
// Search for agent
var agent = GameObject.Find(agentName);
if (agent == null)
{
SynLog.Warn($"[SetupGoapWorldState] Agent '{agentName}' not found in scene");
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found in scene. Available objects: {GetSceneObjectNames()}"
});
}
// JSON deserialization (safe)
Dictionary<string, object> worldState = null;
string[] sensors = null;
try
{
if (!string.IsNullOrWhiteSpace(worldStateStr) && worldStateStr != "{}")
{
worldState = JsonConvert.DeserializeObject<Dictionary<string, object>>(worldStateStr);
}
else
{
worldState = new Dictionary<string, object>();
}
}
catch (Exception ex)
{
SynLog.Warn($"[SetupGoapWorldState] Failed to parse worldState JSON: {ex.Message}");
worldState = new Dictionary<string, object>();
}
try
{
if (!string.IsNullOrWhiteSpace(sensorsStr) && sensorsStr != "[]")
{
sensors = JsonConvert.DeserializeObject<string[]>(sensorsStr);
}
else
{
sensors = new string[0];
}
}
catch (Exception ex)
{
SynLog.Warn($"[SetupGoapWorldState] Failed to parse sensors JSON: {ex.Message}");
sensors = new string[0];
}
// Configure world state
var worldStateConfig = new
{
agentName = agentName,
state = worldState ?? new Dictionary<string, object>(),
sensors = sensors ?? new string[0],
updateFrequency = updateFrequency,
lastUpdate = DateTime.Now,
position = agent.transform.position,
isActive = agent.activeInHierarchy
};
SynLog.Info($"[GOAP] Setup world state for agent '{agentName}' with {worldState?.Count ?? 0} state variables and {sensors?.Length ?? 0} sensors");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"World state configured for agent '{agentName}' successfully",
config = worldStateConfig,
agentInfo = new
{
name = agentName,
position = agent.transform.position,
active = agent.activeInHierarchy,
hasRigidbody = agent.GetComponent<Rigidbody>() != null,
hasCollider = agent.GetComponent<Collider>() != null
}
}, Formatting.Indented);
}
catch (Exception e)
{
Debug.LogError($"[SetupGoapWorldState] Unexpected error: {e.Message}\nStackTrace: {e.StackTrace}");
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message,
stackTrace = e.StackTrace,
details = "Failed to setup GOAP world state"
}, Formatting.Indented);
}
}
// Helper methods: Get scene object names
private string GetSceneObjectNames()
{
try
{
var objects = GameObject.FindObjectsOfType<GameObject>();
var names = objects.Take(10).Select(obj => obj.name).ToArray();
return string.Join(", ", names) + (objects.Length > 10 ? "..." : "");
}
catch
{
return "Unable to retrieve scene objects";
}
}
private string CreateGoapTemplate(Dictionary<string, string> parameters)
{
try
{
string templateType = parameters.GetValueOrDefault("templateType", "generic");
string difficulty = parameters.GetValueOrDefault("difficulty", "normal");
string behaviorsStr = parameters.GetValueOrDefault("behaviors", "[]");
string customizationsStr = parameters.GetValueOrDefault("customizations", "{}");
var behaviors = JsonConvert.DeserializeObject<string[]>(behaviorsStr);
var customizations = JsonConvert.DeserializeObject<Dictionary<string, object>>(customizationsStr);
// Generate AI based on template
var template = GenerateGameSpecificTemplate(templateType, difficulty, behaviors, customizations);
// Create agent from template
var agent = CreateAgentFromTemplate(template);
lastCreatedObject = agent;
createdObjects.Add(agent);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created {templateType} GOAP template",
agentId = agent.GetInstanceID(),
template = template
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string DebugGoapDecisions(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
bool showGraph = bool.Parse(parameters.GetValueOrDefault("showGraph", "true"));
bool showWorldState = bool.Parse(parameters.GetValueOrDefault("showWorldState", "true"));
bool showPlan = bool.Parse(parameters.GetValueOrDefault("showPlan", "true"));
bool logToConsole = bool.Parse(parameters.GetValueOrDefault("logToConsole", "false"));
var agent = GameObject.Find(agentName);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found"
});
}
// Collect debug info (temporary data)
var debugInfo = new
{
currentGoal = "Patrol Area",
currentPlan = new[] { "MoveTo", "LookAround", "CheckForEnemies" },
worldState = new Dictionary<string, object>
{
["has_weapon"] = true,
["enemies_nearby"] = 0,
["health"] = 100,
["position"] = agent.transform.position.ToString()
},
planCost = 3.5f,
planningTime = "0.012s",
graphNodes = 15,
graphEdges = 23
};
if (showGraph)
{
SynLog.Info($"[GOAP Debug] Graph visualization enabled for {agentName}");
}
if (logToConsole)
{
SynLog.Info($"[GOAP Debug] {JsonConvert.SerializeObject(debugInfo, Formatting.Indented)}");
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Debug info for agent '{agentName}'",
debug = debugInfo
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string OptimizeGoapPerformance(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
int maxPlanDepth = int.Parse(parameters.GetValueOrDefault("maxPlanDepth", "10"));
float planningFrequency = float.Parse(parameters.GetValueOrDefault("planningFrequency", "1"));
bool enableMultithreading = bool.Parse(parameters.GetValueOrDefault("enableMultithreading", "true"));
int cacheSize = int.Parse(parameters.GetValueOrDefault("cacheSize", "100"));
// Specific agent or global optimization
var targetAgents = new List<GameObject>();
if (!string.IsNullOrEmpty(agentName))
{
var agent = GameObject.Find(agentName);
if (agent != null) targetAgents.Add(agent);
}
else
{
// Target all GOAP agents
targetAgents.AddRange(GameObject.FindObjectsOfType<GameObject>()
.Where(go => go.name.Contains("GOAP") || go.name.Contains("Agent")));
}
var optimizationSettings = new
{
maxPlanDepth = maxPlanDepth,
planningFrequency = planningFrequency,
enableMultithreading = enableMultithreading,
cacheSize = cacheSize,
affectedAgents = targetAgents.Count,
estimatedPerformanceGain = enableMultithreading ? "40-60%" : "10-20%"
};
SynLog.Info($"[GOAP] Applied optimization settings to {targetAgents.Count} agents");
return JsonConvert.SerializeObject(new
{
success = true,
message = "GOAP performance optimization applied",
settings = optimizationSettings
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
// ===== Behavior Tree Methods =====
/// <summary>
/// Create a BehaviorTreeRunner on a GameObject
/// </summary>
private string CreateBTAgent(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "BTAgent");
string targetObject = parameters.GetValueOrDefault("targetObject", "");
bool autoStart = bool.Parse(parameters.GetValueOrDefault("autoStart", "false"));
float tickInterval = float.Parse(parameters.GetValueOrDefault("tickInterval", "0"));
GameObject agent;
if (!string.IsNullOrEmpty(targetObject))
{
agent = GameObject.Find(targetObject);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Target object '{targetObject}' not found"
});
}
}
else
{
agent = new GameObject(agentName);
}
// Add BehaviorTreeRunner
var runner = agent.GetComponent<BehaviorTreeRunner>();
if (runner == null)
{
runner = agent.AddComponent<BehaviorTreeRunner>();
}
SynLog.Info($"[BT] Created BehaviorTreeRunner on '{agent.name}'");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"BehaviorTreeRunner added to '{agent.name}'",
agentName = agent.name,
autoStart = autoStart,
tickInterval = tickInterval
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Add a node to a behavior tree
/// </summary>
private string AddBTNode(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
string nodeType = parameters.GetValueOrDefault("nodeType", "Sequence");
string nodeName = parameters.GetValueOrDefault("nodeName", "Node");
string parentNode = parameters.GetValueOrDefault("parentNode", "");
var agent = GameObject.Find(agentName);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found"
});
}
var runner = agent.GetComponent<BehaviorTreeRunner>();
if (runner == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' does not have a BehaviorTreeRunner"
});
}
// Create node based on type
BTNode newNode = CreateBTNodeByType(nodeType, nodeName, parameters);
if (newNode == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Unknown node type: {nodeType}"
});
}
SynLog.Info($"[BT] Added {nodeType} node '{nodeName}' to '{agentName}'");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Added {nodeType} node '{nodeName}'",
nodeType = nodeType,
nodeName = nodeName
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private BTNode CreateBTNodeByType(string nodeType, string name, Dictionary<string, string> parameters)
{
switch (nodeType.ToLower())
{
// Composites
case "selector":
return new BTSelector(name);
case "sequence":
return new BTSequence(name);
case "parallel":
return new BTParallel(name);
case "randomselector":
return new BTRandomSelector(name);
case "randomsequence":
return new BTRandomSequence(name);
// Decorators
case "inverter":
return new BTInverter(name);
case "succeeder":
return new BTSucceeder(name);
case "failer":
return new BTFailer(name);
case "repeater":
int count = int.Parse(parameters.GetValueOrDefault("repeatCount", "-1"));
return new BTRepeater(count, name);
case "cooldown":
float cooldownTime = float.Parse(parameters.GetValueOrDefault("cooldownTime", "1"));
return new BTCooldown(cooldownTime, name);
case "timeout":
float timeout = float.Parse(parameters.GetValueOrDefault("timeout", "5"));
return new BTTimeout(timeout, name);
case "retry":
int retries = int.Parse(parameters.GetValueOrDefault("maxRetries", "3"));
return new BTRetry(retries, name);
case "delay":
float delay = float.Parse(parameters.GetValueOrDefault("delay", "1"));
return new BTDelay(delay, name);
// Leaves
case "wait":
float duration = float.Parse(parameters.GetValueOrDefault("duration", "1"));
return new BTWait(duration, name);
case "log":
string message = parameters.GetValueOrDefault("message", "Log");
return new BTLog(message, name);
default:
return null;
}
}
/// <summary>
/// Create a behavior tree from natural language description
/// </summary>
private string CreateBTFromDescription(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
string description = parameters.GetValueOrDefault("description", "");
string gameContext = parameters.GetValueOrDefault("gameContext", "Generic");
var agent = GameObject.Find(agentName);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found"
});
}
var runner = agent.GetComponent<BehaviorTreeRunner>();
if (runner == null)
{
runner = agent.AddComponent<BehaviorTreeRunner>();
}
// Parse description and create tree
var treeStructure = ParseBTDescription(description, gameContext);
// Build the actual tree
BTNode rootNode = BuildBTFromStructure(treeStructure);
if (rootNode != null)
{
runner.SetTree(rootNode);
}
SynLog.Info($"[BT] Created behavior tree from description for '{agentName}'");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Behavior tree created for '{agentName}'",
description = description,
structure = treeStructure
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private Dictionary<string, object> ParseBTDescription(string description, string gameContext)
{
var result = new Dictionary<string, object>();
var nodes = new List<Dictionary<string, object>>();
var lower = description.ToLower();
// Root is typically a Selector or Sequence
string rootType = "Selector";
if (lower.Contains("順番") || lower.Contains("sequence") || lower.Contains("then"))
{
rootType = "Sequence";
}
result["rootType"] = rootType;
// Parse behaviors
// Attack patterns
if (lower.Contains("attack") || lower.Contains("攻撃"))
{
nodes.Add(new Dictionary<string, object>
{
["type"] = "Sequence",
["name"] = "AttackSequence",
["children"] = new[] { "CheckEnemyVisible", "MoveToEnemy", "Attack" }
});
}
// Patrol patterns
if (lower.Contains("patrol") || lower.Contains("パトロール") || lower.Contains("巡回"))
{
nodes.Add(new Dictionary<string, object>
{
["type"] = "Sequence",
["name"] = "PatrolSequence",
["children"] = new[] { "GetNextWaypoint", "MoveToWaypoint", "Wait" }
});
}
// Flee patterns
if (lower.Contains("flee") || lower.Contains("escape") || lower.Contains("逃げ"))
{
nodes.Add(new Dictionary<string, object>
{
["type"] = "Sequence",
["name"] = "FleeSequence",
["children"] = new[] { "CheckHealthLow", "FindSafeSpot", "FleeToSafety" }
});
}
// Idle patterns
if (lower.Contains("idle") || lower.Contains("待機"))
{
nodes.Add(new Dictionary<string, object>
{
["type"] = "Action",
["name"] = "Idle",
["duration"] = 2f
});
}
// Chase patterns
if (lower.Contains("chase") || lower.Contains("follow") || lower.Contains("追跡") || lower.Contains("追いかけ"))
{
nodes.Add(new Dictionary<string, object>
{
["type"] = "Sequence",
["name"] = "ChaseSequence",
["children"] = new[] { "HasTarget", "MoveToTarget" }
});
}
result["nodes"] = nodes;
return result;
}
private BTNode BuildBTFromStructure(Dictionary<string, object> structure)
{
string rootType = structure.GetValueOrDefault("rootType", "Selector")?.ToString() ?? "Selector";
var nodes = structure.GetValueOrDefault("nodes", new List<Dictionary<string, object>>()) as List<Dictionary<string, object>>;
BTComposite root;
if (rootType == "Sequence")
{
root = new BTSequence("Root");
}
else
{
root = new BTSelector("Root");
}
if (nodes != null)
{
foreach (var nodeData in nodes)
{
string nodeType = nodeData.GetValueOrDefault("type", "Action")?.ToString() ?? "Action";
string nodeName = nodeData.GetValueOrDefault("name", "Node")?.ToString() ?? "Node";
BTNode node;
if (nodeType == "Sequence")
{
var seq = new BTSequence(nodeName);
// Add placeholder actions
seq.AddChild(new BTLog($"Executing {nodeName}"));
seq.AddChild(new BTWait(1f));
node = seq;
}
else if (nodeType == "Selector")
{
node = new BTSelector(nodeName);
}
else
{
node = new BTLog(nodeName);
}
root.AddChild(node);
}
}
return root;
}
/// <summary>
/// Start a behavior tree
/// </summary>
private string StartBT(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
var agent = GameObject.Find(agentName);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found"
});
}
var runner = agent.GetComponent<BehaviorTreeRunner>();
if (runner == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' does not have a BehaviorTreeRunner"
});
}
runner.StartTree();
SynLog.Info($"[BT] Started behavior tree on '{agentName}'");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Behavior tree started on '{agentName}'"
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Stop a behavior tree
/// </summary>
private string StopBT(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
var agent = GameObject.Find(agentName);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found"
});
}
var runner = agent.GetComponent<BehaviorTreeRunner>();
if (runner == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' does not have a BehaviorTreeRunner"
});
}
runner.StopTree();
SynLog.Info($"[BT] Stopped behavior tree on '{agentName}'");
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Behavior tree stopped on '{agentName}'"
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Debug behavior tree state
/// </summary>
private string DebugBT(Dictionary<string, string> parameters)
{
try
{
string agentName = parameters.GetValueOrDefault("agentName", "");
var agent = GameObject.Find(agentName);
if (agent == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' not found"
});
}
var runner = agent.GetComponent<BehaviorTreeRunner>();
if (runner == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Agent '{agentName}' does not have a BehaviorTreeRunner"
});
}
var debugInfo = new
{
agentName = agent.name,
isRunning = runner.IsRunning,
lastStatus = runner.LastStatus.ToString(),
hasTree = runner.RootNode != null,
rootNodeName = runner.RootNode?.Name ?? "None"
};
return JsonConvert.SerializeObject(new
{
success = true,
debugInfo = debugInfo
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
// Helper methods
private object ParseNaturalLanguageConditions(string conditions)
{
// Convert natural language conditions to GOAP conditions (simple implementation)
var parsedConditions = new List<object>();
if (conditions.ToLower().Contains("enemy"))
{
parsedConditions.Add(new { key = "enemy_detected", value = true });
}
if (conditions.ToLower().Contains("health") || conditions.ToLower().Contains("hp"))
{
parsedConditions.Add(new { key = "health_low", value = true });
}
if (conditions.ToLower().Contains("distance") || conditions.ToLower().Contains("near"))
{
parsedConditions.Add(new { key = "target_in_range", value = true });
}
return parsedConditions;
}
// ===== Advanced Natural Language GOAP Behavior Parser =====
// Supports English, Japanese, and complex sentence structures
private Dictionary<string, object> ParseNaturalLanguageBehavior(string behavior, string gameContext, string difficulty)
{
var result = new Dictionary<string, object>
{
["goals"] = new List<object>(),
["actions"] = new List<object>(),
["worldState"] = new Dictionary<string, object>(),
["sensors"] = new List<string>(),
["parsedIntent"] = new List<string>(),
["conditions"] = new List<object>()
};
var goals = (List<object>)result["goals"];
var actions = (List<object>)result["actions"];
var worldState = (Dictionary<string, object>)result["worldState"];
var sensors = (List<string>)result["sensors"];
var parsedIntent = (List<string>)result["parsedIntent"];
var conditions = (List<object>)result["conditions"];
var behaviorLower = behavior.ToLower();
// ===== Parse complex sentences =====
// Split by common delimiters for multi-behavior instructions
var sentences = SplitIntoSentences(behavior);
foreach (var sentence in sentences)
{
ParseSingleBehavior(sentence, goals, actions, worldState, sensors, parsedIntent, conditions, difficulty);
}
// ===== Parse conditional behaviors ("when X, do Y") =====
ParseConditionalBehaviors(behavior, goals, actions, worldState, sensors, conditions);
// ===== Parse priority modifiers =====
ParsePriorityModifiers(behavior, goals);
// ===== Parse numeric parameters =====
ParseNumericParameters(behavior, worldState);
// Difficulty adjustment
AdjustForDifficulty(result, difficulty);
// Game context adjustment
AdjustForGameContext(result, gameContext);
// Add default idle behavior if no actions defined
if (actions.Count == 0)
{
actions.Add(new { name = "Idle", cost = 0.1f, preconditions = new string[0], effects = new[] { "waiting" } });
goals.Add(new { name = "Wait", priority = 10 });
}
return result;
}
private string[] SplitIntoSentences(string text)
{
// Split by various delimiters (period, comma, "and", Japanese particles)
var delimiters = new[] { ".", ",", " and ", " then ", "、", "。", "そして", "それから", "かつ" };
var sentences = new List<string> { text };
foreach (var delim in delimiters)
{
var newSentences = new List<string>();
foreach (var s in sentences)
{
newSentences.AddRange(s.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries));
}
sentences = newSentences;
}
return sentences.Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
}
private void ParseSingleBehavior(string sentence, List<object> goals, List<object> actions,
Dictionary<string, object> worldState, List<string> sensors, List<string> parsedIntent,
List<object> conditions, string difficulty)
{
var lower = sentence.ToLower();
// ===== Movement & Navigation =====
if (MatchesAny(lower, "patrol", "巡回", "パトロール", "見回り", "wander", "roam"))
{
parsedIntent.Add("patrol");
goals.Add(new { name = "PatrolArea", priority = 80 });
actions.Add(new { name = "MoveTo", cost = 1f, preconditions = new[] { "has_path" }, effects = new[] { "at_waypoint" } });
actions.Add(new { name = "LookAround", cost = 0.5f, preconditions = new[] { "at_waypoint" }, effects = new[] { "area_scanned" } });
actions.Add(new { name = "NextWaypoint", cost = 0.2f, preconditions = new[] { "area_scanned" }, effects = new[] { "has_path" } });
sensors.AddRange(new[] { "position_sensor", "waypoint_manager", "threat_detector" });
worldState["patrol_route"] = "defined";
}
if (MatchesAny(lower, "follow", "chase", "pursue", "追跡", "追いかける", "ついていく", "尾行", "track"))
{
parsedIntent.Add("follow");
int priority = lower.Contains("aggressive") || lower.Contains("積極") ? 95 : 85;
goals.Add(new { name = "FollowTarget", priority = priority });
actions.Add(new { name = "AcquireTarget", cost = 0.5f, preconditions = new[] { "target_exists" }, effects = new[] { "target_acquired" } });
actions.Add(new { name = "TrackTarget", cost = 0.5f, preconditions = new[] { "target_acquired" }, effects = new[] { "target_tracked" } });
actions.Add(new { name = "MoveToTarget", cost = 1f, preconditions = new[] { "target_tracked" }, effects = new[] { "near_target" } });
actions.Add(new { name = "MaintainDistance", cost = 0.3f, preconditions = new[] { "near_target" }, effects = new[] { "optimal_distance" } });
sensors.AddRange(new[] { "target_tracker", "distance_sensor", "line_of_sight" });
// Extract follow distance if specified
var distMatch = System.Text.RegularExpressions.Regex.Match(lower, @"(\d+)\s*(m|meter|メートル|距離)");
if (distMatch.Success)
{
worldState["follow_distance"] = float.Parse(distMatch.Groups[1].Value);
}
}
if (MatchesAny(lower, "go to", "move to", "navigate", "移動", "向かう", "行く"))
{
parsedIntent.Add("navigate");
goals.Add(new { name = "ReachDestination", priority = 75 });
actions.Add(new { name = "PlanPath", cost = 0.5f, preconditions = new[] { "destination_set" }, effects = new[] { "path_planned" } });
actions.Add(new { name = "FollowPath", cost = 1f, preconditions = new[] { "path_planned" }, effects = new[] { "at_destination" } });
sensors.AddRange(new[] { "pathfinding_sensor", "obstacle_detector" });
}
// ===== Combat & Attack =====
if (MatchesAny(lower, "attack", "fight", "combat", "battle", "攻撃", "戦う", "戦闘", "倒す", "kill", "eliminate"))
{
parsedIntent.Add("combat");
int priority = lower.Contains("priority") || lower.Contains("優先") ? 100 : 90;
goals.Add(new { name = "EliminateEnemy", priority = priority });
// Determine combat style
if (MatchesAny(lower, "melee", "close", "近接", "剣", "sword", "knife", "殴る"))
{
actions.Add(new { name = "CloseDistance", cost = 1f, preconditions = new[] { "enemy_detected" }, effects = new[] { "in_melee_range" } });
actions.Add(new { name = "MeleeAttack", cost = 1.5f, preconditions = new[] { "in_melee_range", "has_melee_weapon" }, effects = new[] { "damage_dealt" } });
actions.Add(new { name = "Combo", cost = 2f, preconditions = new[] { "in_melee_range", "combo_ready" }, effects = new[] { "heavy_damage" } });
worldState["combat_style"] = "melee";
worldState["has_melee_weapon"] = true;
}
else if (MatchesAny(lower, "ranged", "shoot", "snipe", "gun", "射撃", "撃つ", "銃", "弓", "bow", "magic", "魔法"))
{
actions.Add(new { name = "TakeAim", cost = 0.5f, preconditions = new[] { "enemy_in_sight", "has_ranged_weapon" }, effects = new[] { "aiming" } });
actions.Add(new { name = "RangedAttack", cost = 1f, preconditions = new[] { "aiming", "has_ammo" }, effects = new[] { "damage_dealt" } });
actions.Add(new { name = "Reload", cost = 2f, preconditions = new[] { "ammo_low" }, effects = new[] { "ammo_full" } });
actions.Add(new { name = "FindCover", cost = 1f, preconditions = new[] { "under_fire" }, effects = new[] { "in_cover" } });
worldState["combat_style"] = "ranged";
worldState["has_ranged_weapon"] = true;
sensors.Add("ammo_counter");
}
else
{
// Generic combat
actions.Add(new { name = "EngageEnemy", cost = 1f, preconditions = new[] { "enemy_detected" }, effects = new[] { "in_combat_range" } });
actions.Add(new { name = "Attack", cost = 1.5f, preconditions = new[] { "in_combat_range", "has_weapon" }, effects = new[] { "damage_dealt" } });
worldState["has_weapon"] = true;
}
actions.Add(new { name = "ChaseEnemy", cost = 1.5f, preconditions = new[] { "enemy_fleeing" }, effects = new[] { "enemy_in_range" } });
sensors.AddRange(new[] { "enemy_detector", "weapon_status", "health_monitor" });
}
// ===== Defense & Protection =====
if (MatchesAny(lower, "defend", "protect", "guard", "防御", "守る", "護衛", "防衛", "shield"))
{
parsedIntent.Add("defend");
goals.Add(new { name = "DefendPosition", priority = 90 });
// What to defend
if (MatchesAny(lower, "base", "拠点", "home", "本拠地"))
{
actions.Add(new { name = "TakeDefensivePosition", cost = 0.5f, preconditions = new[] { "at_base" }, effects = new[] { "in_defensive_position" } });
worldState["defend_target"] = "base";
}
else if (MatchesAny(lower, "ally", "allies", "味方", "仲間", "team"))
{
actions.Add(new { name = "StayNearAlly", cost = 0.5f, preconditions = new[] { "ally_exists" }, effects = new[] { "near_ally" } });
actions.Add(new { name = "InterceptThreat", cost = 1f, preconditions = new[] { "threat_to_ally" }, effects = new[] { "threat_engaged" } });
worldState["defend_target"] = "ally";
sensors.Add("ally_tracker");
}
else if (MatchesAny(lower, "objective", "目標", "point", "ポイント"))
{
actions.Add(new { name = "GuardObjective", cost = 0.5f, preconditions = new[] { "at_objective" }, effects = new[] { "objective_guarded" } });
worldState["defend_target"] = "objective";
}
actions.Add(new { name = "HoldPosition", cost = 0.3f, preconditions = new[] { "in_defensive_position" }, effects = new[] { "position_held" } });
actions.Add(new { name = "RepelAttack", cost = 1f, preconditions = new[] { "under_attack" }, effects = new[] { "attack_repelled" } });
actions.Add(new { name = "RaiseAlarm", cost = 0.2f, preconditions = new[] { "enemy_detected", "alarm_available" }, effects = new[] { "alarm_raised" } });
sensors.AddRange(new[] { "threat_detector", "perimeter_sensor" });
}
// ===== Stealth & Evasion =====
if (MatchesAny(lower, "stealth", "sneak", "hide", "invisible", "ステルス", "隠れる", "忍ぶ", "潜む", "covert"))
{
parsedIntent.Add("stealth");
goals.Add(new { name = "RemainUndetected", priority = 95 });
actions.Add(new { name = "Hide", cost = 0.5f, preconditions = new[] { "hiding_spot_available" }, effects = new[] { "hidden" } });
actions.Add(new { name = "Sneak", cost = 1f, preconditions = new[] { "not_detected" }, effects = new[] { "position_changed_quietly" } });
actions.Add(new { name = "WaitInShadow", cost = 0.3f, preconditions = new[] { "in_shadow" }, effects = new[] { "detection_chance_reduced" } });
actions.Add(new { name = "Distract", cost = 1.5f, preconditions = new[] { "has_distraction_item" }, effects = new[] { "enemy_distracted" } });
actions.Add(new { name = "SilentTakedown", cost = 2f, preconditions = new[] { "behind_enemy", "not_detected" }, effects = new[] { "enemy_eliminated_quietly" } });
sensors.AddRange(new[] { "visibility_checker", "noise_monitor", "light_detector", "enemy_awareness" });
worldState["noise_level"] = 0;
worldState["visibility"] = 0;
}
if (MatchesAny(lower, "flee", "escape", "retreat", "run away", "逃げる", "退却", "撤退", "逃走"))
{
parsedIntent.Add("flee");
goals.Add(new { name = "Survive", priority = 120 });
actions.Add(new { name = "FindEscapeRoute", cost = 0.5f, preconditions = new[] { "threatened" }, effects = new[] { "escape_route_found" } });
actions.Add(new { name = "Retreat", cost = 0.5f, preconditions = new[] { "escape_route_found" }, effects = new[] { "distance_increased" } });
actions.Add(new { name = "FindCover", cost = 1f, preconditions = new[] { "cover_nearby" }, effects = new[] { "in_cover" } });
actions.Add(new { name = "CallForHelp", cost = 0.3f, preconditions = new[] { "allies_nearby" }, effects = new[] { "help_requested" } });
actions.Add(new { name = "Dodge", cost = 0.5f, preconditions = new[] { "attack_incoming" }, effects = new[] { "attack_dodged" } });
sensors.AddRange(new[] { "health_monitor", "escape_route_finder", "threat_level" });
}
// ===== Resource Management =====
if (MatchesAny(lower, "collect", "gather", "harvest", "collect resources", "収集", "集める", "採取", "回収", "loot"))
{
parsedIntent.Add("gather");
goals.Add(new { name = "GatherResources", priority = 70 });
actions.Add(new { name = "ScanForResources", cost = 0.5f, preconditions = new string[0], effects = new[] { "resources_located" } });
actions.Add(new { name = "MoveToResource", cost = 1f, preconditions = new[] { "resources_located" }, effects = new[] { "at_resource" } });
actions.Add(new { name = "CollectResource", cost = 1.5f, preconditions = new[] { "at_resource", "inventory_not_full" }, effects = new[] { "resource_collected" } });
actions.Add(new { name = "ReturnToBase", cost = 1f, preconditions = new[] { "inventory_full" }, effects = new[] { "at_base" } });
actions.Add(new { name = "DepositResources", cost = 0.5f, preconditions = new[] { "at_base", "has_resources" }, effects = new[] { "resources_deposited" } });
sensors.AddRange(new[] { "resource_detector", "inventory_monitor", "base_locator" });
}
// ===== Social & Interaction =====
if (MatchesAny(lower, "talk", "interact", "trade", "negotiate", "会話", "話す", "交渉", "取引", "communicate"))
{
parsedIntent.Add("social");
goals.Add(new { name = "SocialInteraction", priority = 60 });
actions.Add(new { name = "ApproachNPC", cost = 0.5f, preconditions = new[] { "npc_nearby" }, effects = new[] { "near_npc" } });
actions.Add(new { name = "InitiateDialogue", cost = 0.3f, preconditions = new[] { "near_npc" }, effects = new[] { "in_dialogue" } });
actions.Add(new { name = "Trade", cost = 1f, preconditions = new[] { "in_dialogue", "has_items" }, effects = new[] { "trade_complete" } });
actions.Add(new { name = "GetInformation", cost = 0.5f, preconditions = new[] { "in_dialogue" }, effects = new[] { "info_obtained" } });
sensors.AddRange(new[] { "npc_detector", "dialogue_state", "reputation_tracker" });
}
if (MatchesAny(lower, "help", "assist", "support", "aid", "助ける", "手伝う", "援護", "サポート", "heal ally"))
{
parsedIntent.Add("support");
goals.Add(new { name = "AssistAllies", priority = 85 });
actions.Add(new { name = "LocateAllyInNeed", cost = 0.5f, preconditions = new string[0], effects = new[] { "ally_located" } });
actions.Add(new { name = "MoveToAlly", cost = 1f, preconditions = new[] { "ally_located" }, effects = new[] { "near_ally" } });
actions.Add(new { name = "ProvideSupport", cost = 1f, preconditions = new[] { "near_ally" }, effects = new[] { "ally_supported" } });
actions.Add(new { name = "HealAlly", cost = 1.5f, preconditions = new[] { "near_ally", "has_healing" }, effects = new[] { "ally_healed" } });
actions.Add(new { name = "CoverAlly", cost = 1f, preconditions = new[] { "near_ally", "enemy_present" }, effects = new[] { "ally_protected" } });
sensors.AddRange(new[] { "ally_status_monitor", "team_health_tracker" });
}
// ===== Investigation & Exploration =====
if (MatchesAny(lower, "search", "investigate", "explore", "scout", "捜索", "調査", "探索", "偵察", "look for"))
{
parsedIntent.Add("investigate");
goals.Add(new { name = "InvestigateArea", priority = 65 });
actions.Add(new { name = "ScanArea", cost = 0.5f, preconditions = new string[0], effects = new[] { "area_scanned" } });
actions.Add(new { name = "MoveToPointOfInterest", cost = 1f, preconditions = new[] { "poi_detected" }, effects = new[] { "at_poi" } });
actions.Add(new { name = "ExamineObject", cost = 0.5f, preconditions = new[] { "at_poi" }, effects = new[] { "object_examined" } });
actions.Add(new { name = "MarkLocation", cost = 0.2f, preconditions = new[] { "important_find" }, effects = new[] { "location_marked" } });
actions.Add(new { name = "ReportFindings", cost = 0.3f, preconditions = new[] { "has_intel" }, effects = new[] { "intel_reported" } });
sensors.AddRange(new[] { "environment_scanner", "anomaly_detector", "memory_system" });
}
// ===== Survival =====
if (MatchesAny(lower, "survive", "survival", "stay alive", "生存", "生き残る", "サバイバル"))
{
parsedIntent.Add("survival");
goals.Add(new { name = "Survive", priority = 100 });
actions.Add(new { name = "FindFood", cost = 2f, preconditions = new[] { "hungry" }, effects = new[] { "has_food" } });
actions.Add(new { name = "Eat", cost = 0.5f, preconditions = new[] { "has_food" }, effects = new[] { "hunger_satisfied" } });
actions.Add(new { name = "FindWater", cost = 2f, preconditions = new[] { "thirsty" }, effects = new[] { "has_water" } });
actions.Add(new { name = "Drink", cost = 0.5f, preconditions = new[] { "has_water" }, effects = new[] { "thirst_satisfied" } });
actions.Add(new { name = "FindShelter", cost = 3f, preconditions = new[] { "exposed" }, effects = new[] { "in_shelter" } });
actions.Add(new { name = "Rest", cost = 2f, preconditions = new[] { "safe", "tired" }, effects = new[] { "rested" } });
sensors.AddRange(new[] { "hunger_monitor", "thirst_monitor", "fatigue_monitor", "danger_detector" });
worldState["hunger"] = 50;
worldState["thirst"] = 50;
worldState["fatigue"] = 0;
}
// ===== Work & Production =====
if (MatchesAny(lower, "build", "construct", "craft", "create", "建築", "建設", "作る", "製作", "クラフト"))
{
parsedIntent.Add("build");
goals.Add(new { name = "ConstructStructure", priority = 75 });
actions.Add(new { name = "GatherMaterials", cost = 2f, preconditions = new[] { "materials_needed" }, effects = new[] { "has_materials" } });
actions.Add(new { name = "MoveToBuildSite", cost = 1f, preconditions = new[] { "build_site_set" }, effects = new[] { "at_build_site" } });
actions.Add(new { name = "Build", cost = 3f, preconditions = new[] { "at_build_site", "has_materials" }, effects = new[] { "structure_progress" } });
actions.Add(new { name = "FinishConstruction", cost = 1f, preconditions = new[] { "structure_nearly_complete" }, effects = new[] { "structure_complete" } });
sensors.AddRange(new[] { "material_detector", "construction_progress", "blueprint_manager" });
}
// ===== Special Behaviors =====
if (MatchesAny(lower, "aggressive", "攻撃的", "好戦的", "hostile"))
{
worldState["aggression"] = 0.9f;
worldState["engagement_distance"] = "far";
}
if (MatchesAny(lower, "cautious", "careful", "慎重", "注意深い", "defensive"))
{
worldState["aggression"] = 0.3f;
worldState["risk_tolerance"] = 0.2f;
}
if (MatchesAny(lower, "smart", "intelligent", "clever", "賢い", "知的", "tactical"))
{
actions.Add(new { name = "AnalyzeSituation", cost = 0.5f, preconditions = new string[0], effects = new[] { "situation_analyzed" } });
actions.Add(new { name = "PlanStrategy", cost = 1f, preconditions = new[] { "situation_analyzed" }, effects = new[] { "strategy_planned" } });
sensors.Add("tactical_analyzer");
worldState["intelligence"] = "high";
}
if (MatchesAny(lower, "coward", "fearful", "臆病", "怖がり"))
{
worldState["fear_threshold"] = 0.3f;
worldState["flee_priority"] = 1.5f;
}
if (MatchesAny(lower, "berserk", "rage", "狂暴", "暴走", "frenzy"))
{
actions.Add(new { name = "EnterBerserkMode", cost = 0.5f, preconditions = new[] { "health_low" }, effects = new[] { "berserk_active" } });
actions.Add(new { name = "RecklessAttack", cost = 1f, preconditions = new[] { "berserk_active" }, effects = new[] { "massive_damage" } });
worldState["berserk_threshold"] = 0.3f;
}
}
private void ParseConditionalBehaviors(string behavior, List<object> goals, List<object> actions,
Dictionary<string, object> worldState, List<string> sensors, List<object> conditions)
{
var lower = behavior.ToLower();
// Parse "when X, do Y" patterns
var whenPatterns = new[]
{
@"when\s+(.+?),?\s+(do|then|start)\s+(.+)",
@"if\s+(.+?),?\s+(do|then)\s+(.+)",
@"(.+?)の時[はに]?\s*(.+)",
@"(.+?)したら\s*(.+)",
@"(.+?)なら\s*(.+)"
};
foreach (var pattern in whenPatterns)
{
var matches = System.Text.RegularExpressions.Regex.Matches(lower, pattern);
foreach (System.Text.RegularExpressions.Match match in matches)
{
if (match.Groups.Count >= 3)
{
var condition = match.Groups[1].Value.Trim();
var action = match.Groups[match.Groups.Count - 1].Value.Trim();
conditions.Add(new
{
trigger = ParseCondition(condition),
response = action,
priority = DetermineConditionPriority(condition)
});
}
}
}
// Parse health-based conditions
if (MatchesAny(lower, "when health low", "health below", "hp低い", "体力が少ない", "dying"))
{
conditions.Add(new { trigger = "health_below_30", response = "flee_or_heal", priority = 100 });
actions.Add(new { name = "EmergencyHeal", cost = 0.5f, preconditions = new[] { "health_critical", "has_healing" }, effects = new[] { "health_restored" } });
}
if (MatchesAny(lower, "when detected", "if spotted", "見つかったら", "発見されたら"))
{
conditions.Add(new { trigger = "detected", response = "react_to_detection", priority = 90 });
}
if (MatchesAny(lower, "when outnumbered", "多勢に無勢", "数で負けてる"))
{
conditions.Add(new { trigger = "outnumbered", response = "retreat_or_call_backup", priority = 85 });
actions.Add(new { name = "CallReinforcements", cost = 0.5f, preconditions = new[] { "outnumbered", "comms_available" }, effects = new[] { "reinforcements_called" } });
}
}
private string ParseCondition(string condition)
{
var lower = condition.ToLower();
if (MatchesAny(lower, "health", "hp", "体力", "ライフ"))
return "health_condition";
if (MatchesAny(lower, "enemy", "敵", "エネミー", "threat"))
return "enemy_detected";
if (MatchesAny(lower, "ally", "味方", "仲間"))
return "ally_condition";
if (MatchesAny(lower, "ammo", "弾", "弾薬"))
return "ammo_condition";
if (MatchesAny(lower, "detected", "spotted", "見つかった", "発見"))
return "detection_condition";
return "generic_condition";
}
private int DetermineConditionPriority(string condition)
{
var lower = condition.ToLower();
if (MatchesAny(lower, "critical", "emergency", "危険", "緊急"))
return 100;
if (MatchesAny(lower, "health", "hp", "dying"))
return 95;
if (MatchesAny(lower, "enemy", "threat", "攻撃"))
return 90;
return 70;
}
private void ParsePriorityModifiers(string behavior, List<object> goals)
{
var lower = behavior.ToLower();
// Modify priorities based on keywords
if (MatchesAny(lower, "priority", "important", "優先", "重要", "main"))
{
// Increase priority of recently added goals
// This is handled in goal creation
}
if (MatchesAny(lower, "secondary", "backup", "副次", "サブ", "optional"))
{
// Decrease priority
}
}
private void ParseNumericParameters(string behavior, Dictionary<string, object> worldState)
{
// Extract numeric values from natural language
var patterns = new Dictionary<string, string>
{
[@"(\d+)\s*%?\s*(health|hp|体力)"] = "health_threshold",
[@"(\d+)\s*(m|meter|メートル)\s*(range|距離|範囲)"] = "detection_range",
[@"(\d+)\s*(second|sec|秒)\s*(cooldown|クールダウン|待機)"] = "action_cooldown",
[@"speed\s*[:\s]*(\d+)"] = "movement_speed",
[@"(\d+)\s*(体|人|enemies|targets)"] = "max_targets"
};
foreach (var pattern in patterns)
{
var match = System.Text.RegularExpressions.Regex.Match(behavior.ToLower(), pattern.Key);
if (match.Success)
{
worldState[pattern.Value] = float.Parse(match.Groups[1].Value);
}
}
}
private bool MatchesAny(string text, params string[] keywords)
{
return keywords.Any(k => text.Contains(k.ToLower()));
}
private void AdjustForDifficulty(Dictionary<string, object> result, string difficulty)
{
var actions = (List<object>)result["actions"];
var worldState = (Dictionary<string, object>)result["worldState"];
var sensors = (List<string>)result["sensors"];
switch (difficulty.ToLower())
{
case "easy":
// Reduce action cost
foreach (var actionObj in actions)
{
var action = actionObj as Dictionary<string, object>;
if (action != null && action.ContainsKey("cost"))
{
action["cost"] = Convert.ToSingle(action["cost"]) * 0.8f;
}
}
worldState["reaction_time"] = 1.5f;
worldState["accuracy"] = 0.6f;
break;
case "hard":
// Reduce action cost and optimize efficiency
foreach (var actionObj in actions)
{
var action = actionObj as Dictionary<string, object>;
if (action != null && action.ContainsKey("cost"))
{
action["cost"] = Convert.ToSingle(action["cost"]) * 0.6f;
}
}
worldState["reaction_time"] = 0.3f;
worldState["accuracy"] = 0.9f;
// Additional actions
actions.Add(new { name = "PredictPlayerMove", cost = 0.5f, preconditions = new[] { "player_pattern_analyzed" }, effects = new[] { "player_move_predicted" } });
sensors.Add("pattern_analyzer");
break;
case "adaptive":
worldState["learning_rate"] = 0.1f;
actions.Add(new { name = "AnalyzePlayer", cost = 0.3f, preconditions = new[] { "player_observed" }, effects = new[] { "player_analyzed" } });
actions.Add(new { name = "AdaptStrategy", cost = 0.5f, preconditions = new[] { "player_analyzed" }, effects = new[] { "strategy_adapted" } });
sensors.Add("player_behavior_tracker");
break;
default: // normal
worldState["reaction_time"] = 0.8f;
worldState["accuracy"] = 0.75f;
break;
}
}
private void AdjustForGameContext(Dictionary<string, object> result, string gameContext)
{
var actions = (List<object>)result["actions"];
var worldState = (Dictionary<string, object>)result["worldState"];
var sensors = (List<string>)result["sensors"];
switch (gameContext.ToLower())
{
case "fps":
sensors.Add("line_of_sight_checker");
sensors.Add("sound_detector");
actions.Add(new { name = "TakeCover", cost = 0.5f, preconditions = new[] { "under_fire", "cover_available" }, effects = new[] { "in_cover" } });
actions.Add(new { name = "ThrowGrenade", cost = 2f, preconditions = new[] { "has_grenade", "enemy_grouped" }, effects = new[] { "area_damage_dealt" } });
break;
case "rts":
sensors.Add("fog_of_war_revealer");
sensors.Add("resource_calculator");
actions.Add(new { name = "ConstructBuilding", cost = 3f, preconditions = new[] { "has_resources", "build_location_valid" }, effects = new[] { "building_constructed" } });
actions.Add(new { name = "ScoutArea", cost = 1f, preconditions = new[] { "area_unexplored" }, effects = new[] { "area_revealed" } });
break;
case "rpg":
sensors.Add("quest_tracker");
sensors.Add("dialogue_manager");
actions.Add(new { name = "InteractWithNPC", cost = 0.5f, preconditions = new[] { "npc_nearby" }, effects = new[] { "dialogue_initiated" } });
actions.Add(new { name = "UseSkill", cost = 1f, preconditions = new[] { "skill_available", "mana_sufficient" }, effects = new[] { "skill_used" } });
worldState["mana"] = 100;
break;
case "stealth":
sensors.Add("light_level_detector");
sensors.Add("guard_patrol_tracker");
actions.Add(new { name = "ExtinguishLight", cost = 1f, preconditions = new[] { "light_source_nearby" }, effects = new[] { "area_darkened" } });
actions.Add(new { name = "CreateNoise", cost = 0.5f, preconditions = new[] { "has_noise_maker" }, effects = new[] { "guard_distracted" } });
worldState["detection_level"] = 0;
break;
case "survival":
sensors.Add("hunger_monitor");
sensors.Add("temperature_sensor");
actions.Add(new { name = "FindFood", cost = 2f, preconditions = new[] { "hungry" }, effects = new[] { "has_food" } });
actions.Add(new { name = "BuildShelter", cost = 3f, preconditions = new[] { "has_materials", "shelter_needed" }, effects = new[] { "has_shelter" } });
worldState["hunger"] = 50;
worldState["temperature"] = 20;
break;
}
}
private List<object> GenerateRoleBasedActions(string role, string environment, bool includeDefaults)
{
var actions = new List<object>();
// Default actions
if (includeDefaults)
{
actions.Add(new { name = "Idle", cost = 0.1f, preconditions = new string[0], effects = new[] { "rested" } });
actions.Add(new { name = "MoveTo", cost = 1f, preconditions = new[] { "has_path" }, effects = new[] { "at_target" } });
}
// Role-specific actions
switch (role.ToLower())
{
case "guard":
actions.Add(new { name = "Patrol", cost = 1f, preconditions = new[] { "on_duty" }, effects = new[] { "area_checked" } });
actions.Add(new { name = "Alert", cost = 0.5f, preconditions = new[] { "enemy_detected" }, effects = new[] { "alarm_raised" } });
actions.Add(new { name = "Engage", cost = 2f, preconditions = new[] { "has_weapon", "enemy_in_range" }, effects = new[] { "enemy_eliminated" } });
break;
case "worker":
actions.Add(new { name = "Gather", cost = 1.5f, preconditions = new[] { "resource_available" }, effects = new[] { "has_resource" } });
actions.Add(new { name = "Build", cost = 2f, preconditions = new[] { "has_materials" }, effects = new[] { "structure_built" } });
actions.Add(new { name = "Repair", cost = 1f, preconditions = new[] { "structure_damaged" }, effects = new[] { "structure_repaired" } });
break;
case "enemy":
actions.Add(new { name = "Hunt", cost = 1.5f, preconditions = new[] { "target_detected" }, effects = new[] { "target_found" } });
actions.Add(new { name = "Attack", cost = 2f, preconditions = new[] { "in_attack_range" }, effects = new[] { "damage_dealt" } });
actions.Add(new { name = "CallBackup", cost = 0.5f, preconditions = new[] { "outnumbered" }, effects = new[] { "reinforcements_called" } });
break;
}
// Additional actions based on environment
if (!string.IsNullOrEmpty(environment))
{
switch (environment.ToLower())
{
case "forest":
actions.Add(new { name = "Hide", cost = 0.5f, preconditions = new[] { "in_forest" }, effects = new[] { "hidden" } });
break;
case "urban":
actions.Add(new { name = "UseCover", cost = 0.5f, preconditions = new[] { "cover_available" }, effects = new[] { "in_cover" } });
break;
}
}
return actions;
}
private GameObject CreateAgentFromTemplate(object template)
{
// Create agent from template
var agent = CreatePrimitiveWithMaterial(PrimitiveType.Capsule);
agent.name = "GOAP_Agent_" + DateTime.Now.Ticks;
// Apply settings based on template
// In actual implementation, analyze template data and add appropriate components
return agent;
}
private Dictionary<string, object> GenerateGameSpecificTemplate(string templateType, string difficulty, string[] behaviors, Dictionary<string, object> customizations)
{
var template = new Dictionary<string, object>();
template["type"] = templateType;
template["difficulty"] = difficulty;
template["behaviors"] = behaviors;
template["customizations"] = customizations;
// ===== Professional GOAP Templates Based on Industry Standards =====
switch (templateType.ToLower())
{
// ===== FPS Game Templates =====
case "fps_enemy":
case "fps_soldier":
template["defaultGoals"] = new[]
{
new { name = "EliminateTarget", priority = 100, conditions = new[] { "target_visible", "has_weapon" } },
new { name = "TakeCover", priority = 90, conditions = new[] { "under_fire", "health_low" } },
new { name = "Patrol", priority = 50, conditions = new[] { "no_threats" } },
new { name = "InvestigateSound", priority = 70, conditions = new[] { "suspicious_sound_heard" } },
new { name = "RequestBackup", priority = 85, conditions = new[] { "outnumbered", "radio_available" } }
};
template["defaultActions"] = new[]
{
new { name = "Shoot", cost = 1.0f, preconditions = new[] { "has_ammo", "target_in_sight" }, effects = new[] { "damage_dealt" } },
new { name = "Reload", cost = 2.0f, preconditions = new[] { "ammo_low", "in_cover" }, effects = new[] { "ammo_full" } },
new { name = "TakeCover", cost = 0.5f, preconditions = new[] { "cover_available" }, effects = new[] { "in_cover", "protected" } },
new { name = "FlankEnemy", cost = 3.0f, preconditions = new[] { "flank_route_available" }, effects = new[] { "better_position" } },
new { name = "ThrowGrenade", cost = 2.5f, preconditions = new[] { "has_grenade", "enemies_grouped" }, effects = new[] { "area_damage" } },
new { name = "SuppressiveFire", cost = 1.5f, preconditions = new[] { "has_ammo", "ally_moving" }, effects = new[] { "enemy_suppressed" } },
new { name = "ClearRoom", cost = 4.0f, preconditions = new[] { "at_door", "has_weapon" }, effects = new[] { "room_cleared" } },
new { name = "CallBackup", cost = 0.5f, preconditions = new[] { "radio_available" }, effects = new[] { "backup_requested" } }
};
template["defaultSensors"] = new[] { "vision_cone", "hearing_sphere", "threat_evaluator", "health_monitor", "ammo_counter", "cover_detector" };
template["worldState"] = new Dictionary<string, object>
{
["health"] = 100, ["max_health"] = 100, ["ammo"] = 30, ["max_ammo"] = 30,
["grenades"] = 2, ["accuracy"] = difficulty == "hard" ? 0.85f : 0.65f,
["reaction_time"] = difficulty == "hard" ? 0.2f : 0.5f
};
break;
case "fps_sniper":
template["defaultGoals"] = new[]
{
new { name = "EliminateHighValueTarget", priority = 100, conditions = new[] { "hvt_spotted", "clear_shot" } },
new { name = "MaintainConcealment", priority = 95, conditions = new[] { "not_detected" } },
new { name = "Relocate", priority = 80, conditions = new[] { "position_compromised" } }
};
template["defaultActions"] = new[]
{
new { name = "AimAndShoot", cost = 2.0f, preconditions = new[] { "target_in_scope", "stable_aim" }, effects = new[] { "target_eliminated" } },
new { name = "HoldBreath", cost = 0.5f, preconditions = new[] { "aiming" }, effects = new[] { "stable_aim" } },
new { name = "Relocate", cost = 4.0f, preconditions = new[] { "alternate_position_known" }, effects = new[] { "new_position" } },
new { name = "SpotForTeam", cost = 1.0f, preconditions = new[] { "has_scope", "enemies_visible" }, effects = new[] { "enemies_marked" } }
};
template["defaultSensors"] = new[] { "scope_vision", "movement_detector", "wind_gauge", "distance_calculator" };
break;
// ===== RTS Game Templates =====
case "rts_unit":
case "rts_worker":
template["defaultGoals"] = new[]
{
new { name = "GatherResources", priority = 70, conditions = new[] { "resources_needed", "resource_nearby" } },
new { name = "ConstructBuilding", priority = 80, conditions = new[] { "construction_ordered", "has_materials" } },
new { name = "RepairStructure", priority = 75, conditions = new[] { "structure_damaged" } },
new { name = "ReturnToBase", priority = 60, conditions = new[] { "inventory_full" } },
new { name = "Flee", priority = 100, conditions = new[] { "under_attack", "no_weapon" } }
};
template["defaultActions"] = new[]
{
new { name = "MoveToResource", cost = 1.0f, preconditions = new[] { "resource_located" }, effects = new[] { "at_resource" } },
new { name = "Harvest", cost = 2.0f, preconditions = new[] { "at_resource", "inventory_not_full" }, effects = new[] { "resource_gathered" } },
new { name = "ReturnResources", cost = 1.5f, preconditions = new[] { "has_resources", "base_exists" }, effects = new[] { "resources_deposited" } },
new { name = "Build", cost = 5.0f, preconditions = new[] { "at_build_site", "has_materials" }, effects = new[] { "building_progress" } },
new { name = "Repair", cost = 3.0f, preconditions = new[] { "at_structure", "has_materials" }, effects = new[] { "structure_repaired" } }
};
template["defaultSensors"] = new[] { "resource_scanner", "enemy_radar", "construction_monitor", "inventory_checker" };
break;
case "rts_combat_unit":
template["defaultGoals"] = new[]
{
new { name = "AttackEnemy", priority = 90, conditions = new[] { "enemy_in_range" } },
new { name = "DefendPosition", priority = 85, conditions = new[] { "defense_order", "at_position" } },
new { name = "FormFormation", priority = 70, conditions = new[] { "squad_nearby" } },
new { name = "Retreat", priority = 95, conditions = new[] { "health_critical", "retreat_path_clear" } }
};
template["defaultActions"] = new[]
{
new { name = "Attack", cost = 1.0f, preconditions = new[] { "enemy_in_range", "weapon_ready" }, effects = new[] { "damage_dealt" } },
new { name = "MoveToEnemy", cost = 1.5f, preconditions = new[] { "enemy_detected" }, effects = new[] { "enemy_in_range" } },
new { name = "UseAbility", cost = 2.0f, preconditions = new[] { "ability_ready", "valid_target" }, effects = new[] { "ability_effect" } },
new { name = "FormUp", cost = 1.0f, preconditions = new[] { "allies_nearby" }, effects = new[] { "in_formation" } },
new { name = "Retreat", cost = 0.5f, preconditions = new[] { "retreat_ordered" }, effects = new[] { "retreating" } }
};
template["defaultSensors"] = new[] { "enemy_detector", "ally_tracker", "ability_cooldown", "formation_coordinator" };
break;
// ===== RPG Game Templates =====
case "rpg_npc":
case "rpg_merchant":
template["defaultGoals"] = new[]
{
new { name = "ServeCustomer", priority = 80, conditions = new[] { "customer_present" } },
new { name = "ManageInventory", priority = 60, conditions = new[] { "inventory_needs_restock" } },
new { name = "DailyRoutine", priority = 50, conditions = new[] { "no_customers" } },
new { name = "Rest", priority = 70, conditions = new[] { "nighttime", "shop_closed" } }
};
template["defaultActions"] = new[]
{
new { name = "GreetCustomer", cost = 0.5f, preconditions = new[] { "customer_nearby" }, effects = new[] { "customer_greeted" } },
new { name = "ShowWares", cost = 1.0f, preconditions = new[] { "customer_interested" }, effects = new[] { "wares_displayed" } },
new { name = "Negotiate", cost = 1.5f, preconditions = new[] { "in_transaction" }, effects = new[] { "price_agreed" } },
new { name = "CompleteSale", cost = 0.5f, preconditions = new[] { "price_agreed", "payment_received" }, effects = new[] { "sale_complete" } },
new { name = "RestockShelf", cost = 2.0f, preconditions = new[] { "shelf_empty", "stock_available" }, effects = new[] { "shelf_restocked" } }
};
template["defaultSensors"] = new[] { "customer_detector", "inventory_monitor", "time_tracker", "reputation_tracker" };
break;
case "rpg_quest_giver":
template["defaultGoals"] = new[]
{
new { name = "GiveQuest", priority = 90, conditions = new[] { "player_nearby", "has_quest_available" } },
new { name = "CheckQuestProgress", priority = 85, conditions = new[] { "active_quest_exists" } },
new { name = "GiveReward", priority = 95, conditions = new[] { "quest_completed" } },
new { name = "Idle", priority = 30, conditions = new[] { "no_interaction" } }
};
template["defaultActions"] = new[]
{
new { name = "InitiateDialogue", cost = 0.5f, preconditions = new[] { "player_approaching" }, effects = new[] { "in_dialogue" } },
new { name = "ExplainQuest", cost = 1.0f, preconditions = new[] { "in_dialogue", "quest_available" }, effects = new[] { "quest_explained" } },
new { name = "AssignQuest", cost = 0.5f, preconditions = new[] { "quest_accepted" }, effects = new[] { "quest_assigned" } },
new { name = "ProvideHint", cost = 0.5f, preconditions = new[] { "player_stuck", "hint_available" }, effects = new[] { "hint_given" } },
new { name = "GiveReward", cost = 1.0f, preconditions = new[] { "quest_complete", "has_reward" }, effects = new[] { "reward_given" } }
};
template["defaultSensors"] = new[] { "player_proximity", "quest_status_tracker", "dialogue_state", "reward_inventory" };
break;
case "rpg_enemy":
case "rpg_monster":
template["defaultGoals"] = new[]
{
new { name = "AttackIntruder", priority = 90, conditions = new[] { "intruder_detected", "in_territory" } },
new { name = "DefendTerritory", priority = 85, conditions = new[] { "territory_threatened" } },
new { name = "Hunt", priority = 70, conditions = new[] { "hungry", "prey_nearby" } },
new { name = "Rest", priority = 60, conditions = new[] { "stamina_low", "safe_location" } },
new { name = "Flee", priority = 100, conditions = new[] { "health_critical", "escape_route" } }
};
template["defaultActions"] = new[]
{
new { name = "BasicAttack", cost = 1.0f, preconditions = new[] { "target_in_melee" }, effects = new[] { "damage_dealt" } },
new { name = "SpecialAttack", cost = 3.0f, preconditions = new[] { "special_ready", "target_in_range" }, effects = new[] { "heavy_damage" } },
new { name = "ChargeAttack", cost = 2.0f, preconditions = new[] { "target_far", "charge_ready" }, effects = new[] { "at_target", "damage_dealt" } },
new { name = "Roar", cost = 1.5f, preconditions = new[] { "roar_ready" }, effects = new[] { "enemies_feared" } },
new { name = "Retreat", cost = 0.5f, preconditions = new[] { "health_low" }, effects = new[] { "distance_gained" } }
};
template["defaultSensors"] = new[] { "territory_sensor", "prey_detector", "threat_evaluator", "stamina_monitor" };
break;
// ===== Stealth Game Templates =====
case "stealth_guard":
template["defaultGoals"] = new[]
{
new { name = "Patrol", priority = 60, conditions = new[] { "on_duty", "no_alerts" } },
new { name = "Investigate", priority = 80, conditions = new[] { "suspicious_activity" } },
new { name = "RaiseAlarm", priority = 95, conditions = new[] { "intruder_confirmed" } },
new { name = "Apprehend", priority = 90, conditions = new[] { "intruder_spotted", "backup_available" } },
new { name = "SearchArea", priority = 85, conditions = new[] { "alarm_raised", "intruder_lost" } }
};
template["defaultActions"] = new[]
{
new { name = "PatrolRoute", cost = 1.0f, preconditions = new[] { "patrol_route_set" }, effects = new[] { "area_patrolled" } },
new { name = "InvestigateNoise", cost = 1.5f, preconditions = new[] { "noise_heard" }, effects = new[] { "noise_source_checked" } },
new { name = "CheckShadow", cost = 1.0f, preconditions = new[] { "movement_seen" }, effects = new[] { "area_checked" } },
new { name = "RaiseAlarm", cost = 0.5f, preconditions = new[] { "intruder_confirmed", "near_alarm" }, effects = new[] { "alarm_raised" } },
new { name = "CallRadio", cost = 0.5f, preconditions = new[] { "has_radio", "threat_detected" }, effects = new[] { "backup_alerted" } },
new { name = "PursueIntruder", cost = 2.0f, preconditions = new[] { "intruder_visible" }, effects = new[] { "pursuing" } },
new { name = "SearchPattern", cost = 3.0f, preconditions = new[] { "intruder_lost", "last_known_position" }, effects = new[] { "area_searched" } }
};
template["defaultSensors"] = new[] { "vision_cone", "hearing_radius", "patrol_waypoint", "alarm_status", "radio_comms" };
template["worldState"] = new Dictionary<string, object>
{
["suspicion_level"] = 0, ["alert_state"] = "normal",
["vision_range"] = difficulty == "hard" ? 20f : 12f,
["hearing_range"] = difficulty == "hard" ? 15f : 8f,
["investigation_thoroughness"] = difficulty == "hard" ? 0.9f : 0.6f
};
break;
// ===== Survival Game Templates =====
case "survival_creature":
template["defaultGoals"] = new[]
{
new { name = "Survive", priority = 100, conditions = new[] { "always" } },
new { name = "FindFood", priority = 90, conditions = new[] { "hungry" } },
new { name = "FindWater", priority = 85, conditions = new[] { "thirsty" } },
new { name = "FindShelter", priority = 80, conditions = new[] { "weather_dangerous" } },
new { name = "AvoidPredators", priority = 95, conditions = new[] { "predator_nearby" } },
new { name = "Rest", priority = 70, conditions = new[] { "exhausted", "safe" } }
};
template["defaultActions"] = new[]
{
new { name = "SearchForFood", cost = 2.0f, preconditions = new[] { "hungry" }, effects = new[] { "food_located" } },
new { name = "EatFood", cost = 1.0f, preconditions = new[] { "food_available" }, effects = new[] { "hunger_reduced" } },
new { name = "DrinkWater", cost = 1.0f, preconditions = new[] { "water_available" }, effects = new[] { "thirst_reduced" } },
new { name = "SeekShelter", cost = 2.0f, preconditions = new[] { "shelter_known" }, effects = new[] { "in_shelter" } },
new { name = "Hide", cost = 0.5f, preconditions = new[] { "hiding_spot_available" }, effects = new[] { "hidden" } },
new { name = "Flee", cost = 0.5f, preconditions = new[] { "escape_route" }, effects = new[] { "distance_from_threat" } },
new { name = "Sleep", cost = 3.0f, preconditions = new[] { "safe", "exhausted" }, effects = new[] { "stamina_restored" } }
};
template["defaultSensors"] = new[] { "hunger_monitor", "thirst_monitor", "stamina_monitor", "predator_detector", "weather_sensor", "shelter_finder" };
break;
// ===== Horror Game Templates =====
case "horror_enemy":
case "horror_monster":
template["defaultGoals"] = new[]
{
new { name = "HuntPlayer", priority = 100, conditions = new[] { "player_detected" } },
new { name = "Stalk", priority = 80, conditions = new[] { "player_nearby", "undetected" } },
new { name = "Ambush", priority = 90, conditions = new[] { "ambush_position_available" } },
new { name = "Patrol", priority = 50, conditions = new[] { "player_not_detected" } },
new { name = "ReactToSound", priority = 85, conditions = new[] { "sound_heard" } }
};
template["defaultActions"] = new[]
{
new { name = "Chase", cost = 1.0f, preconditions = new[] { "player_visible" }, effects = new[] { "closing_distance" } },
new { name = "Attack", cost = 1.5f, preconditions = new[] { "player_in_range" }, effects = new[] { "player_damaged" } },
new { name = "Stalk", cost = 0.5f, preconditions = new[] { "player_unaware" }, effects = new[] { "stalking" } },
new { name = "SetAmbush", cost = 2.0f, preconditions = new[] { "ambush_spot" }, effects = new[] { "ambush_ready" } },
new { name = "Teleport", cost = 3.0f, preconditions = new[] { "player_not_looking", "teleport_ready" }, effects = new[] { "new_position" } },
new { name = "MakeNoise", cost = 1.0f, preconditions = new[] { "player_calm" }, effects = new[] { "player_startled" } },
new { name = "DisableLights", cost = 2.0f, preconditions = new[] { "lights_on", "near_switch" }, effects = new[] { "area_dark" } }
};
template["defaultSensors"] = new[] { "player_tracker", "sound_locator", "light_detector", "fear_meter", "line_of_sight" };
template["worldState"] = new Dictionary<string, object>
{
["aggression"] = difficulty == "hard" ? 0.9f : 0.6f,
["stealth_preference"] = difficulty == "hard" ? 0.7f : 0.4f,
["teleport_cooldown"] = difficulty == "hard" ? 10f : 20f
};
break;
default:
// Generic template for unknown types
template["defaultGoals"] = new[] { "Survive", "CompleteObjective" };
template["defaultActions"] = new[] { "Move", "Interact", "Wait" };
template["defaultSensors"] = new[] { "basic_sensor" };
break;
}
return template;
}
/// <summary>
/// Create AI pathfinding system
/// </summary>
private string CreateAIPathfinding(Dictionary<string, string> parameters)
{
try
{
string systemName = parameters.GetValueOrDefault("systemName", "PathfindingAI");
string algorithm = parameters.GetValueOrDefault("algorithm", "AStar");
int gridWidth = Convert.ToInt32(parameters.GetValueOrDefault("gridWidth", "20"));
int gridHeight = Convert.ToInt32(parameters.GetValueOrDefault("gridHeight", "20"));
bool use3D = Convert.ToBoolean(parameters.GetValueOrDefault("use3D", "false"));
var pathfindingScript = GeneratePathfindingScript(systemName, algorithm, gridWidth, gridHeight, use3D);
// ScriptSave
var scriptPath = $"Assets/Scripts/AI/Pathfinding/{systemName}.cs";
var scriptDir = System.IO.Path.GetDirectoryName(scriptPath);
if (!System.IO.Directory.Exists(scriptDir))
{
System.IO.Directory.CreateDirectory(scriptDir);
}
System.IO.File.WriteAllText(scriptPath, pathfindingScript);
// Create test environment
var environment = new GameObject($"{systemName}Environment");
// Create grid
for (int x = 0; x < gridWidth; x++)
{
for (int z = 0; z < gridHeight; z++)
{
if (Random.value < 0.8f) // 80% probability passable
{
var cell = CreatePrimitiveWithMaterial(PrimitiveType.Cube);
cell.name = $"Cell_{x}_{z}";
cell.transform.parent = environment.transform;
cell.transform.position = new Vector3(x, 0, z);
cell.transform.localScale = new Vector3(0.9f, 0.1f, 0.9f);
cell.GetComponent<Renderer>().material.color = Color.white;
}
else // 20% probability for obstacles
{
var obstacle = CreatePrimitiveWithMaterial(PrimitiveType.Cube);
obstacle.name = $"Obstacle_{x}_{z}";
obstacle.transform.parent = environment.transform;
obstacle.transform.position = new Vector3(x, 0.5f, z);
obstacle.GetComponent<Renderer>().material.color = Color.red;
}
}
}
// Create AI agent
var agent = CreatePrimitiveWithMaterial(PrimitiveType.Capsule);
agent.name = "PathfindingAgent";
agent.transform.parent = environment.transform;
agent.transform.position = new Vector3(1, 1, 1);
agent.GetComponent<Renderer>().material.color = Color.blue;
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"AI Pathfinding '{systemName}' created\\nAlgorithm: {algorithm}\\nGrid: {gridWidth}x{gridHeight}\\n3D: {use3D}\\nScript: {scriptPath}"
}
}
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
content = new[]
{
new
{
type = "text",
text = $"Error creating AI pathfinding: {e.Message}"
}
}
});
}
}
private string GenerateMLAgentScript(string agentName, string agentType, int vectorObservationSize, bool useVisualObservation)
{
return $@"using UnityEngine;
public class {agentName} : MonoBehaviour
{{
[Header(""Environment"")]
public Transform goal;
public Transform platform;
[Header(""Agent Settings"")]
public float moveSpeed = 5f;
public float rotationSpeed = 10f;
private Rigidbody rb;
private Vector3 startPosition;
void Start()
{{
rb = GetComponent<Rigidbody>();
if (rb == null) rb = gameObject.AddComponent<Rigidbody>();
startPosition = transform.position;
// Auto-generate goal if not assigned
if (goal == null)
{{
var goalObj = GameObject.FindWithTag(""Goal"");
if (goalObj != null) goal = goalObj.transform;
}}
}}
void Update()
{{
// Simple AI behavior (move towards goal)
if (goal != null)
{{
Vector3 direction = (goal.position - transform.position).normalized;
rb.AddForce(direction * moveSpeed);
// GoalReachDetermine
if (Vector3.Distance(transform.position, goal.position) < 1.5f)
{{
SynLog.Info(""Goal reached!"");
ResetPosition();
}}
}}
// Reset if fallen off platform
if (transform.position.y < -1f)
{{
ResetPosition();
}}
}}
void ResetPosition()
{{
transform.position = startPosition;
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}}
}}";
}
private string GenerateNeuralNetworkScript(string networkName, string networkType, int inputSize, int hiddenSize, int outputSize)
{
return $@"using UnityEngine;
using System.Collections.Generic;
public class {networkName} : MonoBehaviour
{{
[Header(""Network Architecture"")]
public int inputSize = {inputSize};
public int hiddenSize = {hiddenSize};
public int outputSize = {outputSize};
public float learningRate = 0.01f;
private float[,] weightsInputHidden;
private float[,] weightsHiddenOutput;
private float[] hiddenBiases;
private float[] outputBiases;
void Start()
{{
InitializeNetwork();
}}
void InitializeNetwork()
{{
weightsInputHidden = new float[inputSize, hiddenSize];
weightsHiddenOutput = new float[hiddenSize, outputSize];
hiddenBiases = new float[hiddenSize];
outputBiases = new float[outputSize];
// XavierInitialize
for (int i = 0; i < inputSize; i++)
{{
for (int j = 0; j < hiddenSize; j++)
{{
weightsInputHidden[i, j] = Random.Range(-0.5f, 0.5f);
}}
}}
for (int i = 0; i < hiddenSize; i++)
{{
for (int j = 0; j < outputSize; j++)
{{
weightsHiddenOutput[i, j] = Random.Range(-0.5f, 0.5f);
}}
hiddenBiases[i] = Random.Range(-0.1f, 0.1f);
}}
for (int i = 0; i < outputSize; i++)
{{
outputBiases[i] = Random.Range(-0.1f, 0.1f);
}}
}}
public float[] Forward(float[] input)
{{
if (input.Length != inputSize) return new float[outputSize];
// Hidden layer calculation
float[] hiddenOutput = new float[hiddenSize];
for (int j = 0; j < hiddenSize; j++)
{{
float sum = hiddenBiases[j];
for (int i = 0; i < inputSize; i++)
{{
sum += input[i] * weightsInputHidden[i, j];
}}
hiddenOutput[j] = Sigmoid(sum);
}}
// Output layer calculation
float[] output = new float[outputSize];
for (int k = 0; k < outputSize; k++)
{{
float sum = outputBiases[k];
for (int j = 0; j < hiddenSize; j++)
{{
sum += hiddenOutput[j] * weightsHiddenOutput[j, k];
}}
output[k] = Sigmoid(sum);
}}
return output;
}}
private float Sigmoid(float x)
{{
return 1.0f / (1.0f + Mathf.Exp(-x));
}}
}}";
}
private string GenerateBehaviorTreeScript(string treeName, string aiType, bool includePatrol, bool includeChase, bool includeAttack)
{
return $@"using UnityEngine;
public class {treeName} : MonoBehaviour
{{
[Header(""{aiType} AI Settings"")]
public Transform target;
public float detectionRange = 10f;
public float attackRange = 2f;
public float moveSpeed = 3f;
public float patrolRadius = 5f;
private Vector3 originalPosition;
private Vector3 patrolTarget;
private bool hasPatrolTarget = false;
void Start()
{{
originalPosition = transform.position;
if (target == null)
{{
GameObject player = GameObject.FindWithTag(""Player"");
if (player != null) target = player.transform;
}}
}}
void Update()
{{
ExecuteBehaviorTree();
}}
void ExecuteBehaviorTree()
{{
{(includeChase ? @"
// Chase behavior
if (target != null && Vector3.Distance(transform.position, target.position) <= detectionRange)
{
MoveTowards(target.position);
return;
}" : "")}
{(includeAttack ? @"
// Attack behavior
if (target != null && Vector3.Distance(transform.position, target.position) <= attackRange)
{
Attack();
return;
}" : "")}
{(includePatrol ? @"
// Patrol behavior
if (!hasPatrolTarget || Vector3.Distance(transform.position, patrolTarget) < 1f)
{
Vector2 randomCircle = Random.insideUnitCircle * patrolRadius;
patrolTarget = originalPosition + new Vector3(randomCircle.x, 0, randomCircle.y);
hasPatrolTarget = true;
}
MoveTowards(patrolTarget);" : "")}
}}
void MoveTowards(Vector3 targetPosition)
{{
Vector3 direction = (targetPosition - transform.position).normalized;
transform.position += direction * moveSpeed * Time.deltaTime;
transform.LookAt(new Vector3(targetPosition.x, transform.position.y, targetPosition.z));
}}
void Attack()
{{
SynLog.Info($""{{name}} attacks!"");
}}
}}";
}
// === Debug and Test Tools Implementation ===
/// <summary>
/// Game speed control
/// </summary>
private string ControlGameSpeed(Dictionary<string, string> parameters)
{
try
{
var operation = parameters.GetValueOrDefault("operation", "set");
var speed = float.Parse(parameters.GetValueOrDefault("speed", "1"));
var preset = parameters.GetValueOrDefault("preset", "");
var pauseMode = parameters.GetValueOrDefault("pauseMode", "toggle");
switch (operation)
{
case "set":
if (!string.IsNullOrEmpty(preset))
{
speed = preset.ToLower() switch
{
"pause" => 0f,
"slowest" => 0.1f,
"slow" => 0.5f,
"normal" => 1f,
"fast" => 2f,
"fastest" => 5f,
_ => 1f
};
}
Time.timeScale = Mathf.Clamp(speed, 0f, 10f);
break;
case "pause":
switch (pauseMode)
{
case "toggle":
Time.timeScale = Time.timeScale > 0 ? 0 : 1;
break;
case "on":
Time.timeScale = 0;
break;
case "off":
Time.timeScale = 1;
break;
}
break;
case "step":
// Frame step (experimental)
if (Time.timeScale == 0)
{
Time.timeScale = 0.01f;
EditorApplication.delayCall += () => Time.timeScale = 0;
}
break;
case "get":
// Return current speed
break;
}
return JsonConvert.SerializeObject(new
{
success = true,
currentSpeed = Time.timeScale,
isPaused = Time.timeScale == 0,
fixedDeltaTime = Time.fixedDeltaTime,
maximumDeltaTime = Time.maximumDeltaTime,
presetDescription = Time.timeScale switch
{
0 => "Paused",
< 0.2f => "Slowest",
< 0.7f => "Slow",
< 1.5f => "Normal",
< 3f => "Fast",
_ => "Fastest"
}
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Performance profiling
/// </summary>
private string ProfilePerformance(Dictionary<string, string> parameters)
{
try
{
var category = parameters.GetValueOrDefault("category", "general");
var duration = int.Parse(parameters.GetValueOrDefault("duration", "0"));
var detailed = parameters.GetValueOrDefault("detailed", "false") == "true";
var profileData = new Dictionary<string, object>();
switch (category)
{
case "general":
case "all":
// FPSInfo
float fps = 1f / Time.deltaTime;
float avgFps = 1f / Time.smoothDeltaTime;
profileData["fps"] = new Dictionary<string, object>
{
["current"] = Mathf.Round(fps),
["average"] = Mathf.Round(avgFps),
["targetFrameRate"] = Application.targetFrameRate,
["vsyncCount"] = QualitySettings.vSyncCount
};
// MemoryInfo
profileData["memory"] = new Dictionary<string, object>
{
["totalAllocatedMemory"] = Profiler.GetTotalAllocatedMemoryLong() / (1024 * 1024) + " MB",
["totalReservedMemory"] = Profiler.GetTotalReservedMemoryLong() / (1024 * 1024) + " MB",
["totalUnusedReservedMemory"] = Profiler.GetTotalUnusedReservedMemoryLong() / (1024 * 1024) + " MB",
["monoHeapSize"] = Profiler.GetMonoHeapSizeLong() / (1024 * 1024) + " MB",
["monoUsedSize"] = Profiler.GetMonoUsedSizeLong() / (1024 * 1024) + " MB"
};
// Rendering statistics
profileData["rendering"] = new Dictionary<string, object>
{
["frameTime"] = Time.deltaTime * 1000 + " ms",
["smoothFrameTime"] = Time.smoothDeltaTime * 1000 + " ms",
["rendererCount"] = GameObject.FindObjectsOfType<Renderer>().Length,
["activeGameObjects"] = GameObject.FindObjectsOfType<GameObject>().Length
};
break;
case "memory":
profileData["memoryDetailed"] = new Dictionary<string, object>
{
["allocatedMemoryForGraphicsDriver"] = Profiler.GetAllocatedMemoryForGraphicsDriver() / (1024 * 1024) + " MB",
["tempAllocatorSize"] = Profiler.GetTempAllocatorSize() / (1024 * 1024) + " MB",
["totalAllocatedMemory"] = Profiler.GetTotalAllocatedMemoryLong() / (1024 * 1024) + " MB",
["systemMemorySize"] = SystemInfo.systemMemorySize + " MB",
["graphicsMemorySize"] = SystemInfo.graphicsMemorySize + " MB"
};
break;
case "gpu":
profileData["gpu"] = new Dictionary<string, object>
{
["graphicsDeviceName"] = SystemInfo.graphicsDeviceName,
["graphicsDeviceType"] = SystemInfo.graphicsDeviceType.ToString(),
["graphicsDeviceVendor"] = SystemInfo.graphicsDeviceVendor,
["graphicsMemorySize"] = SystemInfo.graphicsMemorySize + " MB",
["maxTextureSize"] = SystemInfo.maxTextureSize,
["npotSupport"] = SystemInfo.npotSupport.ToString(),
["graphicsShaderLevel"] = SystemInfo.graphicsShaderLevel
};
break;
case "cpu":
profileData["cpu"] = new Dictionary<string, object>
{
["processorType"] = SystemInfo.processorType,
["processorCount"] = SystemInfo.processorCount,
["processorFrequency"] = SystemInfo.processorFrequency + " MHz",
["operatingSystem"] = SystemInfo.operatingSystem,
["systemMemorySize"] = SystemInfo.systemMemorySize + " MB"
};
break;
}
// If in detailed mode
if (detailed)
{
profileData["components"] = new Dictionary<string, object>
{
["meshRenderers"] = GameObject.FindObjectsOfType<MeshRenderer>().Length,
["skinnedMeshRenderers"] = GameObject.FindObjectsOfType<SkinnedMeshRenderer>().Length,
["lights"] = GameObject.FindObjectsOfType<Light>().Length,
["cameras"] = GameObject.FindObjectsOfType<Camera>().Length,
["audioSources"] = GameObject.FindObjectsOfType<AudioSource>().Length,
["particleSystems"] = GameObject.FindObjectsOfType<ParticleSystem>().Length,
["colliders"] = GameObject.FindObjectsOfType<Collider>().Length,
["rigidbodies"] = GameObject.FindObjectsOfType<Rigidbody>().Length
};
}
return JsonConvert.SerializeObject(new
{
success = true,
timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
category = category,
data = profileData
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Debug drawing
/// </summary>
private string DebugDraw(Dictionary<string, string> parameters)
{
try
{
var drawType = parameters.GetValueOrDefault("type", "line");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "5"));
var colorStr = parameters.GetValueOrDefault("color", "red");
var persistent = parameters.GetValueOrDefault("persistent", "false") == "true";
Color color = colorStr.ToLower() switch
{
"red" => Color.red,
"green" => Color.green,
"blue" => Color.blue,
"yellow" => Color.yellow,
"white" => Color.white,
"black" => Color.black,
"cyan" => Color.cyan,
"magenta" => Color.magenta,
_ => Color.red
};
switch (drawType.ToLower())
{
case "line":
var start = ParseVector3(parameters.GetValueOrDefault("start", "0,0,0"));
var end = ParseVector3(parameters.GetValueOrDefault("end", "1,1,1"));
Debug.DrawLine(start, end, color, duration);
break;
case "ray":
var origin = ParseVector3(parameters.GetValueOrDefault("origin", "0,0,0"));
var direction = ParseVector3(parameters.GetValueOrDefault("direction", "0,1,0"));
var length = float.Parse(parameters.GetValueOrDefault("length", "10"));
Debug.DrawRay(origin, direction.normalized * length, color, duration);
break;
case "box":
var center = ParseVector3(parameters.GetValueOrDefault("center", "0,0,0"));
var size = ParseVector3(parameters.GetValueOrDefault("size", "1,1,1"));
DrawDebugBox(center, size, color, duration);
break;
case "sphere":
var sphereCenter = ParseVector3(parameters.GetValueOrDefault("center", "0,0,0"));
var radius = float.Parse(parameters.GetValueOrDefault("radius", "1"));
DrawDebugSphere(sphereCenter, radius, color, duration);
break;
case "path":
var pathPoints = parameters.GetValueOrDefault("points", "");
if (!string.IsNullOrEmpty(pathPoints))
{
var points = pathPoints.Split(';').Select(p => ParseVector3(p)).ToArray();
for (int i = 0; i < points.Length - 1; i++)
{
Debug.DrawLine(points[i], points[i + 1], color, duration);
}
}
break;
case "clear":
// Unity doesn't have a direct clear method, but we can suggest workarounds
return JsonConvert.SerializeObject(new
{
success = true,
message = "Debug draw lines will disappear after their duration. To clear immediately, restart play mode."
});
}
return JsonConvert.SerializeObject(new
{
success = true,
type = drawType,
color = colorStr,
duration = duration,
message = $"Debug {drawType} drawn with color {colorStr} for {duration} seconds"
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Run Unity tests using TestRunner API (calls separate assembly via reflection)
/// </summary>
private string RunUnityTests(Dictionary<string, string> parameters)
{
try
{
// Try to find the TestRunner service via reflection
var serviceType = Type.GetType("SynapticPro.TestRunner.NexusTestRunnerService, Synaptic.MCP.Unity.TestRunner");
if (serviceType == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Unity Test Framework not available. Please install com.unity.test-framework package and enable 'Enable playmode tests for all assemblies' in Test Runner settings."
});
}
var executeMethod = serviceType.GetMethod("Execute", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (executeMethod == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "TestRunner service method not found."
});
}
var operation = parameters.GetValueOrDefault("operation", "run");
var mode = parameters.GetValueOrDefault("mode", "editmode");
var filter = parameters.GetValueOrDefault("filter", "");
var result = executeMethod.Invoke(null, new object[] { operation, mode, filter });
return result as string ?? JsonConvert.SerializeObject(new { success = false, error = "No result from TestRunner" });
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Breakpoint management
/// </summary>
private string ManageBreakpoints(Dictionary<string, string> parameters)
{
try
{
var operation = parameters.GetValueOrDefault("operation", "pause");
var condition = parameters.GetValueOrDefault("condition", "");
var message = parameters.GetValueOrDefault("message", "Breakpoint hit");
switch (operation)
{
case "pause":
SynLog.Info($"[BREAKPOINT] {message}");
Debug.Break();
break;
case "conditional":
// Conditional breakpoint example
if (!string.IsNullOrEmpty(condition))
{
// Simple condition check (evaluation required in actual implementation)
var shouldBreak = false;
// Example: condition based on frame count
if (condition.Contains("frame"))
{
var frameNum = int.Parse(System.Text.RegularExpressions.Regex.Match(condition, @"\d+").Value);
shouldBreak = Time.frameCount >= frameNum;
}
// Example: condition based on time
else if (condition.Contains("time"))
{
var timeValue = float.Parse(System.Text.RegularExpressions.Regex.Match(condition, @"\d+\.?\d*").Value);
shouldBreak = Time.time >= timeValue;
}
if (shouldBreak)
{
SynLog.Info($"[CONDITIONAL BREAKPOINT] {message} (Condition: {condition})");
Debug.Break();
}
}
break;
case "log":
// LogPoint (log only without breaking)
SynLog.Info($"[LOGPOINT] {message}");
break;
case "assert":
// Assertion
Debug.Assert(!string.IsNullOrEmpty(condition), $"[ASSERTION FAILED] {message}");
break;
}
return JsonConvert.SerializeObject(new
{
success = true,
operation = operation,
isPaused = EditorApplication.isPaused,
frame = Time.frameCount,
time = Time.time,
message = message
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
// Debug drawing helper methods
private void DrawDebugBox(Vector3 center, Vector3 size, Color color, float duration)
{
var halfSize = size * 0.5f;
// Front
Debug.DrawLine(center + new Vector3(-halfSize.x, -halfSize.y, halfSize.z),
center + new Vector3(halfSize.x, -halfSize.y, halfSize.z), color, duration);
Debug.DrawLine(center + new Vector3(halfSize.x, -halfSize.y, halfSize.z),
center + new Vector3(halfSize.x, halfSize.y, halfSize.z), color, duration);
Debug.DrawLine(center + new Vector3(halfSize.x, halfSize.y, halfSize.z),
center + new Vector3(-halfSize.x, halfSize.y, halfSize.z), color, duration);
Debug.DrawLine(center + new Vector3(-halfSize.x, halfSize.y, halfSize.z),
center + new Vector3(-halfSize.x, -halfSize.y, halfSize.z), color, duration);
// Back
Debug.DrawLine(center + new Vector3(-halfSize.x, -halfSize.y, -halfSize.z),
center + new Vector3(halfSize.x, -halfSize.y, -halfSize.z), color, duration);
Debug.DrawLine(center + new Vector3(halfSize.x, -halfSize.y, -halfSize.z),
center + new Vector3(halfSize.x, halfSize.y, -halfSize.z), color, duration);
Debug.DrawLine(center + new Vector3(halfSize.x, halfSize.y, -halfSize.z),
center + new Vector3(-halfSize.x, halfSize.y, -halfSize.z), color, duration);
Debug.DrawLine(center + new Vector3(-halfSize.x, halfSize.y, -halfSize.z),
center + new Vector3(-halfSize.x, -halfSize.y, -halfSize.z), color, duration);
// Connecting lines
Debug.DrawLine(center + new Vector3(-halfSize.x, -halfSize.y, halfSize.z),
center + new Vector3(-halfSize.x, -halfSize.y, -halfSize.z), color, duration);
Debug.DrawLine(center + new Vector3(halfSize.x, -halfSize.y, halfSize.z),
center + new Vector3(halfSize.x, -halfSize.y, -halfSize.z), color, duration);
Debug.DrawLine(center + new Vector3(halfSize.x, halfSize.y, halfSize.z),
center + new Vector3(halfSize.x, halfSize.y, -halfSize.z), color, duration);
Debug.DrawLine(center + new Vector3(-halfSize.x, halfSize.y, halfSize.z),
center + new Vector3(-halfSize.x, halfSize.y, -halfSize.z), color, duration);
}
private void DrawDebugSphere(Vector3 center, float radius, Color color, float duration)
{
// Simple sphere drawing (represented by 3 circles)
int segments = 16;
// XYPlanar circle
for (int i = 0; i < segments; i++)
{
float angle1 = (float)i / segments * Mathf.PI * 2;
float angle2 = (float)(i + 1) / segments * Mathf.PI * 2;
Vector3 p1 = center + new Vector3(Mathf.Cos(angle1) * radius, Mathf.Sin(angle1) * radius, 0);
Vector3 p2 = center + new Vector3(Mathf.Cos(angle2) * radius, Mathf.Sin(angle2) * radius, 0);
Debug.DrawLine(p1, p2, color, duration);
}
// XZPlanar circle
for (int i = 0; i < segments; i++)
{
float angle1 = (float)i / segments * Mathf.PI * 2;
float angle2 = (float)(i + 1) / segments * Mathf.PI * 2;
Vector3 p1 = center + new Vector3(Mathf.Cos(angle1) * radius, 0, Mathf.Sin(angle1) * radius);
Vector3 p2 = center + new Vector3(Mathf.Cos(angle2) * radius, 0, Mathf.Sin(angle2) * radius);
Debug.DrawLine(p1, p2, color, duration);
}
// YZPlanar circle
for (int i = 0; i < segments; i++)
{
float angle1 = (float)i / segments * Mathf.PI * 2;
float angle2 = (float)(i + 1) / segments * Mathf.PI * 2;
Vector3 p1 = center + new Vector3(0, Mathf.Cos(angle1) * radius, Mathf.Sin(angle1) * radius);
Vector3 p2 = center + new Vector3(0, Mathf.Cos(angle2) * radius, Mathf.Sin(angle2) * radius);
Debug.DrawLine(p1, p2, color, duration);
}
}
// === Animation Tools Implementation ===
/// <summary>
/// AnimatorCreate controller
/// </summary>
private string CreateAnimatorController(Dictionary<string, string> parameters)
{
try
{
var controllerName = parameters.GetValueOrDefault("name", "NewAnimatorController");
var savePath = parameters.GetValueOrDefault("path", "Assets/Animations/Controllers/");
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var applyToObject = parameters.GetValueOrDefault("applyToObject", "true") == "true";
// Create directory
if (!System.IO.Directory.Exists(savePath))
{
System.IO.Directory.CreateDirectory(savePath);
}
// Create Animator controller
var controller = UnityEditor.Animations.AnimatorController.CreateAnimatorControllerAtPath(
savePath + controllerName + ".controller");
// Default layer and states
var rootStateMachine = controller.layers[0].stateMachine;
// Entry, Exit, Any State are created automatically
// Add default idle state
var idleState = rootStateMachine.AddState("Idle");
rootStateMachine.defaultState = idleState;
// Add basic parameters
controller.AddParameter("Speed", AnimatorControllerParameterType.Float);
controller.AddParameter("IsGrounded", AnimatorControllerParameterType.Bool);
controller.AddParameter("Jump", AnimatorControllerParameterType.Trigger);
// To target objectApply
if (applyToObject && !string.IsNullOrEmpty(targetObject))
{
var target = GameObject.Find(targetObject);
if (target != null)
{
var animator = target.GetComponent<Animator>();
if (animator == null)
{
animator = target.AddComponent<Animator>();
}
animator.runtimeAnimatorController = controller;
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
// Select created controller
Selection.activeObject = controller;
EditorGUIUtility.PingObject(controller);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Animator Controller '{controllerName}' created",
path = AssetDatabase.GetAssetPath(controller),
states = new[] { "Idle" },
parameters = new[] { "Speed", "IsGrounded", "Jump" },
appliedTo = applyToObject ? targetObject : "None"
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// AnimationAdd state
/// </summary>
private string AddAnimationState(Dictionary<string, string> parameters)
{
try
{
var controllerPath = parameters.GetValueOrDefault("controllerPath", "");
var stateName = parameters.GetValueOrDefault("stateName", "NewState");
var animationClipPath = parameters.GetValueOrDefault("animationClipPath", "");
var layerIndex = int.Parse(parameters.GetValueOrDefault("layerIndex", "0"));
var isDefault = parameters.GetValueOrDefault("isDefault", "false") == "true";
// Controller load
var controller = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(controllerPath);
if (controller == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Controller not found" });
}
// LayerGet
if (layerIndex >= controller.layers.Length)
{
return JsonConvert.SerializeObject(new { success = false, error = "Invalid layer index" });
}
var stateMachine = controller.layers[layerIndex].stateMachine;
// Register to UNDO
UnityEditor.Undo.RecordObject(controller, $"Add Animation State {stateName}");
// StateAdd
var newState = stateMachine.AddState(stateName);
// AnimationClipSettings
if (!string.IsNullOrEmpty(animationClipPath))
{
var clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(animationClipPath);
if (clip != null)
{
newState.motion = clip;
}
}
// Set as default state
if (isDefault)
{
stateMachine.defaultState = newState;
}
// Adjust state position (states are managed within state machine)
// UnityEditor.Animations.AnimatorState has no position property,
// Access via StateMachine
var states = stateMachine.states;
for (int i = 0; i < states.Length; i++)
{
if (states[i].state == newState)
{
states[i].position = new Vector3(250 * (states.Length % 3), 100 * (states.Length / 3), 0);
stateMachine.states = states;
break;
}
}
EditorUtility.SetDirty(controller);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"State '{stateName}' added to controller",
stateName = stateName,
hasMotion = newState.motion != null,
isDefault = stateMachine.defaultState == newState,
stateCount = stateMachine.states.Length
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// AnimationCreate clip
/// </summary>
private string CreateAnimationClip(Dictionary<string, string> parameters)
{
try
{
var clipName = parameters.GetValueOrDefault("name", "NewAnimation");
var savePath = parameters.GetValueOrDefault("path", "Assets/Animations/Clips/");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "1"));
var frameRate = float.Parse(parameters.GetValueOrDefault("frameRate", "30"));
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var animationType = parameters.GetValueOrDefault("animationType", "position");
// Create directory
if (!System.IO.Directory.Exists(savePath))
{
System.IO.Directory.CreateDirectory(savePath);
}
// AnimationClipCreate
var clip = new AnimationClip();
clip.name = clipName;
clip.frameRate = frameRate;
// Create sample animation curve
AnimationCurve curveX, curveY, curveZ;
switch (animationType.ToLower())
{
case "position":
// PositionAnimation
curveX = AnimationCurve.Linear(0, 0, duration, 2);
curveY = AnimationCurve.EaseInOut(0, 0, duration * 0.5f, 1);
curveY.AddKey(duration, 0);
curveZ = AnimationCurve.Constant(0, duration, 0);
clip.SetCurve("", typeof(Transform), "localPosition.x", curveX);
clip.SetCurve("", typeof(Transform), "localPosition.y", curveY);
clip.SetCurve("", typeof(Transform), "localPosition.z", curveZ);
break;
case "rotation":
// RotationAnimation
curveY = AnimationCurve.Linear(0, 0, duration, 360);
clip.SetCurve("", typeof(Transform), "localEulerAngles.y", curveY);
break;
case "scale":
// ScaleAnimation
var scaleCurve = AnimationCurve.EaseInOut(0, 1, duration * 0.5f, 1.5f);
scaleCurve.AddKey(duration, 1);
clip.SetCurve("", typeof(Transform), "localScale.x", scaleCurve);
clip.SetCurve("", typeof(Transform), "localScale.y", scaleCurve);
clip.SetCurve("", typeof(Transform), "localScale.z", scaleCurve);
break;
case "color":
// Color animation (for Renderer)
var colorR = AnimationCurve.Linear(0, 1, duration, 0);
var colorG = AnimationCurve.Linear(0, 1, duration, 0);
var colorB = AnimationCurve.Linear(0, 1, duration, 1);
clip.SetCurve("", typeof(MeshRenderer), "material._Color.r", colorR);
clip.SetCurve("", typeof(MeshRenderer), "material._Color.g", colorG);
clip.SetCurve("", typeof(MeshRenderer), "material._Color.b", colorB);
break;
}
// Clip settings
var settings = AnimationUtility.GetAnimationClipSettings(clip);
settings.loopTime = true;
AnimationUtility.SetAnimationClipSettings(clip, settings);
// Save
AssetDatabase.CreateAsset(clip, savePath + clipName + ".anim");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
// Apply to target
if (!string.IsNullOrEmpty(targetObject))
{
var target = GameObject.Find(targetObject);
if (target != null)
{
var animator = target.GetComponent<Animator>();
if (animator == null)
{
var animation = target.AddComponent<Animation>();
animation.clip = clip;
animation.Play();
}
}
}
Selection.activeObject = clip;
EditorGUIUtility.PingObject(clip);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Animation clip '{clipName}' created",
path = AssetDatabase.GetAssetPath(clip),
duration = duration,
frameRate = frameRate,
animationType = animationType,
isLooping = settings.loopTime
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Setup blend tree
/// </summary>
private string SetupBlendTree(Dictionary<string, string> parameters)
{
try
{
var controllerPath = parameters.GetValueOrDefault("controllerPath", "");
var stateName = parameters.GetValueOrDefault("stateName", "Movement");
var blendType = parameters.GetValueOrDefault("blendType", "1D");
var parameterName = parameters.GetValueOrDefault("parameterName", "Speed");
var layerIndex = int.Parse(parameters.GetValueOrDefault("layerIndex", "0"));
// Controller load
var controller = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(controllerPath);
if (controller == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Controller not found" });
}
var stateMachine = controller.layers[layerIndex].stateMachine;
// Create state for blend tree
var blendState = stateMachine.AddState(stateName);
var blendTree = new UnityEditor.Animations.BlendTree();
blendTree.name = stateName + "_BlendTree";
// Blend type settings
switch (blendType.ToUpper())
{
case "1D":
blendTree.blendType = UnityEditor.Animations.BlendTreeType.Simple1D;
blendTree.blendParameter = parameterName;
// Add sample motion (use existing animations in actual project)
// Idle (Speed = 0)
blendTree.AddChild(null, 0);
// Walk (Speed = 0.5)
blendTree.AddChild(null, 0.5f);
// Run (Speed = 1)
blendTree.AddChild(null, 1);
break;
case "2D":
blendTree.blendType = UnityEditor.Animations.BlendTreeType.SimpleDirectional2D;
blendTree.blendParameter = parameterName + "X";
blendTree.blendParameterY = parameterName + "Y";
// Add if parameter does not exist
if (!controller.parameters.Any(p => p.name == parameterName + "X"))
{
controller.AddParameter(parameterName + "X", AnimatorControllerParameterType.Float);
}
if (!controller.parameters.Any(p => p.name == parameterName + "Y"))
{
controller.AddParameter(parameterName + "Y", AnimatorControllerParameterType.Float);
}
// Directional motionsAdd
blendTree.AddChild(null, new Vector2(0, 0)); // Idle
blendTree.AddChild(null, new Vector2(0, 1)); // Forward
blendTree.AddChild(null, new Vector2(1, 0)); // Right
blendTree.AddChild(null, new Vector2(0, -1)); // Back
blendTree.AddChild(null, new Vector2(-1, 0)); // Left
break;
}
// Set blend tree to state
blendState.motion = blendTree;
// Save as asset
AssetDatabase.AddObjectToAsset(blendTree, AssetDatabase.GetAssetPath(controller));
EditorUtility.SetDirty(controller);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Blend tree '{stateName}' created",
blendType = blendType,
parameterName = parameterName,
childCount = blendTree.children.Length,
stateName = stateName
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// AnimationAdd transition
/// </summary>
private string AddAnimationTransition(Dictionary<string, string> parameters)
{
try
{
var controllerPath = parameters.GetValueOrDefault("controllerPath", "");
var fromState = parameters.GetValueOrDefault("fromState", "");
var toState = parameters.GetValueOrDefault("toState", "");
var condition = parameters.GetValueOrDefault("condition", "");
var conditionValue = parameters.GetValueOrDefault("conditionValue", "");
var hasExitTime = parameters.GetValueOrDefault("hasExitTime", "true") == "true";
var transitionDuration = float.Parse(parameters.GetValueOrDefault("transitionDuration", "0.25"));
var layerIndex = int.Parse(parameters.GetValueOrDefault("layerIndex", "0"));
// Controller load
var controller = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(controllerPath);
if (controller == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Controller not found" });
}
var stateMachine = controller.layers[layerIndex].stateMachine;
// State search
UnityEditor.Animations.AnimatorState sourceState = null;
UnityEditor.Animations.AnimatorState destState = null;
// Any StateTransition from
bool fromAnyState = fromState.ToLower() == "any" || fromState.ToLower() == "anystate";
if (!fromAnyState)
{
foreach (var state in stateMachine.states)
{
if (state.state.name == fromState)
sourceState = state.state;
if (state.state.name == toState)
destState = state.state;
}
if (sourceState == null)
{
return JsonConvert.SerializeObject(new { success = false, error = $"Source state '{fromState}' not found" });
}
}
else
{
// From Any State, only dest state is needed
foreach (var state in stateMachine.states)
{
if (state.state.name == toState)
destState = state.state;
}
}
if (destState == null)
{
return JsonConvert.SerializeObject(new { success = false, error = $"Destination state '{toState}' not found" });
}
// TransitionCreate
UnityEditor.Animations.AnimatorStateTransition transition;
if (fromAnyState)
{
transition = stateMachine.AddAnyStateTransition(destState);
}
else
{
transition = sourceState.AddTransition(destState);
}
// TransitionSettings
transition.hasExitTime = hasExitTime;
transition.duration = transitionDuration;
transition.exitTime = hasExitTime ? 0.9f : 0;
// ConditionSettings
if (!string.IsNullOrEmpty(condition))
{
// ParameterExistsDoorCheck
var param = controller.parameters.FirstOrDefault(p => p.name == condition);
if (param != null)
{
UnityEditor.Animations.AnimatorConditionMode mode;
switch (param.type)
{
case AnimatorControllerParameterType.Bool:
mode = conditionValue.ToLower() == "true" ?
UnityEditor.Animations.AnimatorConditionMode.If :
UnityEditor.Animations.AnimatorConditionMode.IfNot;
transition.AddCondition(mode, 0, condition);
break;
case AnimatorControllerParameterType.Float:
float floatValue = float.Parse(conditionValue);
mode = UnityEditor.Animations.AnimatorConditionMode.Greater;
transition.AddCondition(mode, floatValue, condition);
break;
case AnimatorControllerParameterType.Int:
int intValue = int.Parse(conditionValue);
mode = UnityEditor.Animations.AnimatorConditionMode.Equals;
transition.AddCondition(mode, intValue, condition);
break;
case AnimatorControllerParameterType.Trigger:
transition.AddCondition(UnityEditor.Animations.AnimatorConditionMode.If, 0, condition);
break;
}
}
}
EditorUtility.SetDirty(controller);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Transition created from '{fromState}' to '{toState}'",
hasExitTime = hasExitTime,
duration = transitionDuration,
hasCondition = !string.IsNullOrEmpty(condition),
condition = condition,
fromAnyState = fromAnyState
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// AnimationSetup layer
/// </summary>
private string SetupAnimationLayer(Dictionary<string, string> parameters)
{
try
{
var controllerPath = parameters.GetValueOrDefault("controllerPath", "");
var layerName = parameters.GetValueOrDefault("layerName", "NewLayer");
var weight = float.Parse(parameters.GetValueOrDefault("weight", "1"));
var blendMode = parameters.GetValueOrDefault("blendMode", "override");
var maskPath = parameters.GetValueOrDefault("avatarMaskPath", "");
// Controller load
var controller = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(controllerPath);
if (controller == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Controller not found" });
}
// Add new layer
controller.AddLayer(layerName);
var layerIndex = controller.layers.Length - 1;
var layers = controller.layers;
var newLayer = layers[layerIndex];
// Layer settings
newLayer.defaultWeight = weight;
// Blend mode settings
switch (blendMode.ToLower())
{
case "additive":
newLayer.blendingMode = UnityEditor.Animations.AnimatorLayerBlendingMode.Additive;
break;
default:
newLayer.blendingMode = UnityEditor.Animations.AnimatorLayerBlendingMode.Override;
break;
}
// Avatar mask settings
if (!string.IsNullOrEmpty(maskPath))
{
var mask = AssetDatabase.LoadAssetAtPath<AvatarMask>(maskPath);
if (mask != null)
{
newLayer.avatarMask = mask;
}
}
// Sync settings (as needed)
newLayer.syncedLayerIndex = -1;
controller.layers = layers;
// Add default state
var stateMachine = newLayer.stateMachine;
var defaultState = stateMachine.AddState("Default");
stateMachine.defaultState = defaultState;
EditorUtility.SetDirty(controller);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Layer '{layerName}' added to controller",
layerIndex = layerIndex,
weight = weight,
blendMode = blendMode,
hasMask = !string.IsNullOrEmpty(maskPath) && newLayer.avatarMask != null
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// AnimationCreate event
/// </summary>
private string CreateAnimationEvent(Dictionary<string, string> parameters)
{
try
{
var clipPath = parameters.GetValueOrDefault("clipPath", "");
var eventTime = float.Parse(parameters.GetValueOrDefault("time", "0.5"));
var functionName = parameters.GetValueOrDefault("functionName", "OnAnimationEvent");
var stringParameter = parameters.GetValueOrDefault("stringParameter", "");
var floatParameter = float.Parse(parameters.GetValueOrDefault("floatParameter", "0"));
var intParameter = int.Parse(parameters.GetValueOrDefault("intParameter", "0"));
// AnimationClip load
var clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(clipPath);
if (clip == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Animation clip not found" });
}
// AnimationEventCreate
var animEvent = new AnimationEvent();
animEvent.time = eventTime;
animEvent.functionName = functionName;
// Parameter settings (only one can be set)
if (!string.IsNullOrEmpty(stringParameter))
{
animEvent.stringParameter = stringParameter;
}
else if (floatParameter != 0)
{
animEvent.floatParameter = floatParameter;
}
else if (intParameter != 0)
{
animEvent.intParameter = intParameter;
}
// Get existing events and add new event
var events = AnimationUtility.GetAnimationEvents(clip);
var eventsList = new List<AnimationEvent>(events);
eventsList.Add(animEvent);
// Event settings
AnimationUtility.SetAnimationEvents(clip, eventsList.ToArray());
EditorUtility.SetDirty(clip);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Animation event '{functionName}' added at {eventTime}s",
eventCount = eventsList.Count,
time = eventTime,
functionName = functionName,
hasStringParam = !string.IsNullOrEmpty(stringParameter),
hasFloatParam = floatParameter != 0,
hasIntParam = intParameter != 0
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Setup avatar
/// </summary>
private string SetupAvatar(Dictionary<string, string> parameters)
{
try
{
var modelPath = parameters.GetValueOrDefault("modelPath", "");
var avatarName = parameters.GetValueOrDefault("avatarName", "NewAvatar");
var isHumanoid = parameters.GetValueOrDefault("isHumanoid", "true") == "true";
var rootBone = parameters.GetValueOrDefault("rootBone", "");
// Model load
var modelImporter = AssetImporter.GetAtPath(modelPath) as ModelImporter;
if (modelImporter == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Model not found or not a valid model file" });
}
// Animation type settings
if (isHumanoid)
{
modelImporter.animationType = ModelImporterAnimationType.Human;
modelImporter.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
// Humanoid settings
var humanDescription = modelImporter.humanDescription;
humanDescription.upperArmTwist = 0.5f;
humanDescription.lowerArmTwist = 0.5f;
humanDescription.upperLegTwist = 0.5f;
humanDescription.lowerLegTwist = 0.5f;
humanDescription.armStretch = 0.05f;
humanDescription.legStretch = 0.05f;
humanDescription.feetSpacing = 0.0f;
humanDescription.hasTranslationDoF = false;
modelImporter.humanDescription = humanDescription;
}
else
{
modelImporter.animationType = ModelImporterAnimationType.Generic;
// Root bone settings
if (!string.IsNullOrEmpty(rootBone))
{
// Temporarily instantiate model and search for bone
var tempObject = AssetDatabase.LoadAssetAtPath<GameObject>(modelPath);
if (tempObject != null)
{
var rootTransform = tempObject.transform.Find(rootBone);
if (rootTransform != null)
{
modelImporter.motionNodeName = rootBone;
}
}
}
}
// Apply import settings
modelImporter.SaveAndReimport();
// Get avatar
var avatar = AssetDatabase.LoadAssetAtPath<Avatar>(modelPath);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Avatar '{avatarName}' setup completed",
modelPath = modelPath,
isHumanoid = isHumanoid,
isValid = avatar != null && avatar.isValid,
hasHumanDescription = isHumanoid
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Create timeline
/// </summary>
private string CreateTimeline(Dictionary<string, string> parameters)
{
try
{
var timelineName = parameters.GetValueOrDefault("name", "NewTimeline");
var savePath = parameters.GetValueOrDefault("path", "Assets/Timelines/");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "10"));
var frameRate = float.Parse(parameters.GetValueOrDefault("frameRate", "30"));
var targetObject = parameters.GetValueOrDefault("targetObject", "");
// TimelineCheck if feature is enabled
var timelineAssetType = System.Type.GetType("UnityEngine.Timeline.TimelineAsset, Unity.Timeline");
if (timelineAssetType == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Timeline package is not installed. Please install it from Package Manager."
});
}
// Create directory
if (!System.IO.Directory.Exists(savePath))
{
System.IO.Directory.CreateDirectory(savePath);
}
// TimelineAssetCreate
var timeline = ScriptableObject.CreateInstance(timelineAssetType);
// Frame rate settings
var frameRateProperty = timelineAssetType.GetProperty("frameRate");
if (frameRateProperty != null)
{
frameRateProperty.SetValue(timeline, frameRate);
}
// Save as asset
AssetDatabase.CreateAsset(timeline, savePath + timelineName + ".playable");
// To target objectPlayableDirectorAdd
if (!string.IsNullOrEmpty(targetObject))
{
var target = GameObject.Find(targetObject);
if (target != null)
{
var playableDirectorType = System.Type.GetType("UnityEngine.Playables.PlayableDirector, UnityEngine.DirectorModule");
if (playableDirectorType != null)
{
var director = target.GetComponent(playableDirectorType);
if (director == null)
{
director = target.AddComponent(playableDirectorType);
}
// Set Timeline as PlayableAsset
var playableAssetProperty = playableDirectorType.GetProperty("playableAsset");
if (playableAssetProperty != null)
{
playableAssetProperty.SetValue(director, timeline);
}
}
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Selection.activeObject = timeline;
EditorGUIUtility.PingObject(timeline);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Timeline '{timelineName}' created",
path = AssetDatabase.GetAssetPath(timeline),
frameRate = frameRate,
appliedTo = !string.IsNullOrEmpty(targetObject) ? targetObject : "None"
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Bake animation
/// </summary>
private string BakeAnimation(Dictionary<string, string> parameters)
{
try
{
var sourceObject = parameters.GetValueOrDefault("sourceObject", "");
var animationName = parameters.GetValueOrDefault("animationName", "BakedAnimation");
var startFrame = int.Parse(parameters.GetValueOrDefault("startFrame", "0"));
var endFrame = int.Parse(parameters.GetValueOrDefault("endFrame", "60"));
var frameRate = float.Parse(parameters.GetValueOrDefault("frameRate", "30"));
var savePath = parameters.GetValueOrDefault("path", "Assets/Animations/Baked/");
// SourceObjectGet
var source = GameObject.Find(sourceObject);
if (source == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Source object not found" });
}
// Create directory
if (!System.IO.Directory.Exists(savePath))
{
System.IO.Directory.CreateDirectory(savePath);
}
// NewAnimationClipCreate
var bakedClip = new AnimationClip();
bakedClip.name = animationName;
bakedClip.frameRate = frameRate;
// GameObjectRecorder available after 2018.1
// Alternative: Simple keyframe record
var transform = source.transform;
// Record Transform animation
AnimationCurve posX = new AnimationCurve();
AnimationCurve posY = new AnimationCurve();
AnimationCurve posZ = new AnimationCurve();
AnimationCurve rotX = new AnimationCurve();
AnimationCurve rotY = new AnimationCurve();
AnimationCurve rotZ = new AnimationCurve();
AnimationCurve rotW = new AnimationCurve();
AnimationCurve scaleX = new AnimationCurve();
AnimationCurve scaleY = new AnimationCurve();
AnimationCurve scaleZ = new AnimationCurve();
// Record pose for each frame
float timeStep = 1f / frameRate;
for (int frame = startFrame; frame <= endFrame; frame++)
{
float time = frame * timeStep;
// Advance animation to specific frame
var animator = source.GetComponent<Animator>();
if (animator != null && animator.runtimeAnimatorController != null)
{
animator.Update(timeStep);
}
// Record current pose
posX.AddKey(time, transform.localPosition.x);
posY.AddKey(time, transform.localPosition.y);
posZ.AddKey(time, transform.localPosition.z);
var rotation = transform.localRotation;
rotX.AddKey(time, rotation.x);
rotY.AddKey(time, rotation.y);
rotZ.AddKey(time, rotation.z);
rotW.AddKey(time, rotation.w);
scaleX.AddKey(time, transform.localScale.x);
scaleY.AddKey(time, transform.localScale.y);
scaleZ.AddKey(time, transform.localScale.z);
}
// Set curves to clip
bakedClip.SetCurve("", typeof(Transform), "localPosition.x", posX);
bakedClip.SetCurve("", typeof(Transform), "localPosition.y", posY);
bakedClip.SetCurve("", typeof(Transform), "localPosition.z", posZ);
bakedClip.SetCurve("", typeof(Transform), "localRotation.x", rotX);
bakedClip.SetCurve("", typeof(Transform), "localRotation.y", rotY);
bakedClip.SetCurve("", typeof(Transform), "localRotation.z", rotZ);
bakedClip.SetCurve("", typeof(Transform), "localRotation.w", rotW);
bakedClip.SetCurve("", typeof(Transform), "localScale.x", scaleX);
bakedClip.SetCurve("", typeof(Transform), "localScale.y", scaleY);
bakedClip.SetCurve("", typeof(Transform), "localScale.z", scaleZ);
// Save as asset
AssetDatabase.CreateAsset(bakedClip, savePath + animationName + ".anim");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Selection.activeObject = bakedClip;
EditorGUIUtility.PingObject(bakedClip);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Animation '{animationName}' baked successfully",
path = AssetDatabase.GetAssetPath(bakedClip),
frameCount = endFrame - startFrame + 1,
duration = (endFrame - startFrame) / frameRate,
frameRate = frameRate
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
// === UI Detail Construction Tools Implementation ===
/// <summary>
/// Auto settings for UI anchor and pivot
/// </summary>
private string SetupUIAnchors(Dictionary<string, string> parameters)
{
try
{
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var anchorPreset = parameters.GetValueOrDefault("anchorPreset", "center");
var pivotPreset = parameters.GetValueOrDefault("pivotPreset", "center");
var margin = float.Parse(parameters.GetValueOrDefault("margin", "10"));
var recursive = parameters.GetValueOrDefault("recursive", "false") == "true";
var target = GameObject.Find(targetObject);
if (target == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Target object not found" });
}
var rectTransforms = recursive ?
target.GetComponentsInChildren<RectTransform>() :
new[] { target.GetComponent<RectTransform>() };
var processedCount = 0;
foreach (var rectTransform in rectTransforms)
{
if (rectTransform == null) continue;
// AnchorSettings
Vector2 anchorMin, anchorMax;
switch (anchorPreset.ToLower())
{
case "top-left":
anchorMin = anchorMax = new Vector2(0, 1);
break;
case "top-center":
anchorMin = anchorMax = new Vector2(0.5f, 1);
break;
case "top-right":
anchorMin = anchorMax = new Vector2(1, 1);
break;
case "middle-left":
anchorMin = anchorMax = new Vector2(0, 0.5f);
break;
case "center":
anchorMin = anchorMax = new Vector2(0.5f, 0.5f);
break;
case "middle-right":
anchorMin = anchorMax = new Vector2(1, 0.5f);
break;
case "bottom-left":
anchorMin = anchorMax = new Vector2(0, 0);
break;
case "bottom-center":
anchorMin = anchorMax = new Vector2(0.5f, 0);
break;
case "bottom-right":
anchorMin = anchorMax = new Vector2(1, 0);
break;
case "stretch-horizontal":
anchorMin = new Vector2(0, 0.5f);
anchorMax = new Vector2(1, 0.5f);
break;
case "stretch-vertical":
anchorMin = new Vector2(0.5f, 0);
anchorMax = new Vector2(0.5f, 1);
break;
case "stretch-all":
anchorMin = new Vector2(0, 0);
anchorMax = new Vector2(1, 1);
break;
default:
anchorMin = anchorMax = new Vector2(0.5f, 0.5f);
break;
}
rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
// Pivot settings
Vector2 pivot;
switch (pivotPreset.ToLower())
{
case "top-left":
pivot = new Vector2(0, 1);
break;
case "top-center":
pivot = new Vector2(0.5f, 1);
break;
case "top-right":
pivot = new Vector2(1, 1);
break;
case "middle-left":
pivot = new Vector2(0, 0.5f);
break;
case "center":
pivot = new Vector2(0.5f, 0.5f);
break;
case "middle-right":
pivot = new Vector2(1, 0.5f);
break;
case "bottom-left":
pivot = new Vector2(0, 0);
break;
case "bottom-center":
pivot = new Vector2(0.5f, 0);
break;
case "bottom-right":
pivot = new Vector2(1, 0);
break;
default:
pivot = new Vector2(0.5f, 0.5f);
break;
}
rectTransform.pivot = pivot;
// Apply margin (if stretch)
if (anchorPreset.Contains("stretch"))
{
if (anchorPreset == "stretch-horizontal" || anchorPreset == "stretch-all")
{
rectTransform.offsetMin = new Vector2(margin, rectTransform.offsetMin.y);
rectTransform.offsetMax = new Vector2(-margin, rectTransform.offsetMax.y);
}
if (anchorPreset == "stretch-vertical" || anchorPreset == "stretch-all")
{
rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, margin);
rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, -margin);
}
}
processedCount++;
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"UI anchors set for {processedCount} objects",
anchorPreset = anchorPreset,
pivotPreset = pivotPreset,
margin = margin,
processedCount = processedCount
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Create responsive UI
/// </summary>
private string CreateResponsiveUI(Dictionary<string, string> parameters)
{
try
{
var containerName = parameters.GetValueOrDefault("containerName", "ResponsiveContainer");
var layoutType = parameters.GetValueOrDefault("layoutType", "horizontal");
var spacing = float.Parse(parameters.GetValueOrDefault("spacing", "10"));
var padding = float.Parse(parameters.GetValueOrDefault("padding", "20"));
var childAlignment = parameters.GetValueOrDefault("childAlignment", "middle-center");
var useContentSizeFitter = parameters.GetValueOrDefault("useContentSizeFitter", "true") == "true";
// CanvasCheck
var canvas = GameObject.FindObjectOfType<Canvas>();
if (canvas == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No Canvas found in scene" });
}
// Create responsive container
var container = new GameObject(containerName);
container.transform.SetParent(canvas.transform, false);
var rectTransform = container.AddComponent<RectTransform>();
// Add layout group
LayoutGroup layoutGroup;
if (layoutType.ToLower() == "vertical")
{
var verticalLayout = container.AddComponent<VerticalLayoutGroup>();
verticalLayout.spacing = spacing;
verticalLayout.padding = new RectOffset((int)padding, (int)padding, (int)padding, (int)padding);
layoutGroup = verticalLayout;
}
else
{
var horizontalLayout = container.AddComponent<HorizontalLayoutGroup>();
horizontalLayout.spacing = spacing;
horizontalLayout.padding = new RectOffset((int)padding, (int)padding, (int)padding, (int)padding);
layoutGroup = horizontalLayout;
}
// Child element alignment settings
switch (childAlignment.ToLower())
{
case "upper-left":
layoutGroup.childAlignment = TextAnchor.UpperLeft;
break;
case "upper-center":
layoutGroup.childAlignment = TextAnchor.UpperCenter;
break;
case "upper-right":
layoutGroup.childAlignment = TextAnchor.UpperRight;
break;
case "middle-left":
layoutGroup.childAlignment = TextAnchor.MiddleLeft;
break;
case "middle-center":
layoutGroup.childAlignment = TextAnchor.MiddleCenter;
break;
case "middle-right":
layoutGroup.childAlignment = TextAnchor.MiddleRight;
break;
case "lower-left":
layoutGroup.childAlignment = TextAnchor.LowerLeft;
break;
case "lower-center":
layoutGroup.childAlignment = TextAnchor.LowerCenter;
break;
case "lower-right":
layoutGroup.childAlignment = TextAnchor.LowerRight;
break;
}
// Content Size FitterAdd
if (useContentSizeFitter)
{
var sizeFitter = container.AddComponent<ContentSizeFitter>();
sizeFitter.horizontalFit = layoutType.ToLower() == "horizontal" ?
ContentSizeFitter.FitMode.PreferredSize :
ContentSizeFitter.FitMode.Unconstrained;
sizeFitter.verticalFit = layoutType.ToLower() == "vertical" ?
ContentSizeFitter.FitMode.PreferredSize :
ContentSizeFitter.FitMode.Unconstrained;
}
// Default anchor settings (stretch)
rectTransform.anchorMin = new Vector2(0, 0);
rectTransform.anchorMax = new Vector2(1, 1);
rectTransform.offsetMin = Vector2.zero;
rectTransform.offsetMax = Vector2.zero;
// Create 3 sample child elements
for (int i = 0; i < 3; i++)
{
var childButton = CreateButton($"Button{i+1}", new Dictionary<string, string>
{
["text"] = $"Button {i+1}"
});
childButton.transform.SetParent(container.transform, false);
// Layout ElementAdd
var layoutElement = childButton.AddComponent<LayoutElement>();
layoutElement.minWidth = 100;
layoutElement.minHeight = 40;
layoutElement.preferredWidth = 150;
layoutElement.preferredHeight = 50;
}
Selection.activeGameObject = container;
EditorGUIUtility.PingObject(container);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Responsive UI container '{containerName}' created",
layoutType = layoutType,
childCount = 3,
hasContentSizeFitter = useContentSizeFitter,
spacing = spacing,
padding = padding
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// UIAnimationsettings
/// </summary>
private string SetupUIAnimation(Dictionary<string, string> parameters)
{
try
{
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var animationType = parameters.GetValueOrDefault("animationType", "fade");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "0.5"));
var delay = float.Parse(parameters.GetValueOrDefault("delay", "0"));
var easing = parameters.GetValueOrDefault("easing", "ease");
var autoPlay = parameters.GetValueOrDefault("autoPlay", "false") == "true";
var target = GameObject.Find(targetObject);
if (target == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Target object not found" });
}
var animator = target.GetComponent<Animator>();
if (animator == null)
{
animator = target.AddComponent<Animator>();
}
// UI Animation ControllerCreate
var controllerPath = "Assets/Animations/UI/UIAnimationController.controller";
var controllerDir = System.IO.Path.GetDirectoryName(controllerPath);
if (!System.IO.Directory.Exists(controllerDir))
{
System.IO.Directory.CreateDirectory(controllerDir);
}
UnityEditor.Animations.AnimatorController controller;
if (System.IO.File.Exists(controllerPath))
{
controller = AssetDatabase.LoadAssetAtPath<UnityEditor.Animations.AnimatorController>(controllerPath);
}
else
{
controller = UnityEditor.Animations.AnimatorController.CreateAnimatorControllerAtPath(controllerPath);
}
animator.runtimeAnimatorController = controller;
// AnimationClipCreate
var clipPath = $"Assets/Animations/UI/{targetObject}_{animationType}.anim";
var clip = new AnimationClip();
clip.name = $"{targetObject}_{animationType}";
var rectTransform = target.GetComponent<RectTransform>();
var canvasGroup = target.GetComponent<CanvasGroup>();
if (canvasGroup == null && (animationType == "fade" || animationType == "scale-fade"))
{
canvasGroup = target.AddComponent<CanvasGroup>();
}
// Curve settings for different animation types
switch (animationType.ToLower())
{
case "fade":
if (canvasGroup != null)
{
var alphaCurve = AnimationCurve.EaseInOut(0, 0, duration, 1);
clip.SetCurve("", typeof(CanvasGroup), "m_Alpha", alphaCurve);
}
break;
case "scale":
var scaleStartCurve = AnimationCurve.EaseInOut(0, 0, duration, 1);
clip.SetCurve("", typeof(RectTransform), "m_LocalScale.x", scaleStartCurve);
clip.SetCurve("", typeof(RectTransform), "m_LocalScale.y", scaleStartCurve);
break;
case "slide-left":
var slideXCurve = AnimationCurve.EaseInOut(0, -Screen.width, duration, 0);
clip.SetCurve("", typeof(RectTransform), "m_AnchoredPosition.x", slideXCurve);
break;
case "slide-up":
var slideYCurve = AnimationCurve.EaseInOut(0, -Screen.height, duration, 0);
clip.SetCurve("", typeof(RectTransform), "m_AnchoredPosition.y", slideYCurve);
break;
case "scale-fade":
var scaleCurve = AnimationCurve.EaseInOut(0, 0.5f, duration, 1);
var fadeCurve = AnimationCurve.EaseInOut(0, 0, duration, 1);
clip.SetCurve("", typeof(RectTransform), "m_LocalScale.x", scaleCurve);
clip.SetCurve("", typeof(RectTransform), "m_LocalScale.y", scaleCurve);
if (canvasGroup != null)
{
clip.SetCurve("", typeof(CanvasGroup), "m_Alpha", fadeCurve);
}
break;
}
// ClipSave
AssetDatabase.CreateAsset(clip, clipPath);
// Add to state machine
var rootStateMachine = controller.layers[0].stateMachine;
var animState = rootStateMachine.AddState(animationType);
animState.motion = clip;
// ParameterAdd
if (!controller.parameters.Any(p => p.name == "Play"))
{
controller.AddParameter("Play", AnimatorControllerParameterType.Trigger);
}
// TransitionAdd
var entryTransition = rootStateMachine.AddEntryTransition(animState);
entryTransition.AddCondition(UnityEditor.Animations.AnimatorConditionMode.If, 0, "Play");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
// Auto play
if (autoPlay)
{
animator.SetTrigger("Play");
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"UI animation '{animationType}' setup for {targetObject}",
animationType = animationType,
duration = duration,
hasCanvasGroup = canvasGroup != null,
clipPath = clipPath,
autoPlayed = autoPlay
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Create UI grid
/// </summary>
private string CreateUIGrid(Dictionary<string, string> parameters)
{
try
{
var gridName = parameters.GetValueOrDefault("gridName", "UIGrid");
var columns = int.Parse(parameters.GetValueOrDefault("columns", "3"));
var rows = int.Parse(parameters.GetValueOrDefault("rows", "3"));
var cellSize = ParseVector2(parameters.GetValueOrDefault("cellSize", "100,100"));
var spacing = ParseVector2(parameters.GetValueOrDefault("spacing", "10,10"));
var padding = parameters.GetValueOrDefault("padding", "10,10,10,10");
var fillType = parameters.GetValueOrDefault("fillType", "button");
// CanvasCheck
var canvas = GameObject.FindObjectOfType<Canvas>();
if (canvas == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No Canvas found in scene" });
}
// Create grid container
var gridContainer = new GameObject(gridName);
gridContainer.transform.SetParent(canvas.transform, false);
var rectTransform = gridContainer.AddComponent<RectTransform>();
// Grid Layout GroupAdd
var gridLayout = gridContainer.AddComponent<GridLayoutGroup>();
gridLayout.cellSize = cellSize;
gridLayout.spacing = spacing;
gridLayout.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
gridLayout.constraintCount = columns;
// Padding settings
var paddingValues = padding.Split(',');
if (paddingValues.Length >= 4)
{
gridLayout.padding = new RectOffset(
int.Parse(paddingValues[0]), // left
int.Parse(paddingValues[1]), // right
int.Parse(paddingValues[2]), // top
int.Parse(paddingValues[3]) // bottom
);
}
// Content Size FitterAdd
var sizeFitter = gridContainer.AddComponent<ContentSizeFitter>();
sizeFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
sizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// GridElementCreate
var createdElements = new List<GameObject>();
for (int i = 0; i < rows * columns; i++)
{
GameObject element;
switch (fillType.ToLower())
{
case "button":
element = CreateButton($"GridButton_{i}", new Dictionary<string, string>
{
["text"] = $"Btn {i + 1}"
});
break;
case "image":
element = CreateImage($"GridImage_{i}", new Dictionary<string, string>());
var image = element.GetComponent<Image>();
image.color = new Color(Random.Range(0.3f, 1f), Random.Range(0.3f, 1f), Random.Range(0.3f, 1f));
break;
case "text":
element = CreateText($"GridText_{i}", new Dictionary<string, string>
{
["text"] = $"Item {i + 1}"
});
break;
case "toggle":
element = CreateToggle($"GridToggle_{i}", new Dictionary<string, string>());
break;
default:
element = new GameObject($"GridItem_{i}");
element.AddComponent<RectTransform>();
var img = element.AddComponent<Image>();
img.color = Color.gray;
break;
}
element.transform.SetParent(gridContainer.transform, false);
createdElements.Add(element);
}
// Anchor settings (center placement)
rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
rectTransform.anchoredPosition = Vector2.zero;
Selection.activeGameObject = gridContainer;
EditorGUIUtility.PingObject(gridContainer);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"UI Grid '{gridName}' created with {createdElements.Count} elements",
columns = columns,
rows = rows,
elementCount = createdElements.Count,
cellSize = $"{cellSize.x}x{cellSize.y}",
fillType = fillType
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Setup scroll view
/// </summary>
private string SetupScrollView(Dictionary<string, string> parameters)
{
try
{
var scrollViewName = parameters.GetValueOrDefault("scrollViewName", "ScrollView");
var scrollDirection = parameters.GetValueOrDefault("scrollDirection", "vertical");
var contentType = parameters.GetValueOrDefault("contentType", "text");
var itemCount = int.Parse(parameters.GetValueOrDefault("itemCount", "10"));
var itemSize = ParseVector2(parameters.GetValueOrDefault("itemSize", "200,50"));
var useScrollbar = parameters.GetValueOrDefault("useScrollbar", "true") == "true";
var elasticity = float.Parse(parameters.GetValueOrDefault("elasticity", "0.1"));
// CanvasCheck
var canvas = GameObject.FindObjectOfType<Canvas>();
if (canvas == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No Canvas found in scene" });
}
// ScrollViewCreate
var scrollView = new GameObject(scrollViewName);
scrollView.transform.SetParent(canvas.transform, false);
var scrollRect = scrollView.AddComponent<ScrollRect>();
var scrollRectTransform = scrollView.GetComponent<RectTransform>();
// Anchor settings (occupies large part of screen)
scrollRectTransform.anchorMin = new Vector2(0.1f, 0.1f);
scrollRectTransform.anchorMax = new Vector2(0.9f, 0.9f);
scrollRectTransform.offsetMin = Vector2.zero;
scrollRectTransform.offsetMax = Vector2.zero;
// Add background image
var scrollImage = scrollView.AddComponent<Image>();
scrollImage.color = new Color(1f, 1f, 1f, 0.1f);
// ViewportCreate
var viewport = new GameObject("Viewport");
viewport.transform.SetParent(scrollView.transform, false);
var viewportRect = viewport.AddComponent<RectTransform>();
viewportRect.anchorMin = Vector2.zero;
viewportRect.anchorMax = Vector2.one;
viewportRect.offsetMin = Vector2.zero;
viewportRect.offsetMax = Vector2.zero;
var viewportMask = viewport.AddComponent<Mask>();
viewportMask.showMaskGraphic = false;
var viewportImage = viewport.AddComponent<Image>();
viewportImage.color = Color.clear;
// ContentCreate
var content = new GameObject("Content");
content.transform.SetParent(viewport.transform, false);
var contentRect = content.AddComponent<RectTransform>();
// Content settings according to scroll direction
if (scrollDirection.ToLower() == "vertical")
{
contentRect.anchorMin = new Vector2(0, 1);
contentRect.anchorMax = new Vector2(1, 1);
contentRect.pivot = new Vector2(0.5f, 1);
scrollRect.horizontal = false;
scrollRect.vertical = true;
// Vertical Layout GroupAdd
var verticalLayout = content.AddComponent<VerticalLayoutGroup>();
verticalLayout.childControlHeight = false;
verticalLayout.childControlWidth = true;
verticalLayout.childForceExpandHeight = false;
verticalLayout.childForceExpandWidth = true;
verticalLayout.spacing = 5;
}
else
{
contentRect.anchorMin = new Vector2(0, 0);
contentRect.anchorMax = new Vector2(0, 1);
contentRect.pivot = new Vector2(0, 0.5f);
scrollRect.horizontal = true;
scrollRect.vertical = false;
// Horizontal Layout GroupAdd
var horizontalLayout = content.AddComponent<HorizontalLayoutGroup>();
horizontalLayout.childControlHeight = true;
horizontalLayout.childControlWidth = false;
horizontalLayout.childForceExpandHeight = true;
horizontalLayout.childForceExpandWidth = false;
horizontalLayout.spacing = 5;
}
// Content Size FitterAdd
var contentSizeFitter = content.AddComponent<ContentSizeFitter>();
if (scrollDirection.ToLower() == "vertical")
{
contentSizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
else
{
contentSizeFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
}
// Create scrollbar
if (useScrollbar)
{
if (scrollDirection.ToLower() == "vertical")
{
var verticalScrollbar = CreateScrollbar("Scrollbar Vertical", true);
verticalScrollbar.transform.SetParent(scrollView.transform, false);
scrollRect.verticalScrollbar = verticalScrollbar.GetComponent<Scrollbar>();
}
else
{
var horizontalScrollbar = CreateScrollbar("Scrollbar Horizontal", false);
horizontalScrollbar.transform.SetParent(scrollView.transform, false);
scrollRect.horizontalScrollbar = horizontalScrollbar.GetComponent<Scrollbar>();
}
}
// ScrollRectSettings
scrollRect.content = contentRect;
scrollRect.viewport = viewportRect;
scrollRect.elasticity = elasticity;
scrollRect.movementType = ScrollRect.MovementType.Elastic;
// Create content items
for (int i = 0; i < itemCount; i++)
{
GameObject item;
switch (contentType.ToLower())
{
case "text":
item = CreateText($"Item_{i}", new Dictionary<string, string>
{
["text"] = $"List Item {i + 1}"
});
break;
case "button":
item = CreateButton($"Item_{i}", new Dictionary<string, string>
{
["text"] = $"Button {i + 1}"
});
break;
case "image":
item = CreateImage($"Item_{i}", new Dictionary<string, string>());
var img = item.GetComponent<Image>();
img.color = new Color(Random.Range(0.3f, 1f), Random.Range(0.3f, 1f), Random.Range(0.3f, 1f));
break;
default:
item = new GameObject($"Item_{i}");
item.AddComponent<RectTransform>();
var defaultImg = item.AddComponent<Image>();
defaultImg.color = Color.gray;
break;
}
item.transform.SetParent(content.transform, false);
// Layout ElementAdd
var layoutElement = item.AddComponent<LayoutElement>();
layoutElement.preferredWidth = itemSize.x;
layoutElement.preferredHeight = itemSize.y;
}
Selection.activeGameObject = scrollView;
EditorGUIUtility.PingObject(scrollView);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Scroll View '{scrollViewName}' created with {itemCount} items",
scrollDirection = scrollDirection,
itemCount = itemCount,
hasScrollbar = useScrollbar,
contentType = contentType
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private GameObject CreateScrollbar(string name, bool isVertical)
{
var scrollbar = new GameObject(name);
var scrollbarRect = scrollbar.AddComponent<RectTransform>();
if (isVertical)
{
scrollbarRect.anchorMin = new Vector2(1, 0);
scrollbarRect.anchorMax = new Vector2(1, 1);
scrollbarRect.offsetMin = new Vector2(-20, 0);
scrollbarRect.offsetMax = new Vector2(0, 0);
}
else
{
scrollbarRect.anchorMin = new Vector2(0, 0);
scrollbarRect.anchorMax = new Vector2(1, 0);
scrollbarRect.offsetMin = new Vector2(0, -20);
scrollbarRect.offsetMax = new Vector2(0, 0);
}
var scrollbarImage = scrollbar.AddComponent<Image>();
scrollbarImage.color = new Color(0.5f, 0.5f, 0.5f, 0.3f);
var scrollbarComponent = scrollbar.AddComponent<Scrollbar>();
scrollbarComponent.direction = isVertical ? Scrollbar.Direction.BottomToTop : Scrollbar.Direction.LeftToRight;
// HandleCreate
var handle = new GameObject("Handle");
handle.transform.SetParent(scrollbar.transform, false);
var handleRect = handle.AddComponent<RectTransform>();
handleRect.anchorMin = Vector2.zero;
handleRect.anchorMax = Vector2.one;
handleRect.offsetMin = Vector2.zero;
handleRect.offsetMax = Vector2.zero;
var handleImage = handle.AddComponent<Image>();
handleImage.color = new Color(0.8f, 0.8f, 0.8f, 0.8f);
scrollbarComponent.handleRect = handleRect;
scrollbarComponent.targetGraphic = handleImage;
return scrollbar;
}
/// <summary>
/// Create UI notification system
/// </summary>
private string CreateUINotification(Dictionary<string, string> parameters)
{
try
{
var notificationName = parameters.GetValueOrDefault("notificationName", "NotificationSystem");
var notificationType = parameters.GetValueOrDefault("notificationType", "toast");
var position = parameters.GetValueOrDefault("position", "top-right");
var animationType = parameters.GetValueOrDefault("animationType", "slide");
var autoHide = parameters.GetValueOrDefault("autoHide", "true") == "true";
var hideDelay = float.Parse(parameters.GetValueOrDefault("hideDelay", "3"));
// CanvasCheck
var canvas = GameObject.FindObjectOfType<Canvas>();
if (canvas == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No Canvas found in scene" });
}
// Create notification container
var notificationContainer = new GameObject(notificationName);
notificationContainer.transform.SetParent(canvas.transform, false);
var containerRect = notificationContainer.AddComponent<RectTransform>();
// Set position
switch (position.ToLower())
{
case "top-left":
containerRect.anchorMin = new Vector2(0, 1);
containerRect.anchorMax = new Vector2(0, 1);
containerRect.pivot = new Vector2(0, 1);
containerRect.anchoredPosition = new Vector2(20, -20);
break;
case "top-center":
containerRect.anchorMin = new Vector2(0.5f, 1);
containerRect.anchorMax = new Vector2(0.5f, 1);
containerRect.pivot = new Vector2(0.5f, 1);
containerRect.anchoredPosition = new Vector2(0, -20);
break;
case "top-right":
containerRect.anchorMin = new Vector2(1, 1);
containerRect.anchorMax = new Vector2(1, 1);
containerRect.pivot = new Vector2(1, 1);
containerRect.anchoredPosition = new Vector2(-20, -20);
break;
case "center":
containerRect.anchorMin = new Vector2(0.5f, 0.5f);
containerRect.anchorMax = new Vector2(0.5f, 0.5f);
containerRect.pivot = new Vector2(0.5f, 0.5f);
containerRect.anchoredPosition = Vector2.zero;
break;
case "bottom-center":
containerRect.anchorMin = new Vector2(0.5f, 0);
containerRect.anchorMax = new Vector2(0.5f, 0);
containerRect.pivot = new Vector2(0.5f, 0);
containerRect.anchoredPosition = new Vector2(0, 20);
break;
}
// Vertical Layout GroupAdd
var layoutGroup = notificationContainer.AddComponent<VerticalLayoutGroup>();
layoutGroup.childAlignment = TextAnchor.UpperCenter;
layoutGroup.spacing = 10;
layoutGroup.padding = new RectOffset(10, 10, 10, 10);
// Content Size FitterAdd
var sizeFitter = notificationContainer.AddComponent<ContentSizeFitter>();
sizeFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
sizeFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// Create sample notification
for (int i = 0; i < 2; i++)
{
var notification = CreateNotificationItem(
$"Notification {i + 1}",
$"This is sample notification message {i + 1}",
notificationType
);
notification.transform.SetParent(notificationContainer.transform, false);
// AnimationSettings
if (animationType != "none")
{
var canvasGroup = notification.AddComponent<CanvasGroup>();
var animator = notification.AddComponent<Animator>();
// Auto-hide settings
if (autoHide)
{
StartAutoHideCoroutine(notification, hideDelay);
}
}
}
Selection.activeGameObject = notificationContainer;
EditorGUIUtility.PingObject(notificationContainer);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Notification system '{notificationName}' created",
notificationType = notificationType,
position = position,
autoHide = autoHide,
hideDelay = hideDelay,
sampleCount = 2
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private GameObject CreateNotificationItem(string title, string message, string type)
{
var notification = new GameObject("Notification");
var rect = notification.AddComponent<RectTransform>();
rect.sizeDelta = new Vector2(300, 80);
// Background
var background = notification.AddComponent<Image>();
switch (type.ToLower())
{
case "success":
background.color = new Color(0.2f, 0.8f, 0.2f, 0.9f);
break;
case "warning":
background.color = new Color(1f, 0.8f, 0.2f, 0.9f);
break;
case "error":
background.color = new Color(0.8f, 0.2f, 0.2f, 0.9f);
break;
default:
background.color = new Color(0.2f, 0.2f, 0.2f, 0.9f);
break;
}
// Title text
var titleObj = CreateText("Title", new Dictionary<string, string>
{
["text"] = title,
["fontSize"] = "16"
});
titleObj.transform.SetParent(notification.transform, false);
var titleRect = titleObj.GetComponent<RectTransform>();
titleRect.anchorMin = new Vector2(0, 0.6f);
titleRect.anchorMax = new Vector2(1, 1);
titleRect.offsetMin = new Vector2(10, 0);
titleRect.offsetMax = new Vector2(-10, 0);
// MessageText
var messageObj = CreateText("Message", new Dictionary<string, string>
{
["text"] = message,
["fontSize"] = "12"
});
messageObj.transform.SetParent(notification.transform, false);
var messageRect = messageObj.GetComponent<RectTransform>();
messageRect.anchorMin = new Vector2(0, 0);
messageRect.anchorMax = new Vector2(1, 0.6f);
messageRect.offsetMin = new Vector2(10, 0);
messageRect.offsetMax = new Vector2(-10, 0);
return notification;
}
private void StartAutoHideCoroutine(GameObject notification, float delay)
{
// Coroutine alternative in editor (use Coroutine in actual game)
EditorApplication.delayCall += () =>
{
if (notification != null)
{
var canvasGroup = notification.GetComponent<CanvasGroup>();
if (canvasGroup != null)
{
// Fade out effect (simple version)
for (float t = 0; t < 1; t += 0.1f)
{
var alpha = 1 - t;
EditorApplication.delayCall += () =>
{
if (canvasGroup != null) canvasGroup.alpha = alpha;
};
}
// Delete
EditorApplication.delayCall += () =>
{
if (notification != null) GameObject.Destroy(notification);
};
}
}
};
}
/// <summary>
/// UI navigation settings
/// </summary>
private string SetupUINavigation(Dictionary<string, string> parameters)
{
try
{
var navigationName = parameters.GetValueOrDefault("navigationName", "UINavigation");
var navigationType = parameters.GetValueOrDefault("navigationType", "tab");
var itemCount = int.Parse(parameters.GetValueOrDefault("itemCount", "3"));
var orientation = parameters.GetValueOrDefault("orientation", "horizontal");
var selectedIndex = int.Parse(parameters.GetValueOrDefault("selectedIndex", "0"));
// CanvasCheck
var canvas = GameObject.FindObjectOfType<Canvas>();
if (canvas == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No Canvas found in scene" });
}
// Create navigation container
var navContainer = new GameObject(navigationName);
navContainer.transform.SetParent(canvas.transform, false);
var containerRect = navContainer.AddComponent<RectTransform>();
containerRect.anchorMin = new Vector2(0, 0.9f);
containerRect.anchorMax = new Vector2(1, 1);
containerRect.offsetMin = Vector2.zero;
containerRect.offsetMax = Vector2.zero;
// Add layout group
LayoutGroup layoutGroup;
if (orientation.ToLower() == "vertical")
{
layoutGroup = navContainer.AddComponent<VerticalLayoutGroup>();
}
else
{
layoutGroup = navContainer.AddComponent<HorizontalLayoutGroup>();
}
if (layoutGroup is HorizontalLayoutGroup horizontalGroup)
{
horizontalGroup.childControlWidth = true;
horizontalGroup.childControlHeight = true;
horizontalGroup.childForceExpandWidth = true;
horizontalGroup.childForceExpandHeight = true;
horizontalGroup.spacing = 5;
}
else if (layoutGroup is VerticalLayoutGroup verticalGroup)
{
verticalGroup.childControlWidth = true;
verticalGroup.childControlHeight = true;
verticalGroup.childForceExpandWidth = true;
verticalGroup.childForceExpandHeight = true;
verticalGroup.spacing = 5;
}
// Add toggle group (radio button-style operation)
var toggleGroup = navContainer.AddComponent<ToggleGroup>();
toggleGroup.allowSwitchOff = false;
// Create navigation item
var createdItems = new List<GameObject>();
for (int i = 0; i < itemCount; i++)
{
GameObject navItem;
switch (navigationType.ToLower())
{
case "tab":
navItem = CreateTabItem($"Tab {i + 1}", i == selectedIndex);
break;
case "button":
navItem = CreateButton($"NavButton_{i}", new Dictionary<string, string>
{
["text"] = $"Nav {i + 1}"
});
break;
case "toggle":
navItem = CreateToggle($"NavToggle_{i}", new Dictionary<string, string>());
var toggle = navItem.GetComponent<Toggle>();
toggle.group = toggleGroup;
toggle.isOn = i == selectedIndex;
break;
default:
navItem = CreateButton($"NavItem_{i}", new Dictionary<string, string>
{
["text"] = $"Item {i + 1}"
});
break;
}
navItem.transform.SetParent(navContainer.transform, false);
createdItems.Add(navItem);
}
Selection.activeGameObject = navContainer;
EditorGUIUtility.PingObject(navContainer);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"UI Navigation '{navigationName}' created",
navigationType = navigationType,
itemCount = itemCount,
orientation = orientation,
selectedIndex = selectedIndex
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private GameObject CreateTabItem(string title, bool isSelected)
{
var tab = CreateButton($"Tab_{title}", new Dictionary<string, string>
{
["text"] = title
});
// Selected state styling
var button = tab.GetComponent<Button>();
var image = tab.GetComponent<Image>();
if (isSelected)
{
image.color = new Color(0.8f, 0.8f, 0.8f);
}
else
{
image.color = new Color(0.6f, 0.6f, 0.6f);
}
return tab;
}
/// <summary>
/// Create UI dialog
/// </summary>
private string CreateUIDialog(Dictionary<string, string> parameters)
{
try
{
var dialogName = parameters.GetValueOrDefault("dialogName", "Dialog");
var dialogType = parameters.GetValueOrDefault("dialogType", "confirmation");
var title = parameters.GetValueOrDefault("title", "Dialog Title");
var message = parameters.GetValueOrDefault("message", "Dialog message content");
var hasOverlay = parameters.GetValueOrDefault("hasOverlay", "true") == "true";
var isModal = parameters.GetValueOrDefault("isModal", "true") == "true";
// CanvasCheck
var canvas = GameObject.FindObjectOfType<Canvas>();
if (canvas == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No Canvas found in scene" });
}
// Create overlay
GameObject overlay = null;
if (hasOverlay)
{
overlay = new GameObject("DialogOverlay");
overlay.transform.SetParent(canvas.transform, false);
var overlayRect = overlay.AddComponent<RectTransform>();
overlayRect.anchorMin = Vector2.zero;
overlayRect.anchorMax = Vector2.one;
overlayRect.offsetMin = Vector2.zero;
overlayRect.offsetMax = Vector2.zero;
var overlayImage = overlay.AddComponent<Image>();
overlayImage.color = new Color(0, 0, 0, 0.5f);
if (isModal)
{
var overlayButton = overlay.AddComponent<Button>();
overlayButton.onClick.AddListener(() => {
if (overlay != null) GameObject.Destroy(overlay.transform.parent.gameObject);
});
}
}
// Create dialog
var dialog = new GameObject(dialogName);
dialog.transform.SetParent(overlay != null ? overlay.transform : canvas.transform, false);
var dialogRect = dialog.AddComponent<RectTransform>();
dialogRect.anchorMin = new Vector2(0.5f, 0.5f);
dialogRect.anchorMax = new Vector2(0.5f, 0.5f);
dialogRect.pivot = new Vector2(0.5f, 0.5f);
dialogRect.sizeDelta = new Vector2(400, 300);
dialogRect.anchoredPosition = Vector2.zero;
// Background
var dialogImage = dialog.AddComponent<Image>();
dialogImage.color = Color.white;
// Title area
var titleArea = new GameObject("TitleArea");
titleArea.transform.SetParent(dialog.transform, false);
var titleAreaRect = titleArea.AddComponent<RectTransform>();
titleAreaRect.anchorMin = new Vector2(0, 0.8f);
titleAreaRect.anchorMax = new Vector2(1, 1);
titleAreaRect.offsetMin = Vector2.zero;
titleAreaRect.offsetMax = Vector2.zero;
var titleBg = titleArea.AddComponent<Image>();
titleBg.color = new Color(0.9f, 0.9f, 0.9f);
var titleText = CreateText("Title", new Dictionary<string, string>
{
["text"] = title,
["fontSize"] = "18"
});
titleText.transform.SetParent(titleArea.transform, false);
var titleTextRect = titleText.GetComponent<RectTransform>();
titleTextRect.anchorMin = Vector2.zero;
titleTextRect.anchorMax = Vector2.one;
titleTextRect.offsetMin = new Vector2(20, 0);
titleTextRect.offsetMax = new Vector2(-20, 0);
// Message area
var messageArea = new GameObject("MessageArea");
messageArea.transform.SetParent(dialog.transform, false);
var messageAreaRect = messageArea.AddComponent<RectTransform>();
messageAreaRect.anchorMin = new Vector2(0, 0.3f);
messageAreaRect.anchorMax = new Vector2(1, 0.8f);
messageAreaRect.offsetMin = Vector2.zero;
messageAreaRect.offsetMax = Vector2.zero;
var messageText = CreateText("Message", new Dictionary<string, string>
{
["text"] = message,
["fontSize"] = "14"
});
messageText.transform.SetParent(messageArea.transform, false);
var messageTextRect = messageText.GetComponent<RectTransform>();
messageTextRect.anchorMin = Vector2.zero;
messageTextRect.anchorMax = Vector2.one;
messageTextRect.offsetMin = new Vector2(20, 10);
messageTextRect.offsetMax = new Vector2(-20, -10);
// Button area
var buttonArea = new GameObject("ButtonArea");
buttonArea.transform.SetParent(dialog.transform, false);
var buttonAreaRect = buttonArea.AddComponent<RectTransform>();
buttonAreaRect.anchorMin = new Vector2(0, 0);
buttonAreaRect.anchorMax = new Vector2(1, 0.3f);
buttonAreaRect.offsetMin = Vector2.zero;
buttonAreaRect.offsetMax = Vector2.zero;
var buttonLayout = buttonArea.AddComponent<HorizontalLayoutGroup>();
buttonLayout.spacing = 10;
buttonLayout.padding = new RectOffset(20, 20, 20, 20);
buttonLayout.childControlWidth = true;
buttonLayout.childControlHeight = true;
buttonLayout.childForceExpandWidth = true;
buttonLayout.childForceExpandHeight = true;
// Create buttons according to dialog type
switch (dialogType.ToLower())
{
case "confirmation":
var okButton = CreateButton("OKButton", new Dictionary<string, string> { ["text"] = "OK" });
var cancelButton = CreateButton("CancelButton", new Dictionary<string, string> { ["text"] = "Cancel" });
okButton.transform.SetParent(buttonArea.transform, false);
cancelButton.transform.SetParent(buttonArea.transform, false);
break;
case "alert":
var alertButton = CreateButton("AlertButton", new Dictionary<string, string> { ["text"] = "OK" });
alertButton.transform.SetParent(buttonArea.transform, false);
break;
case "input":
var inputField = CreateInputField("DialogInput", new Dictionary<string, string>
{
["placeholder"] = "Enter text..."
});
inputField.transform.SetParent(messageArea.transform, false);
var submitButton = CreateButton("SubmitButton", new Dictionary<string, string> { ["text"] = "Submit" });
var inputCancelButton = CreateButton("CancelButton", new Dictionary<string, string> { ["text"] = "Cancel" });
submitButton.transform.SetParent(buttonArea.transform, false);
inputCancelButton.transform.SetParent(buttonArea.transform, false);
break;
}
Selection.activeGameObject = dialog;
EditorGUIUtility.PingObject(dialog);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Dialog '{dialogName}' created",
dialogType = dialogType,
hasOverlay = hasOverlay,
isModal = isModal,
title = title
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Optimize UI canvas
/// </summary>
private string OptimizeUICanvas(Dictionary<string, string> parameters)
{
try
{
var canvasName = parameters.GetValueOrDefault("canvasName", "");
var optimizationType = parameters.GetValueOrDefault("optimizationType", "performance");
var targetFrameRate = int.Parse(parameters.GetValueOrDefault("targetFrameRate", "60"));
var enablePixelPerfect = parameters.GetValueOrDefault("enablePixelPerfect", "false") == "true";
Canvas targetCanvas;
if (string.IsNullOrEmpty(canvasName))
{
targetCanvas = GameObject.FindObjectOfType<Canvas>();
}
else
{
var canvasObj = GameObject.Find(canvasName);
targetCanvas = canvasObj?.GetComponent<Canvas>();
}
if (targetCanvas == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Canvas not found" });
}
var optimizations = new List<string>();
switch (optimizationType.ToLower())
{
case "performance":
// Performance optimization
var canvasScaler = targetCanvas.GetComponent<CanvasScaler>();
if (canvasScaler != null)
{
canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
canvasScaler.referenceResolution = new Vector2(1920, 1080);
canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
canvasScaler.matchWidthOrHeight = 0.5f;
optimizations.Add("Canvas Scaler optimized");
}
// Canvas GroupOptimize
var canvasGroups = targetCanvas.GetComponentsInChildren<CanvasGroup>();
foreach (var group in canvasGroups)
{
if (group.alpha == 0)
{
group.blocksRaycasts = false;
group.interactable = false;
}
}
optimizations.Add($"Optimized {canvasGroups.Length} Canvas Groups");
// Remove unnecessary Graphic Raycasters
var graphicRaycasters = targetCanvas.GetComponentsInChildren<GraphicRaycaster>();
for (int i = 1; i < graphicRaycasters.Length; i++) // Keep the first one
{
GameObject.Destroy(graphicRaycasters[i]);
}
if (graphicRaycasters.Length > 1)
{
optimizations.Add($"Removed {graphicRaycasters.Length - 1} excess Graphic Raycasters");
}
break;
case "quality":
// Quality optimization
if (enablePixelPerfect)
{
targetCanvas.pixelPerfect = true;
optimizations.Add("Pixel Perfect enabled");
}
// High quality text settings
var texts = targetCanvas.GetComponentsInChildren<Text>();
foreach (var text in texts)
{
text.supportRichText = false; // For performance
if (text.fontSize < 24)
{
text.material = Resources.GetBuiltinResource<Material>("UI/Default Font Material");
}
}
optimizations.Add($"Optimized {texts.Length} Text components");
break;
case "mobile":
// Mobile optimization
targetCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
var mobileCanvasScaler = targetCanvas.GetComponent<CanvasScaler>();
if (mobileCanvasScaler != null)
{
mobileCanvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
mobileCanvasScaler.referenceResolution = new Vector2(1080, 1920); // Portrait orientation
mobileCanvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
mobileCanvasScaler.matchWidthOrHeight = 1f; // Height-based
}
// Adjust touch-enabled UI element sizes
var buttons = targetCanvas.GetComponentsInChildren<Button>();
foreach (var button in buttons)
{
var buttonRect = button.GetComponent<RectTransform>();
if (buttonRect.sizeDelta.x < 44 || buttonRect.sizeDelta.y < 44)
{
buttonRect.sizeDelta = new Vector2(
Mathf.Max(buttonRect.sizeDelta.x, 44),
Mathf.Max(buttonRect.sizeDelta.y, 44)
);
}
}
optimizations.Add($"Mobile-optimized {buttons.Length} buttons");
break;
}
// FPS limit settings
if (targetFrameRate > 0)
{
Application.targetFrameRate = targetFrameRate;
optimizations.Add($"Target frame rate set to {targetFrameRate}");
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Canvas '{targetCanvas.name}' optimized",
optimizationType = optimizationType,
optimizations = optimizations,
canvasName = targetCanvas.name
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
/// <summary>
/// Safe Areasettings
/// </summary>
private string SetupSafeArea(Dictionary<string, string> parameters)
{
try
{
var safeAreaName = parameters.GetValueOrDefault("safeAreaName", "SafeAreaContainer");
var targetObject = parameters.GetValueOrDefault("targetObject", "");
var applyToCanvas = parameters.GetValueOrDefault("applyToCanvas", "false") == "true";
var includeNotch = parameters.GetValueOrDefault("includeNotch", "true") == "true";
Canvas canvas = null;
RectTransform targetRect = null;
if (applyToCanvas)
{
canvas = GameObject.FindObjectOfType<Canvas>();
if (canvas == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No Canvas found in scene" });
}
targetRect = canvas.GetComponent<RectTransform>();
}
else if (!string.IsNullOrEmpty(targetObject))
{
var target = GameObject.Find(targetObject);
if (target == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Target object not found" });
}
targetRect = target.GetComponent<RectTransform>();
}
else
{
// Create new Safe Area container
canvas = GameObject.FindObjectOfType<Canvas>();
if (canvas == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No Canvas found in scene" });
}
var safeAreaContainer = new GameObject(safeAreaName);
safeAreaContainer.transform.SetParent(canvas.transform, false);
targetRect = safeAreaContainer.AddComponent<RectTransform>();
}
if (targetRect == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "No RectTransform found" });
}
// Create Safe Area script (editor-only simple version)
var safeAreaScript = @"
using UnityEngine;
using SynapticAIPro;
public class SafeAreaController : MonoBehaviour
{
private RectTransform rectTransform;
private Rect lastSafeArea = new Rect(0, 0, 0, 0);
void Start()
{
rectTransform = GetComponent<RectTransform>();
ApplySafeArea();
}
void Update()
{
if (lastSafeArea != Screen.safeArea)
{
ApplySafeArea();
}
}
void ApplySafeArea()
{
Rect safeArea = Screen.safeArea;
lastSafeArea = safeArea;
Vector2 anchorMin = safeArea.position;
Vector2 anchorMax = safeArea.position + safeArea.size;
anchorMin.x /= Screen.width;
anchorMin.y /= Screen.height;
anchorMax.x /= Screen.width;
anchorMax.y /= Screen.height;
rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
}
}";
// ScriptFileSave
var scriptPath = "Assets/Scripts/UI/SafeAreaController.cs";
var scriptDir = System.IO.Path.GetDirectoryName(scriptPath);
if (!System.IO.Directory.Exists(scriptDir))
{
System.IO.Directory.CreateDirectory(scriptDir);
}
System.IO.File.WriteAllText(scriptPath, safeAreaScript);
AssetDatabase.Refresh();
// Provisional Safe Area settings in editor (iPhone X style)
if (includeNotch)
{
// Settings considering notch
targetRect.anchorMin = new Vector2(0, 0.05f); // Bottom 5%
targetRect.anchorMax = new Vector2(1, 0.95f); // Top 5%
}
else
{
// BasicSafe Area
targetRect.anchorMin = new Vector2(0.02f, 0.02f); // 2% each side
targetRect.anchorMax = new Vector2(0.98f, 0.98f);
}
targetRect.offsetMin = Vector2.zero;
targetRect.offsetMax = Vector2.zero;
Selection.activeGameObject = targetRect.gameObject;
EditorGUIUtility.PingObject(targetRect.gameObject);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Safe Area setup completed for '{targetRect.gameObject.name}'",
includeNotch = includeNotch,
scriptPath = scriptPath,
anchorMin = $"{targetRect.anchorMin.x:F3}, {targetRect.anchorMin.y:F3}",
anchorMax = $"{targetRect.anchorMax.x:F3}, {targetRect.anchorMax.y:F3}"
}, Formatting.Indented);
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new { success = false, error = e.Message });
}
}
private string GeneratePathfindingScript(string systemName, string algorithm, int gridWidth, int gridHeight, bool use3D)
{
return $@"using UnityEngine;
using System.Collections.Generic;
public class {systemName} : MonoBehaviour
{{
[Header(""Grid Settings"")]
public int gridWidth = {gridWidth};
public int gridHeight = {gridHeight};
public bool use3D = {use3D};
public float nodeSize = 1f;
[Header(""Pathfinding Settings"")]
public LayerMask obstacleLayer = 1;
public bool allowDiagonal = true;
private PathNode[,] grid;
private List<Vector3> currentPath;
public class PathNode
{{
public int x, y;
public bool isWalkable;
public float gCost, hCost;
public float fCost {{ get {{ return gCost + hCost; }} }}
public PathNode parent;
public PathNode(int x, int y, bool isWalkable = true)
{{
this.x = x;
this.y = y;
this.isWalkable = isWalkable;
}}
}}
void Start()
{{
CreateGrid();
}}
void CreateGrid()
{{
grid = new PathNode[gridWidth, gridHeight];
for (int x = 0; x < gridWidth; x++)
{{
for (int y = 0; y < gridHeight; y++)
{{
Vector3 worldPos = new Vector3(x * nodeSize, 0, y * nodeSize);
bool walkable = !Physics.CheckSphere(worldPos, nodeSize / 2, obstacleLayer);
grid[x, y] = new PathNode(x, y, walkable);
}}
}}
}}
public List<Vector3> FindPath(Vector3 startPos, Vector3 targetPos)
{{
PathNode startNode = GetNodeFromWorldPos(startPos);
PathNode targetNode = GetNodeFromWorldPos(targetPos);
if (startNode == null || targetNode == null || !targetNode.isWalkable)
return null;
return FindPathAStar(startNode, targetNode);
}}
private List<Vector3> FindPathAStar(PathNode startNode, PathNode targetNode)
{{
List<PathNode> openSet = new List<PathNode>();
HashSet<PathNode> closedSet = new HashSet<PathNode>();
openSet.Add(startNode);
while (openSet.Count > 0)
{{
PathNode currentNode = openSet[0];
for (int i = 1; i < openSet.Count; i++)
{{
if (openSet[i].fCost < currentNode.fCost ||
(openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost))
{{
currentNode = openSet[i];
}}
}}
openSet.Remove(currentNode);
closedSet.Add(currentNode);
if (currentNode == targetNode)
{{
List<Vector3> path = new List<Vector3>();
PathNode pathNode = targetNode;
while (pathNode != startNode)
{{
path.Add(new Vector3(pathNode.x * nodeSize, 0, pathNode.y * nodeSize));
pathNode = pathNode.parent;
}}
path.Add(new Vector3(startNode.x * nodeSize, 0, startNode.y * nodeSize));
path.Reverse();
currentPath = path;
return path;
}}
foreach (PathNode neighbor in GetNeighbors(currentNode))
{{
if (!neighbor.isWalkable || closedSet.Contains(neighbor))
continue;
float newCostToNeighbor = currentNode.gCost + GetDistance(currentNode, neighbor);
if (newCostToNeighbor < neighbor.gCost || !openSet.Contains(neighbor))
{{
neighbor.gCost = newCostToNeighbor;
neighbor.hCost = GetDistance(neighbor, targetNode);
neighbor.parent = currentNode;
if (!openSet.Contains(neighbor))
openSet.Add(neighbor);
}}
}}
}}
return null;
}}
private List<PathNode> GetNeighbors(PathNode node)
{{
List<PathNode> neighbors = new List<PathNode>();
for (int x = -1; x <= 1; x++)
{{
for (int y = -1; y <= 1; y++)
{{
if (x == 0 && y == 0) continue;
if (!allowDiagonal && (Mathf.Abs(x) + Mathf.Abs(y)) > 1) continue;
int checkX = node.x + x;
int checkY = node.y + y;
if (checkX >= 0 && checkX < gridWidth && checkY >= 0 && checkY < gridHeight)
{{
neighbors.Add(grid[checkX, checkY]);
}}
}}
}}
return neighbors;
}}
private float GetDistance(PathNode nodeA, PathNode nodeB)
{{
float dstX = Mathf.Abs(nodeA.x - nodeB.x);
float dstY = Mathf.Abs(nodeA.y - nodeB.y);
return allowDiagonal ? Mathf.Sqrt(dstX * dstX + dstY * dstY) : dstX + dstY;
}}
private PathNode GetNodeFromWorldPos(Vector3 worldPos)
{{
int x = Mathf.RoundToInt(worldPos.x / nodeSize);
int y = Mathf.RoundToInt(worldPos.z / nodeSize);
if (x >= 0 && x < gridWidth && y >= 0 && y < gridHeight)
return grid[x, y];
return null;
}}
}}";
}
#endregion
#region Helper Methods
/// <summary>
/// Get light counts by type (replaces deprecated Light.GetLights)
/// </summary>
private Dictionary<string, object> GetLightCounts()
{
var allLights = UnityEngine.Object.FindObjectsOfType<Light>();
int directional = 0, point = 0, spot = 0;
foreach (var light in allLights)
{
switch (light.type)
{
case LightType.Directional: directional++; break;
case LightType.Point: point++; break;
case LightType.Spot: spot++; break;
}
}
return new Dictionary<string, object>
{
["total"] = directional + point + spot,
["directional"] = directional,
["point"] = point,
["spot"] = spot
};
}
/// <summary>
/// Convert Dictionary<string, string> to Dictionary<string, object>
/// </summary>
private Dictionary<string, object> ConvertParameters(Dictionary<string, string> parameters)
{
var result = new Dictionary<string, object>();
foreach (var kvp in parameters)
{
result[kvp.Key] = kvp.Value;
}
return result;
}
/// <summary>
/// Get target GameObject from parameters
/// </summary>
private GameObject GetTargetGameObject(Dictionary<string, string> parameters)
{
// Search target with various key names
string targetName = parameters.GetValueOrDefault("target") ??
parameters.GetValueOrDefault("gameObject") ??
parameters.GetValueOrDefault("object") ??
parameters.GetValueOrDefault("targetObject") ??
parameters.GetValueOrDefault("name") ??
parameters.GetValueOrDefault("objectName") ??
parameters.GetValueOrDefault("targetName") ??
parameters.GetValueOrDefault("source") ??
parameters.GetValueOrDefault("sourceName");
if (string.IsNullOrEmpty(targetName) || targetName == "last")
{
return lastCreatedObject;
}
// First, try by InstanceID or direct name search
var found = FindGameObjectByNameOrId(targetName);
if (found != null) return found;
// If not found, partial match search
var allObjects = GameObject.FindObjectsOfType<GameObject>();
foreach (var obj in allObjects)
{
if (obj.name.Contains(targetName))
{
return obj;
}
}
// If still not found, search in created objects
foreach (var obj in createdObjects)
{
if (obj != null && obj.name.Contains(targetName))
{
return obj;
}
}
return null;
}
#endregion
#region UI Design Tools
/// <summary>
/// Apply theme (dark theme, light theme, custom)
/// </summary>
private string ApplyUITheme(Dictionary<string, object> parameters)
{
try
{
string themeName = parameters.ContainsKey("theme") ? parameters["theme"].ToString() : "dark";
string targetName = parameters.ContainsKey("target") ? parameters["target"].ToString() : null;
var theme = GetThemeColors(themeName);
var targets = GetUITargets(targetName);
int updatedCount = 0;
foreach (var target in targets)
{
ApplyThemeToGameObject(target, theme);
updatedCount++;
}
return $"Applied theme '{themeName}' to {updatedCount} UI elements";
}
catch (Exception e)
{
return $"Theme application error: {e.Message}";
}
}
/// <summary>
/// UI color palette settings
/// </summary>
private string SetUIColors(Dictionary<string, object> parameters)
{
try
{
string targetName = parameters.ContainsKey("target") ? parameters["target"].ToString() : null;
string primaryColor = parameters.ContainsKey("primary") ? parameters["primary"].ToString() : "#3498db";
string secondaryColor = parameters.ContainsKey("secondary") ? parameters["secondary"].ToString() : "#2c3e50";
string accentColor = parameters.ContainsKey("accent") ? parameters["accent"].ToString() : "#e74c3c";
string backgroundColor = parameters.ContainsKey("background") ? parameters["background"].ToString() : "#ecf0f1";
var targets = GetUITargets(targetName);
int updatedCount = 0;
foreach (var target in targets)
{
var colorMap = new Dictionary<string, Color>
{
["primary"] = ParseColor(primaryColor),
["secondary"] = ParseColor(secondaryColor),
["accent"] = ParseColor(accentColor),
["background"] = ParseColor(backgroundColor)
};
ApplyColorsToGameObject(target, colorMap);
updatedCount++;
}
return $"Color palette applied successfully to {updatedCount} UI elements";
}
catch (Exception e)
{
return $"Color configuration error: {e.Message}";
}
}
/// <summary>
/// Apply style to UI elements (minimal, modern, neon, etc.)
/// </summary>
private string StyleUIElements(Dictionary<string, object> parameters)
{
try
{
string targetName = parameters.ContainsKey("target") ? parameters["target"].ToString() : null;
string styleName = parameters.ContainsKey("style") ? parameters["style"].ToString() : "minimal";
var targets = GetUITargets(targetName);
var styleConfig = GetStyleConfig(styleName);
int updatedCount = 0;
foreach (var target in targets)
{
ApplyStyleToGameObject(target, styleConfig);
updatedCount++;
}
return $"Applied style '{styleName}' to {updatedCount} UI elements";
}
catch (Exception e)
{
return $"Style application error: {e.Message}";
}
}
/// <summary>
/// Add UI visual effects (shadow, glow, gradient)
/// </summary>
private string AddUIEffects(Dictionary<string, object> parameters)
{
try
{
string targetName = parameters.ContainsKey("target") ? parameters["target"].ToString() : null;
string effectType = parameters.ContainsKey("effect") ? parameters["effect"].ToString() : "shadow";
var targets = GetUITargets(targetName);
int updatedCount = 0;
foreach (var target in targets)
{
switch (effectType.ToLower())
{
case "shadow":
AddShadowEffect(target, parameters);
break;
case "glow":
AddGlowEffect(target, parameters);
break;
case "gradient":
AddGradientEffect(target, parameters);
break;
case "outline":
AddOutlineEffect(target, parameters);
break;
}
updatedCount++;
}
return $"Added effect '{effectType}' to {updatedCount} UI elements";
}
catch (Exception e)
{
return $"Effect addition error: {e.Message}";
}
}
/// <summary>
/// Typography settings (font, size, weight)
/// </summary>
private string SetTypography(Dictionary<string, object> parameters)
{
try
{
string targetName = parameters.ContainsKey("target") ? parameters["target"].ToString() : null;
string fontFamily = parameters.ContainsKey("font") ? parameters["font"].ToString() : null;
float fontSize = parameters.ContainsKey("size") ? Convert.ToSingle(parameters["size"]) : 14f;
string fontWeight = parameters.ContainsKey("weight") ? parameters["weight"].ToString() : "normal";
string textColor = parameters.ContainsKey("color") ? parameters["color"].ToString() : "#000000";
var targets = GetUITargets(targetName);
int updatedCount = 0;
foreach (var target in targets)
{
var textComponents = target.GetComponentsInChildren<TextMeshProUGUI>(true);
var legacyTextComponents = target.GetComponentsInChildren<Text>(true);
foreach (var textComp in textComponents)
{
if (fontFamily != null)
{
var font = GetTMPFont(fontFamily);
if (font != null) textComp.font = font;
}
textComp.fontSize = fontSize;
textComp.color = ParseColor(textColor);
// Apply font weight
ApplyFontWeight(textComp, fontWeight);
updatedCount++;
}
foreach (var textComp in legacyTextComponents)
{
if (fontFamily != null)
{
var font = GetLegacyFont(fontFamily);
if (font != null) textComp.font = font;
}
textComp.fontSize = Mathf.RoundToInt(fontSize);
textComp.color = ParseColor(textColor);
// Legacy text font style
ApplyLegacyFontWeight(textComp, fontWeight);
updatedCount++;
}
}
return $"Typography applied successfully to {updatedCount} text elements";
}
catch (Exception e)
{
return $"Typography configuration error: {e.Message}";
}
}
#region Helper Methods for UI Design
private Dictionary<string, Color> GetThemeColors(string themeName)
{
switch (themeName.ToLower())
{
case "dark":
return new Dictionary<string, Color>
{
["primary"] = new Color(0.2f, 0.2f, 0.2f, 1f),
["secondary"] = new Color(0.15f, 0.15f, 0.15f, 1f),
["accent"] = new Color(0.3f, 0.6f, 1f, 1f),
["text"] = Color.white,
["background"] = new Color(0.1f, 0.1f, 0.1f, 1f)
};
case "light":
return new Dictionary<string, Color>
{
["primary"] = Color.white,
["secondary"] = new Color(0.95f, 0.95f, 0.95f, 1f),
["accent"] = new Color(0.2f, 0.6f, 1f, 1f),
["text"] = Color.black,
["background"] = new Color(0.98f, 0.98f, 0.98f, 1f)
};
case "neon":
return new Dictionary<string, Color>
{
["primary"] = new Color(0.05f, 0.05f, 0.1f, 1f),
["secondary"] = new Color(0.1f, 0.05f, 0.15f, 1f),
["accent"] = new Color(0f, 1f, 1f, 1f),
["text"] = new Color(0f, 1f, 0.5f, 1f),
["background"] = Color.black
};
default:
return GetThemeColors("dark");
}
}
private List<GameObject> GetUITargets(string targetName)
{
var targets = new List<GameObject>();
if (string.IsNullOrEmpty(targetName))
{
// Get all UI elements
var canvases = UnityEngine.Object.FindObjectsOfType<Canvas>();
foreach (var canvas in canvases)
{
targets.Add(canvas.gameObject);
}
}
else
{
var found = GameObject.Find(targetName);
if (found != null)
{
targets.Add(found);
}
// Pattern matching by name
var allObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (var obj in allObjects)
{
if (obj.name.ToLower().Contains(targetName.ToLower()))
{
targets.Add(obj);
}
}
}
return targets.Distinct().ToList();
}
private void ApplyThemeToGameObject(GameObject target, Dictionary<string, Color> theme)
{
// Image components
var images = target.GetComponentsInChildren<Image>(true);
foreach (var image in images)
{
if (image.gameObject.name.ToLower().Contains("background"))
{
image.color = theme["background"];
}
else if (image.gameObject.name.ToLower().Contains("button"))
{
image.color = theme["primary"];
}
else
{
image.color = theme["secondary"];
}
}
// Text components
var texts = target.GetComponentsInChildren<TextMeshProUGUI>(true);
foreach (var text in texts)
{
text.color = theme["text"];
}
var legacyTexts = target.GetComponentsInChildren<Text>(true);
foreach (var text in legacyTexts)
{
text.color = theme["text"];
}
}
private void ApplyColorsToGameObject(GameObject target, Dictionary<string, Color> colorMap)
{
var images = target.GetComponentsInChildren<Image>(true);
var buttons = target.GetComponentsInChildren<Button>(true);
foreach (var image in images)
{
var objName = image.gameObject.name.ToLower();
if (objName.Contains("primary") || objName.Contains("button"))
{
image.color = colorMap["primary"];
}
else if (objName.Contains("secondary"))
{
image.color = colorMap["secondary"];
}
else if (objName.Contains("accent"))
{
image.color = colorMap["accent"];
}
else if (objName.Contains("background"))
{
image.color = colorMap["background"];
}
}
}
private Dictionary<string, object> GetStyleConfig(string styleName)
{
switch (styleName.ToLower())
{
case "minimal":
return new Dictionary<string, object>
{
["cornerRadius"] = 0f,
["borderWidth"] = 0f,
["padding"] = 10f,
["spacing"] = 5f
};
case "modern":
return new Dictionary<string, object>
{
["cornerRadius"] = 8f,
["borderWidth"] = 1f,
["padding"] = 15f,
["spacing"] = 10f
};
case "rounded":
return new Dictionary<string, object>
{
["cornerRadius"] = 20f,
["borderWidth"] = 2f,
["padding"] = 20f,
["spacing"] = 15f
};
default:
return GetStyleConfig("minimal");
}
}
private void ApplyStyleToGameObject(GameObject target, Dictionary<string, object> styleConfig)
{
// Style application logic
// In actual implementation, set UI component style properties
}
private void AddShadowEffect(GameObject target, Dictionary<string, object> parameters)
{
var shadow = target.GetComponent<Shadow>();
if (shadow == null)
{
shadow = target.AddComponent<Shadow>();
}
// Apply shadow settings from parameters
if (parameters.ContainsKey("shadowColor"))
{
shadow.effectColor = ParseColor(parameters["shadowColor"].ToString());
}
if (parameters.ContainsKey("shadowDistance"))
{
var distance = Convert.ToSingle(parameters["shadowDistance"]);
shadow.effectDistance = new Vector2(distance, -distance);
}
}
private void AddGlowEffect(GameObject target, Dictionary<string, object> parameters)
{
// Glow effect implementation
// Use Unity UI effect components
}
private void AddGradientEffect(GameObject target, Dictionary<string, object> parameters)
{
// Gradient effect implementation
// Use custom shader or UIGradient component
}
private void AddOutlineEffect(GameObject target, Dictionary<string, object> parameters)
{
var outline = target.GetComponent<Outline>();
if (outline == null)
{
outline = target.AddComponent<Outline>();
}
if (parameters.ContainsKey("outlineColor"))
{
outline.effectColor = ParseColor(parameters["outlineColor"].ToString());
}
if (parameters.ContainsKey("outlineDistance"))
{
var distance = Convert.ToSingle(parameters["outlineDistance"]);
outline.effectDistance = new Vector2(distance, distance);
}
}
private TMP_FontAsset GetTMPFont(string fontName)
{
// Search and get font asset
var fonts = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
return fonts.FirstOrDefault(f => f.name.ToLower().Contains(fontName.ToLower()));
}
private Font GetLegacyFont(string fontName)
{
// Search and get legacy font
var fonts = Resources.FindObjectsOfTypeAll<Font>();
return fonts.FirstOrDefault(f => f.name.ToLower().Contains(fontName.ToLower()));
}
private void ApplyFontWeight(TextMeshProUGUI textComp, string weight)
{
switch (weight.ToLower())
{
case "bold":
textComp.fontStyle |= FontStyles.Bold;
break;
case "italic":
textComp.fontStyle |= FontStyles.Italic;
break;
case "normal":
textComp.fontStyle = FontStyles.Normal;
break;
}
}
private void ApplyLegacyFontWeight(Text textComp, string weight)
{
switch (weight.ToLower())
{
case "bold":
textComp.fontStyle = FontStyle.Bold;
break;
case "italic":
textComp.fontStyle = FontStyle.Italic;
break;
case "normal":
textComp.fontStyle = FontStyle.Normal;
break;
}
}
#endregion
#endregion
#region Undo/Redo and Batch Operations
/// <summary>
/// Batch execute multiple operations
/// </summary>
private async Task<string> ExecuteBatch(Dictionary<string, string> parameters)
{
try
{
string tasksJson = parameters.GetValueOrDefault("tasks", "[]");
bool progressFeedback = parameters.GetValueOrDefault("progressFeedback", "true") == "true";
var tasks = JsonConvert.DeserializeObject<List<BatchTask>>(tasksJson);
if (tasks == null || tasks.Count == 0)
{
return "No tasks to execute";
}
var results = new List<string>();
var successCount = 0;
var failCount = 0;
// Start undo group
UnityEditor.Undo.SetCurrentGroupName("Batch Operations");
var undoGroup = UnityEditor.Undo.GetCurrentGroup();
for (int i = 0; i < tasks.Count; i++)
{
var task = tasks[i];
try
{
if (progressFeedback)
{
SynLog.Info($"[Batch {i+1}/{tasks.Count}] {task.description}");
}
// Execute task
var operation = new NexusUnityOperation
{
type = task.tool,
parameters = task.parameters ?? new Dictionary<string, string>()
};
var result = await ExecuteOperation(operation);
results.Add($"✅ {task.description}: {result}");
successCount++;
// Wait a bit (for Unity editor responsiveness)
await Task.Delay(10);
}
catch (Exception e)
{
results.Add($"❌ {task.description}: {e.Message}");
failCount++;
}
}
UnityEditor.Undo.CollapseUndoOperations(undoGroup);
var summary = $"Batch execution completed: {successCount} succeeded, {failCount} failed\n\n" +
string.Join("\n", results);
return summary;
}
catch (Exception e)
{
return $"Batch execution error: {e.Message}";
}
}
#region Batch Helper Methods
private class BatchTask
{
public string tool;
public Dictionary<string, string> parameters;
public string description;
}
private string GetSearchFilterForAssetType(string assetType)
{
switch (assetType.ToLower())
{
case "texture":
return "t:Texture2D";
case "model":
return "t:Model";
case "audio":
return "t:AudioClip";
default:
return "";
}
}
private void ApplyTextureImportSettings(string assetPath, Dictionary<string, object> settings)
{
var importer = UnityEditor.AssetImporter.GetAtPath(assetPath) as UnityEditor.TextureImporter;
if (importer == null) return;
foreach (var setting in settings)
{
switch (setting.Key.ToLower())
{
case "maxsize":
if (int.TryParse(setting.Value.ToString(), out int maxSize))
{
importer.maxTextureSize = maxSize;
}
break;
case "compression":
if (System.Enum.TryParse<UnityEditor.TextureImporterCompression>(setting.Value.ToString(), true, out var compression))
{
importer.textureCompression = compression;
}
break;
case "mipmaps":
if (bool.TryParse(setting.Value.ToString(), out bool mipmaps))
{
importer.mipmapEnabled = mipmaps;
}
break;
}
}
importer.SaveAndReimport();
}
private void ApplyModelImportSettings(string assetPath, Dictionary<string, object> settings)
{
var importer = UnityEditor.AssetImporter.GetAtPath(assetPath) as UnityEditor.ModelImporter;
if (importer == null) return;
foreach (var setting in settings)
{
switch (setting.Key.ToLower())
{
case "scale":
if (float.TryParse(setting.Value.ToString(), out float scale))
{
importer.globalScale = scale;
}
break;
case "generatecolliders":
if (bool.TryParse(setting.Value.ToString(), out bool generateColliders))
{
importer.addCollider = generateColliders;
}
break;
case "importmaterials":
if (bool.TryParse(setting.Value.ToString(), out bool importMaterials))
{
importer.materialImportMode = importMaterials ?
UnityEditor.ModelImporterMaterialImportMode.ImportStandard :
UnityEditor.ModelImporterMaterialImportMode.None;
}
break;
}
}
importer.SaveAndReimport();
}
private void ApplyAudioImportSettings(string assetPath, Dictionary<string, object> settings)
{
var importer = UnityEditor.AssetImporter.GetAtPath(assetPath) as UnityEditor.AudioImporter;
if (importer == null) return;
foreach (var setting in settings)
{
switch (setting.Key.ToLower())
{
case "quality":
if (float.TryParse(setting.Value.ToString(), out float quality))
{
var defaultSettings = importer.defaultSampleSettings;
defaultSettings.quality = quality;
importer.defaultSampleSettings = defaultSettings;
}
break;
case "loadtype":
if (System.Enum.TryParse<UnityEngine.AudioClipLoadType>(setting.Value.ToString(), true, out var loadType))
{
var defaultSettings = importer.defaultSampleSettings;
defaultSettings.loadType = loadType;
importer.defaultSampleSettings = defaultSettings;
}
break;
}
}
importer.SaveAndReimport();
}
private bool UpdateComponentProperties(Component component, Dictionary<string, object> propertyUpdates)
{
bool modified = false;
foreach (var update in propertyUpdates)
{
try
{
SetComponentProperty(component, update.Key, update.Value.ToString());
modified = true;
}
catch (Exception e)
{
SynLog.Warn($"Failed to update property {update.Key} on {component.GetType().Name}: {e.Message}");
}
}
return modified;
}
#endregion
#endregion
#region Package Management
/// <summary>
/// Get list of installed packages
/// </summary>
private string ListPackages(Dictionary<string, string> parameters)
{
try
{
var filterType = parameters.GetValueOrDefault("filter", "all").ToLower();
var listRequest = UnityEditor.PackageManager.Client.List(true, filterType == "offline");
// Wait synchronously (Editor only)
while (!listRequest.IsCompleted)
{
System.Threading.Thread.Sleep(10);
}
if (listRequest.Status == UnityEditor.PackageManager.StatusCode.Success)
{
var packages = listRequest.Result.Select(p => new
{
name = p.name,
displayName = p.displayName,
version = p.version,
description = p.description,
documentationUrl = p.documentationUrl,
type = p.source.ToString(),
isBuiltIn = p.source == UnityEditor.PackageManager.PackageSource.BuiltIn
}).ToList();
return JsonConvert.SerializeObject(new
{
success = true,
packages = packages,
count = packages.Count
}, Formatting.Indented);
}
else
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Failed to list packages: {listRequest.Error?.message}"
});
}
}
catch (Exception e)
{
return CreateErrorResponse("ListPackages", e, parameters);
}
}
/// <summary>
/// Install package
/// </summary>
private string InstallPackage(Dictionary<string, string> parameters)
{
try
{
var packageId = parameters.GetValueOrDefault("packageId", "");
if (string.IsNullOrEmpty(packageId))
{
return CreateMissingParameterResponse("InstallPackage", "packageId", parameters);
}
SynLog.Info($"[InstallPackage] Installing package: {packageId}");
var addRequest = UnityEditor.PackageManager.Client.Add(packageId);
// Wait synchronously
while (!addRequest.IsCompleted)
{
System.Threading.Thread.Sleep(10);
}
if (addRequest.Status == UnityEditor.PackageManager.StatusCode.Success)
{
var package = addRequest.Result;
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Successfully installed package: {package.displayName}",
package = new
{
name = package.name,
displayName = package.displayName,
version = package.version
}
});
}
else
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Failed to install package: {addRequest.Error?.message}"
});
}
}
catch (Exception e)
{
return CreateErrorResponse("InstallPackage", e, parameters);
}
}
/// <summary>
/// Uninstall package
/// </summary>
private string RemovePackage(Dictionary<string, string> parameters)
{
try
{
var packageName = parameters.GetValueOrDefault("packageName", "");
if (string.IsNullOrEmpty(packageName))
{
return CreateMissingParameterResponse("RemovePackage", "packageName", parameters);
}
SynLog.Info($"[RemovePackage] Removing package: {packageName}");
var removeRequest = UnityEditor.PackageManager.Client.Remove(packageName);
// Wait synchronously
while (!removeRequest.IsCompleted)
{
System.Threading.Thread.Sleep(10);
}
if (removeRequest.Status == UnityEditor.PackageManager.StatusCode.Success)
{
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Successfully removed package: {packageName}"
});
}
else
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Failed to remove package: {removeRequest.Error?.message}"
});
}
}
catch (Exception e)
{
return CreateErrorResponse("RemovePackage", e, parameters);
}
}
/// <summary>
/// Check package existence
/// </summary>
private string CheckPackage(Dictionary<string, string> parameters)
{
try
{
var packageName = parameters.GetValueOrDefault("packageName", "");
if (string.IsNullOrEmpty(packageName))
{
return CreateMissingParameterResponse("CheckPackage", "packageName", parameters);
}
var listRequest = UnityEditor.PackageManager.Client.List();
// Wait synchronously
while (!listRequest.IsCompleted)
{
System.Threading.Thread.Sleep(10);
}
if (listRequest.Status == UnityEditor.PackageManager.StatusCode.Success)
{
var package = listRequest.Result.FirstOrDefault(p => p.name == packageName);
return JsonConvert.SerializeObject(new
{
success = true,
isInstalled = package != null,
package = package != null ? new
{
name = package.name,
displayName = package.displayName,
version = package.version,
source = package.source.ToString()
} : null
});
}
else
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Failed to check package: {listRequest.Error?.message}"
});
}
}
catch (Exception e)
{
return CreateErrorResponse("CheckPackage", e, parameters);
}
}
#endregion
#region Serialization Helpers
/// <summary>
/// Convert values for serialization to prevent circular references (Vector3, etc.)
/// </summary>
private object ConvertValueForSerialization(object value)
{
if (value == null) return null;
// Vector3Prevent circular references
if (value is Vector3 v3)
{
return new { x = v3.x, y = v3.y, z = v3.z };
}
// Vector2Prevent circular references
if (value is Vector2 v2)
{
return new { x = v2.x, y = v2.y };
}
// ColorPrevent circular references
if (value is Color color)
{
return new { r = color.r, g = color.g, b = color.b, a = color.a };
}
// QuaternionPrevent circular references
if (value is Quaternion quat)
{
return new { x = quat.x, y = quat.y, z = quat.z, w = quat.w };
}
// GameObjectArrayPrevent circular references
if (value is GameObject[] gameObjects)
{
return gameObjects.Select(go => go != null ? new { name = go.name, instanceId = go.GetInstanceID() } : null).ToArray();
}
// Single GameObject to prevent circular references
if (value is GameObject gameObject)
{
return gameObject != null ? new { name = gameObject.name, instanceId = gameObject.GetInstanceID() } : null;
}
// TransformPrevent circular references
if (value is Transform transform)
{
return transform != null ? new { name = transform.name, position = ConvertValueForSerialization(transform.position) } : null;
}
// Material - prevent circular references from Color.linear
if (value is Material material)
{
if (material == null) return null;
var assetPath = UnityEditor.AssetDatabase.GetAssetPath(material);
return new {
name = material.name,
shader = material.shader != null ? material.shader.name : null,
assetPath = !string.IsNullOrEmpty(assetPath) ? assetPath : null,
instanceId = material.GetInstanceID()
};
}
// Texture - prevent circular references
if (value is Texture texture)
{
if (texture == null) return null;
var assetPath = UnityEditor.AssetDatabase.GetAssetPath(texture);
return new {
name = texture.name,
width = texture.width,
height = texture.height,
assetPath = !string.IsNullOrEmpty(assetPath) ? assetPath : null,
instanceId = texture.GetInstanceID()
};
}
// Generic UnityEngine.Object fallback - prevent circular references
if (value is UnityEngine.Object unityObj)
{
if (unityObj == null) return null;
var assetPath = UnityEditor.AssetDatabase.GetAssetPath(unityObj);
return new {
name = unityObj.name,
type = unityObj.GetType().Name,
assetPath = !string.IsNullOrEmpty(assetPath) ? assetPath : null,
instanceId = unityObj.GetInstanceID()
};
}
return value;
}
#endregion
#region Property Parsing Helpers
/// <summary>
/// Parse key=value format string and convert to Dictionary
/// Supported formats: "mass = 5.0", "mass=5.0; useGravity=true", "position = (1, 2, 3)"
/// </summary>
private Dictionary<string, object> ParseKeyValueString(string input)
{
var result = new Dictionary<string, object>();
if (string.IsNullOrEmpty(input))
return result;
SynLog.Info($"[ParseKeyValueString] Parsing: '{input}'");
try
{
// Process multiple properties separated by semicolons
var pairs = input.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var pair in pairs)
{
var trimmedPair = pair.Trim();
if (string.IsNullOrEmpty(trimmedPair))
continue;
// Split by = or :
var separatorIndex = trimmedPair.IndexOf('=');
if (separatorIndex < 0)
{
separatorIndex = trimmedPair.IndexOf(':');
}
if (separatorIndex > 0)
{
var key = trimmedPair.Substring(0, separatorIndex).Trim();
var value = trimmedPair.Substring(separatorIndex + 1).Trim();
if (!string.IsNullOrEmpty(key))
{
result[key] = value;
SynLog.Info($"[ParseKeyValueString] Parsed: {key} = {value}");
}
}
else
{
// If neither = nor :, treat entire string as "value" key
result["value"] = trimmedPair;
SynLog.Info($"[ParseKeyValueString] No separator found, using as value: {trimmedPair}");
}
}
}
catch (Exception ex)
{
Debug.LogError($"[ParseKeyValueString] Error parsing '{input}': {ex.Message}");
result["value"] = input; // Fallback
}
return result;
}
#endregion
#region Error Handling Helpers
/// <summary>
/// Helper method to generate unified error response
/// </summary>
private string CreateErrorResponse(string methodName, Exception exception, Dictionary<string, string> parameters = null)
{
// Safe logging
try
{
Debug.LogError($"[{methodName}] Error: {exception?.Message ?? "Unknown error"}");
Debug.LogError($"[{methodName}] Exception type: {exception?.GetType().Name ?? "Unknown"}");
// Limit stack trace as it can be too long
string stackTrace = exception?.StackTrace ?? "";
if (stackTrace.Length > 1000)
{
stackTrace = stackTrace.Substring(0, 1000) + "... (truncated)";
}
Debug.LogError($"[{methodName}] Stack trace: {stackTrace}");
}
catch (Exception loggingEx)
{
Debug.LogError($"[CreateErrorResponse] Failed to log error: {loggingEx.Message}");
}
// Safely create error response
try
{
var errorResponse = new Dictionary<string, object>
{
["success"] = false,
["error"] = exception?.Message ?? "Unknown error occurred",
["method"] = methodName ?? "Unknown method",
["errorType"] = exception?.GetType().Name ?? "Unknown",
["timestamp"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
["parameters"] = parameters?.Keys.ToArray() ?? new string[0],
["debugInfo"] = "Check Unity Console for detailed debug logs"
};
// Additional context info
if (parameters != null && parameters.Count > 0)
{
var paramInfo = new Dictionary<string, object>();
foreach (var kvp in parameters.Take(5)) // Only first 5 parameters
{
try
{
paramInfo[kvp.Key] = kvp.Value?.Length > 100 ?
kvp.Value.Substring(0, 100) + "..." : kvp.Value;
}
catch
{
paramInfo[kvp.Key] = "[Unable to serialize]";
}
}
errorResponse["parameterValues"] = paramInfo;
}
return JsonConvert.SerializeObject(errorResponse, Formatting.Indented);
}
catch (Exception serializationEx)
{
// Last resort: minimal error info
Debug.LogError($"[CreateErrorResponse] Failed to serialize error response: {serializationEx.Message}");
return JsonConvert.SerializeObject(new
{
success = false,
error = "Error occurred and could not be properly serialized",
method = methodName ?? "Unknown",
timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
});
}
}
/// <summary>
/// Generate response for missing parameter error
/// </summary>
private string CreateMissingParameterResponse(string methodName, string missingParameter, Dictionary<string, string> parameters)
{
Debug.LogError($"[{methodName}] Missing required parameter: {missingParameter}");
var errorResponse = new
{
success = false,
error = $"Required parameter '{missingParameter}' is missing",
method = methodName,
missingParameter = missingParameter,
receivedParameters = parameters.Keys.ToArray(),
hint = $"Please provide the '{missingParameter}' parameter"
};
return JsonConvert.SerializeObject(errorResponse, Formatting.Indented);
}
/// <summary>
/// GameObject not found ErrorGenerate response
/// </summary>
private string CreateGameObjectNotFoundResponse(string methodName, string searchedName, Dictionary<string, string> parameters)
{
var availableObjects = GameObject.FindObjectsOfType<GameObject>().Take(10).Select(o => o.name);
var errorResponse = new
{
success = false,
error = $"GameObject '{searchedName}' not found",
method = methodName,
searchedName = searchedName,
availableObjects = availableObjects.ToArray(),
parameters = parameters.Keys.ToArray(),
hint = "Check available objects or ensure the GameObject exists in the scene"
};
return JsonConvert.SerializeObject(errorResponse, Formatting.Indented);
}
#endregion
// Screen Effects Helper Classes
[System.Serializable]
public class ScreenShakeController : MonoBehaviour
{
private Vector3 originalPosition;
private float shakeDuration = 0f;
private float shakeIntensity = 0f;
private float shakeFrequency = 10f;
private float shakeDamping = 1f;
private float shakeTimer = 0f;
void Awake()
{
originalPosition = transform.localPosition;
}
public void StartShake(float duration, float intensity, float frequency, float damping)
{
shakeDuration = duration;
shakeIntensity = intensity;
shakeFrequency = frequency;
shakeDamping = damping;
shakeTimer = 0f;
}
void Update()
{
if (shakeDuration > 0)
{
shakeTimer += Time.deltaTime;
float progress = shakeTimer / shakeDuration;
float dampingFactor = 1f - Mathf.Pow(progress, shakeDamping);
float x = Mathf.PerlinNoise(Time.time * shakeFrequency, 0f) * 2f - 1f;
float y = Mathf.PerlinNoise(0f, Time.time * shakeFrequency) * 2f - 1f;
transform.localPosition = originalPosition + new Vector3(x, y, 0f) * shakeIntensity * dampingFactor;
if (shakeTimer >= shakeDuration)
{
transform.localPosition = originalPosition;
shakeDuration = 0f;
}
}
}
}
[System.Serializable]
public class ScreenFadeController : MonoBehaviour
{
private UnityEngine.UI.Image fadeImage;
private float fadeDuration;
private float fadeDelay;
private string fadeType;
private string fadeCurve;
private float fadeTimer = 0f;
private bool isFading = false;
void Awake()
{
fadeImage = GetComponent<UnityEngine.UI.Image>();
}
public void StartFade(string type, float duration, float delay, string curve)
{
fadeType = type;
fadeDuration = duration;
fadeDelay = delay;
fadeCurve = curve;
fadeTimer = -delay;
isFading = true;
// Set initial alpha
Color c = fadeImage.color;
c.a = (fadeType == "FadeIn") ? 1f : 0f;
fadeImage.color = c;
}
void Update()
{
if (isFading)
{
fadeTimer += Time.deltaTime;
if (fadeTimer > 0)
{
float progress = fadeTimer / fadeDuration;
progress = Mathf.Clamp01(progress);
// Apply curve
switch (fadeCurve)
{
case "EaseIn":
progress = progress * progress;
break;
case "EaseOut":
progress = 1f - (1f - progress) * (1f - progress);
break;
case "EaseInOut":
progress = progress < 0.5f ? 2f * progress * progress : 1f - 2f * (1f - progress) * (1f - progress);
break;
}
Color c = fadeImage.color;
if (fadeType == "FadeIn")
{
c.a = 1f - progress;
}
else // FadeOut
{
c.a = progress;
}
fadeImage.color = c;
if (fadeTimer >= fadeDuration)
{
isFading = false;
if (fadeType == "FadeIn")
{
GameObject.Destroy(transform.parent.gameObject); // Destroy canvas after fade in
}
}
}
}
}
}
[System.Serializable]
public class VignetteController : MonoBehaviour
{
public float intensity = 0.5f;
public float smoothness = 0.5f;
public bool rounded = true;
public Color vignetteColor = Color.black;
private UnityEngine.UI.Image vignetteImage;
private Material vignetteMaterial;
void Start()
{
vignetteImage = GetComponent<UnityEngine.UI.Image>();
CreateVignetteMaterial();
UpdateVignette();
}
void CreateVignetteMaterial()
{
// Create a simple vignette using gradient
var texture = new Texture2D(256, 256);
for (int x = 0; x < 256; x++)
{
for (int y = 0; y < 256; y++)
{
float distX = (x - 128f) / 128f;
float distY = (y - 128f) / 128f;
float dist = rounded ? Mathf.Sqrt(distX * distX + distY * distY) : Mathf.Max(Mathf.Abs(distX), Mathf.Abs(distY));
float vignette = Mathf.Clamp01(Mathf.Lerp(0f, 1f, (dist - (1f - intensity)) / smoothness));
texture.SetPixel(x, y, new Color(0, 0, 0, vignette));
}
}
texture.Apply();
var sprite = Sprite.Create(texture, new Rect(0, 0, 256, 256), Vector2.one * 0.5f);
vignetteImage.sprite = sprite;
vignetteImage.type = UnityEngine.UI.Image.Type.Sliced;
vignetteImage.color = vignetteColor;
}
public void UpdateVignette()
{
CreateVignetteMaterial();
}
}
[System.Serializable]
public class ChromaticAberrationController : MonoBehaviour
{
public float intensity = 0.1f;
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
// Simple chromatic aberration using color channel offset
// This is a basic implementation without custom shaders
if (intensity > 0 && source != null && destination != null)
{
// For a basic effect, we just copy the source
// Real implementation would offset RGB channels
Graphics.Blit(source, destination);
}
else
{
Graphics.Blit(source, destination);
}
}
}
// Shader Tools Helper Classes
[System.Serializable]
public class ShaderPropertyAnimator : MonoBehaviour
{
private Renderer targetRenderer;
private MaterialPropertyBlock propBlock;
private List<AnimationData> animations = new List<AnimationData>();
[System.Serializable]
private class AnimationData
{
public string propertyName;
public AnimationType type;
public object startValue;
public object endValue;
public float duration;
public string curve;
public bool loop;
public float timer;
public bool isPlaying = true;
}
private enum AnimationType
{
Color,
Float,
Vector
}
void Awake()
{
targetRenderer = GetComponent<Renderer>();
propBlock = new MaterialPropertyBlock();
}
public void AnimateColor(string propertyName, Color start, Color end, float duration, string curve, bool loop)
{
var anim = new AnimationData
{
propertyName = propertyName,
type = AnimationType.Color,
startValue = start,
endValue = end,
duration = duration,
curve = curve,
loop = loop,
timer = 0f
};
animations.Add(anim);
}
public void AnimateFloat(string propertyName, float start, float end, float duration, string curve, bool loop)
{
var anim = new AnimationData
{
propertyName = propertyName,
type = AnimationType.Float,
startValue = start,
endValue = end,
duration = duration,
curve = curve,
loop = loop,
timer = 0f
};
animations.Add(anim);
}
public void AnimateVector(string propertyName, Vector3 start, Vector3 end, float duration, string curve, bool loop)
{
var anim = new AnimationData
{
propertyName = propertyName,
type = AnimationType.Vector,
startValue = start,
endValue = end,
duration = duration,
curve = curve,
loop = loop,
timer = 0f
};
animations.Add(anim);
}
void Update()
{
if (targetRenderer == null || animations.Count == 0) return;
targetRenderer.GetPropertyBlock(propBlock);
bool hasChanges = false;
foreach (var anim in animations)
{
if (!anim.isPlaying) continue;
anim.timer += Time.deltaTime;
float progress = Mathf.Clamp01(anim.timer / anim.duration);
// Apply curve
progress = ApplyCurve(progress, anim.curve);
// Apply animation based on type
switch (anim.type)
{
case AnimationType.Color:
Color currentColor = Color.Lerp((Color)anim.startValue, (Color)anim.endValue, progress);
propBlock.SetColor(anim.propertyName, currentColor);
hasChanges = true;
break;
case AnimationType.Float:
float currentFloat = Mathf.Lerp((float)anim.startValue, (float)anim.endValue, progress);
propBlock.SetFloat(anim.propertyName, currentFloat);
hasChanges = true;
break;
case AnimationType.Vector:
Vector4 currentVector = Vector4.Lerp((Vector3)anim.startValue, (Vector3)anim.endValue, progress);
propBlock.SetVector(anim.propertyName, currentVector);
hasChanges = true;
break;
}
// Handle loop
if (anim.timer >= anim.duration)
{
if (anim.loop)
{
anim.timer = 0f;
}
else
{
anim.isPlaying = false;
}
}
}
if (hasChanges)
{
targetRenderer.SetPropertyBlock(propBlock);
}
}
private float ApplyCurve(float t, string curve)
{
switch (curve)
{
case "EaseIn":
return t * t;
case "EaseOut":
return 1f - (1f - t) * (1f - t);
case "EaseInOut":
return t < 0.5f ? 2f * t * t : 1f - 2f * (1f - t) * (1f - t);
case "Bounce":
if (t < 0.5f)
return 8f * t * t * t * t;
else
return 1f - 8f * (t - 1f) * (t - 1f) * (t - 1f) * (t - 1f);
default: // Linear
return t;
}
}
}
[System.Serializable]
public class MaterialPropertyBlockController : MonoBehaviour
{
public string blockName = "CustomPropertyBlock";
public bool preserveSharedMaterial = true;
private MaterialPropertyBlock propBlock;
private Renderer targetRenderer;
private Dictionary<string, object> properties = new Dictionary<string, object>();
void Awake()
{
targetRenderer = GetComponent<Renderer>();
propBlock = new MaterialPropertyBlock();
}
public void SetProperty(string name, object value)
{
properties[name] = value;
}
public int GetPropertyCount()
{
return properties.Count;
}
public void ApplyPropertyBlock()
{
if (targetRenderer == null) return;
targetRenderer.GetPropertyBlock(propBlock);
foreach (var prop in properties)
{
if (prop.Value is float floatValue)
{
propBlock.SetFloat(prop.Key, floatValue);
}
else if (prop.Value is int intValue)
{
propBlock.SetInt(prop.Key, intValue);
}
else if (prop.Value is Color colorValue)
{
propBlock.SetColor(prop.Key, colorValue);
}
else if (prop.Value is Vector4 vectorValue)
{
propBlock.SetVector(prop.Key, vectorValue);
}
else if (prop.Value is Texture textureValue)
{
propBlock.SetTexture(prop.Key, textureValue);
}
else if (prop.Value is string stringValue)
{
// Try to parse as color
if (ColorUtility.TryParseHtmlString(stringValue, out Color color))
{
propBlock.SetColor(prop.Key, color);
}
}
}
targetRenderer.SetPropertyBlock(propBlock);
}
}
[System.Serializable]
public class ShaderTextureAnimator : MonoBehaviour
{
public string propertyName = "_MainTex";
public string animationType = "Scroll";
public Vector2 scrollSpeed = Vector2.one;
public Vector2 tilingScale = Vector2.one;
public int columns = 4;
public int rows = 4;
public float fps = 30f;
private Renderer targetRenderer;
private MaterialPropertyBlock propBlock;
private float timer = 0f;
private int currentFrame = 0;
void Awake()
{
targetRenderer = GetComponent<Renderer>();
propBlock = new MaterialPropertyBlock();
}
void Update()
{
if (targetRenderer == null) return;
targetRenderer.GetPropertyBlock(propBlock);
switch (animationType)
{
case "Scroll":
Vector2 offset = scrollSpeed * Time.time;
propBlock.SetVector(propertyName + "_ST", new Vector4(tilingScale.x, tilingScale.y, offset.x, offset.y));
break;
case "Flipbook":
timer += Time.deltaTime;
if (timer >= 1f / fps)
{
timer = 0f;
currentFrame = (currentFrame + 1) % (columns * rows);
}
int row = currentFrame / columns;
int col = currentFrame % columns;
Vector2 size = new Vector2(1f / columns, 1f / rows);
Vector2 frameOffset = new Vector2(col * size.x, (rows - 1 - row) * size.y);
propBlock.SetVector(propertyName + "_ST", new Vector4(size.x, size.y, frameOffset.x, frameOffset.y));
break;
case "Rotate":
float angle = Time.time * scrollSpeed.x * Mathf.Deg2Rad;
float cos = Mathf.Cos(angle);
float sin = Mathf.Sin(angle);
Matrix4x4 rotationMatrix = new Matrix4x4();
rotationMatrix.SetRow(0, new Vector4(cos, -sin, 0, 0));
rotationMatrix.SetRow(1, new Vector4(sin, cos, 0, 0));
rotationMatrix.SetRow(2, new Vector4(0, 0, 1, 0));
rotationMatrix.SetRow(3, new Vector4(0, 0, 0, 1));
propBlock.SetMatrix(propertyName + "_TexMatrix", rotationMatrix);
break;
}
targetRenderer.SetPropertyBlock(propBlock);
}
}
// Camera Effects Helper Classes - Removed
// Advanced camera effects require Post Processing Stack or custom shaders
// ===== Weather System Helper Classes =====
[System.Serializable]
public class RainController : MonoBehaviour
{
public float intensity = 0.5f;
public float dropSize = 0.02f;
public float dropSpeed = 10f;
public float windStrength = 0f;
public Vector3 rainArea = new Vector3(50, 30, 50);
public bool useSplash = true;
private ParticleSystem rainParticles;
private List<GameObject> splashEffects = new List<GameObject>();
void Start()
{
rainParticles = GetComponent<ParticleSystem>();
if (useSplash)
{
InitializeSplashEffects();
}
}
void InitializeSplashEffects()
{
// Create splash effect pool
for (int i = 0; i < 50; i++)
{
var splash = CreatePrimitiveWithMaterial(PrimitiveType.Quad);
splash.name = $"RainSplash_{i}";
splash.transform.SetParent(transform);
splash.transform.localScale = Vector3.one * 0.1f;
splash.SetActive(false);
splashEffects.Add(splash);
}
}
void Update()
{
// Update wind effect
if (rainParticles != null)
{
var velocityOverLifetime = rainParticles.velocityOverLifetime;
// Keep all axes in same mode when updating
velocityOverLifetime.x = new ParticleSystem.MinMaxCurve(windStrength);
velocityOverLifetime.y = new ParticleSystem.MinMaxCurve(0f);
velocityOverLifetime.z = new ParticleSystem.MinMaxCurve(0f);
}
}
}
[System.Serializable]
public class SnowController : MonoBehaviour
{
public float intensity = 0.3f;
public float flakeSizeMin = 0.1f;
public float flakeSizeMax = 0.3f;
public float fallSpeed = 1f;
public float turbulence = 0.5f;
public Vector3 snowArea = new Vector3(50, 20, 50);
public bool accumulation = false;
private ParticleSystem snowParticles;
private float accumulationLevel = 0f;
void Start()
{
snowParticles = GetComponent<ParticleSystem>();
}
void Update()
{
if (accumulation)
{
accumulationLevel += Time.deltaTime * intensity * 0.01f;
// Would update ground materials/shaders here
}
}
}
[System.Serializable]
public class WindController : MonoBehaviour
{
public float windStrength = 5f;
public Vector3 windDirection = Vector3.right;
public float turbulence = 0.5f;
public float gustFrequency = 0.2f;
public bool affectTrees = true;
public bool affectParticles = true;
private WindZone windZone;
private float gustTimer = 0f;
void Start()
{
windZone = GetComponent<WindZone>();
}
void Update()
{
// Wind gust simulation
gustTimer += Time.deltaTime;
float gustMultiplier = 1f + Mathf.Sin(gustTimer * gustFrequency * Mathf.PI * 2f) * 0.5f;
if (windZone != null)
{
windZone.windMain = windStrength * gustMultiplier;
}
// Affect particle systems if enabled
if (affectParticles)
{
var particleSystems = UnityEngine.Object.FindObjectsOfType<ParticleSystem>();
foreach (var ps in particleSystems)
{
var forceOverLifetime = ps.forceOverLifetime;
if (forceOverLifetime.enabled)
{
forceOverLifetime.x = new ParticleSystem.MinMaxCurve(windDirection.x * windStrength);
forceOverLifetime.z = new ParticleSystem.MinMaxCurve(windDirection.z * windStrength);
}
}
}
}
}
[System.Serializable]
public class LightningController : MonoBehaviour
{
public float frequency = 0.1f;
public float intensity = 5f;
public float duration = 0.2f;
public Color lightningColor = new Color(0.88f, 0.88f, 1f);
public float minDelay = 2f;
public float maxDelay = 10f;
public bool affectSkybox = true;
public Light lightningLight;
private float nextStrikeTime;
private bool isStriking = false;
private float strikeTimer = 0f;
private Color originalAmbientColor;
private Material skyboxMaterial;
void Start()
{
nextStrikeTime = Time.time + Random.Range(minDelay, maxDelay);
originalAmbientColor = RenderSettings.ambientLight;
if (affectSkybox && RenderSettings.skybox != null)
{
skyboxMaterial = new Material(RenderSettings.skybox);
RenderSettings.skybox = skyboxMaterial;
}
}
void Update()
{
if (Time.time >= nextStrikeTime && !isStriking)
{
StartCoroutine(StrikeLightning());
nextStrikeTime = Time.time + Random.Range(minDelay, maxDelay) / frequency;
}
if (isStriking)
{
strikeTimer += Time.deltaTime;
if (strikeTimer >= duration)
{
EndStrike();
}
}
}
System.Collections.IEnumerator StrikeLightning()
{
isStriking = true;
strikeTimer = 0f;
// Flash effect
if (lightningLight != null)
{
lightningLight.intensity = intensity;
}
// Ambient light flash
RenderSettings.ambientLight = Color.Lerp(originalAmbientColor, lightningColor, 0.8f);
// Skybox tint
if (skyboxMaterial != null)
{
skyboxMaterial.SetColor("_Tint", lightningColor);
}
// Multiple flashes for realism
yield return new WaitForSeconds(0.05f);
if (lightningLight != null) lightningLight.intensity = 0;
yield return new WaitForSeconds(0.05f);
if (lightningLight != null) lightningLight.intensity = intensity * 0.7f;
yield return null;
}
void EndStrike()
{
isStriking = false;
if (lightningLight != null)
{
lightningLight.intensity = 0;
}
RenderSettings.ambientLight = originalAmbientColor;
if (skyboxMaterial != null)
{
skyboxMaterial.SetColor("_Tint", Color.white);
}
}
void OnDestroy()
{
// Restore original settings
RenderSettings.ambientLight = originalAmbientColor;
if (skyboxMaterial != null)
{
RenderSettings.skybox = skyboxMaterial.shader.name.Contains("Procedural") ? null : skyboxMaterial;
}
}
}
[System.Serializable]
public class ThunderstormController : MonoBehaviour
{
public float intensity = 0.8f;
public float windStrength = 10f;
public float lightningFrequency = 0.2f;
public float fogDensity = 0.02f;
public float duration = 0f;
private float startTime;
private GameObject[] weatherComponents;
private Color originalFogColor;
private float originalFogDensity;
private bool originalFogState;
void Start()
{
startTime = Time.time;
// Store original fog settings
originalFogState = RenderSettings.fog;
originalFogColor = RenderSettings.fogColor;
originalFogDensity = RenderSettings.fogDensity;
// Collect all weather components
weatherComponents = new GameObject[]
{
GameObject.Find("RainSystem"),
GameObject.Find("WindSystem"),
GameObject.Find("LightningSystem")
};
}
void Update()
{
if (duration > 0 && Time.time - startTime > duration)
{
EndThunderstorm();
}
}
void EndThunderstorm()
{
// Restore fog settings
RenderSettings.fog = originalFogState;
RenderSettings.fogColor = originalFogColor;
RenderSettings.fogDensity = originalFogDensity;
// Destroy weather components
foreach (var component in weatherComponents)
{
if (component != null)
{
Destroy(component);
}
}
Destroy(gameObject);
}
}
// ===== Unified Weather System Helper Classes =====
[System.Serializable]
public class WeatherParameters : MonoBehaviour
{
private Dictionary<string, object> parameters = new Dictionary<string, object>();
public void SetParameter(string key, object value)
{
parameters[key] = value;
}
public T GetParameter<T>(string key, T defaultValue = default(T))
{
if (parameters.ContainsKey(key))
{
return (T)parameters[key];
}
return defaultValue;
}
}
[System.Serializable]
public class WeatherSystemController : MonoBehaviour
{
public string currentPreset = "sunny";
public string currentWeatherState = "sunny";
public float intensity = 0.5f;
public float transitionTime = 5f;
private Coroutine transitionCoroutine;
private List<GameObject> weatherEffects = new List<GameObject>();
// Stored weather states for transitions
private WeatherState currentState;
private WeatherState targetState;
[System.Serializable]
public class WeatherState
{
public float fogDensity;
public Color fogColor;
public Color ambientLight;
public float sunIntensity;
public Color sunColor;
public float shadowStrength;
}
public void ClearWeatherEffects()
{
// Remove all weather particle systems
var rainSystem = GameObject.Find("RainSystem");
if (rainSystem != null) DestroyImmediate(rainSystem);
var snowSystem = GameObject.Find("SnowSystem");
if (snowSystem != null) DestroyImmediate(snowSystem);
var windSystem = GameObject.Find("WindSystem");
if (windSystem != null) DestroyImmediate(windSystem);
var lightningSystem = GameObject.Find("LightningSystem");
if (lightningSystem != null) DestroyImmediate(lightningSystem);
var thunderstorm = GameObject.Find("Thunderstorm");
if (thunderstorm != null) DestroyImmediate(thunderstorm);
var fogSystem = GameObject.Find("FoggyWeather");
if (fogSystem != null) DestroyImmediate(fogSystem);
weatherEffects.Clear();
}
public void TransitionToPreset(string preset, float duration)
{
if (transitionCoroutine != null)
{
StopCoroutine(transitionCoroutine);
}
transitionCoroutine = StartCoroutine(TransitionCoroutine(preset, duration));
}
System.Collections.IEnumerator TransitionCoroutine(string preset, float duration)
{
// Capture current state
currentState = new WeatherState
{
fogDensity = RenderSettings.fogDensity,
fogColor = RenderSettings.fogColor,
ambientLight = RenderSettings.ambientLight,
sunIntensity = 1f,
sunColor = Color.white,
shadowStrength = 0.7f
};
var sun = GameObject.Find("Directional Light");
if (sun == null) sun = GameObject.Find("Sun");
if (sun != null)
{
var light = sun.GetComponent<Light>();
if (light != null)
{
currentState.sunIntensity = light.intensity;
currentState.sunColor = light.color;
currentState.shadowStrength = light.shadowStrength;
}
}
// Apply new preset temporarily to get target values
var tempController = gameObject.AddComponent<WeatherSystemController>();
tempController.enabled = false;
// Get target state based on preset
targetState = GetTargetStateForPreset(preset);
// Clean up temp controller
DestroyImmediate(tempController);
// Clear current effects
ClearWeatherEffects();
// Start particle effects for target preset
ApplyPresetEffects(preset);
// Transition atmosphere over time
float elapsed = 0;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
t = Mathf.SmoothStep(0, 1, t);
// Interpolate fog
RenderSettings.fogDensity = Mathf.Lerp(currentState.fogDensity, targetState.fogDensity, t);
RenderSettings.fogColor = Color.Lerp(currentState.fogColor, targetState.fogColor, t);
RenderSettings.ambientLight = Color.Lerp(currentState.ambientLight, targetState.ambientLight, t);
// Interpolate sun
if (sun != null)
{
var light = sun.GetComponent<Light>();
if (light != null)
{
light.intensity = Mathf.Lerp(currentState.sunIntensity, targetState.sunIntensity, t);
light.color = Color.Lerp(currentState.sunColor, targetState.sunColor, t);
light.shadowStrength = Mathf.Lerp(currentState.shadowStrength, targetState.shadowStrength, t);
}
}
yield return null;
}
// Ensure final values are set
RenderSettings.fogDensity = targetState.fogDensity;
RenderSettings.fogColor = targetState.fogColor;
RenderSettings.ambientLight = targetState.ambientLight;
currentWeatherState = preset;
currentPreset = preset;
}
private WeatherState GetTargetStateForPreset(string preset)
{
switch (preset.ToLower())
{
case "sunny":
return new WeatherState
{
fogDensity = 0.01f,
fogColor = new Color(0.9f, 0.95f, 1f),
ambientLight = new Color(1f, 0.95f, 0.8f),
sunIntensity = 1.2f,
sunColor = new Color(1f, 0.95f, 0.8f),
shadowStrength = 0.7f
};
case "rainy":
return new WeatherState
{
fogDensity = 0.05f * intensity,
fogColor = new Color(0.4f, 0.4f, 0.5f),
ambientLight = new Color(0.5f, 0.5f, 0.6f),
sunIntensity = 0.4f,
sunColor = new Color(0.8f, 0.8f, 0.9f),
shadowStrength = 0.3f
};
case "snowy":
return new WeatherState
{
fogDensity = 0.02f,
fogColor = new Color(0.85f, 0.85f, 0.9f),
ambientLight = new Color(0.8f, 0.8f, 0.85f),
sunIntensity = 0.8f,
sunColor = new Color(0.9f, 0.9f, 1f),
shadowStrength = 0.2f
};
case "stormy":
return new WeatherState
{
fogDensity = 0.08f,
fogColor = new Color(0.3f, 0.3f, 0.4f),
ambientLight = new Color(0.2f, 0.2f, 0.3f),
sunIntensity = 0.1f,
sunColor = new Color(0.5f, 0.5f, 0.6f),
shadowStrength = 0.1f
};
case "foggy":
return new WeatherState
{
fogDensity = 0.1f * intensity,
fogColor = new Color(0.7f, 0.7f, 0.75f),
ambientLight = new Color(0.6f, 0.6f, 0.65f),
sunIntensity = 0.3f,
sunColor = new Color(0.85f, 0.85f, 0.9f),
shadowStrength = 0.1f
};
default:
return new WeatherState
{
fogDensity = 0.01f,
fogColor = Color.white,
ambientLight = Color.white,
sunIntensity = 1f,
sunColor = Color.white,
shadowStrength = 0.5f
};
}
}
private void ApplyPresetEffects(string preset)
{
// This method would be called from the main executor
// to recreate particle effects during transitions
}
}
// ===== Time of Day System Helper Classes =====
[System.Serializable]
public class TimeOfDayController : MonoBehaviour
{
public float currentTime = 12f;
public float dayDuration = 60f;
public float latitude = 35f;
public float longitude = 0f;
public bool enableClouds = true;
public bool enableStars = true;
public Light sunLight;
public Light moonLight;
// Color settings
public Color sunriseColor = new Color(1f, 0.55f, 0.26f);
public Color noonColor = Color.white;
public Color sunsetColor = new Color(1f, 0.42f, 0.21f);
public Color nightColor = new Color(0.12f, 0.23f, 0.54f);
// Time settings
public float sunriseTime = 6f;
public float sunsetTime = 18f;
// Fog settings
public float fogDensityDay = 0.005f;
public float fogDensityNight = 0.02f;
// Performance settings
[Tooltip("Update interval in seconds. Lower = smoother but heavier. Default 0.1 = 10 updates/sec")]
public float updateInterval = 0.1f;
// Internal
private float timeSpeed;
private List<TimeEvent> timeEvents = new List<TimeEvent>();
public SkyboxBlendController skyboxController;
private float lastUpdateTime;
private float lastVisualTime = -1f;
void Start()
{
timeSpeed = 24f / dayDuration;
UpdateTimeOfDay();
}
void Update()
{
// Update time every frame (lightweight)
currentTime += timeSpeed * Time.deltaTime;
if (currentTime >= 24f) currentTime -= 24f;
if (currentTime < 0f) currentTime += 24f;
// Throttle visual updates for performance
if (Time.time - lastUpdateTime >= updateInterval)
{
// Only update visuals if time changed significantly (0.01 hour = ~36 seconds)
if (Mathf.Abs(currentTime - lastVisualTime) > 0.01f)
{
UpdateTimeOfDay();
lastVisualTime = currentTime;
}
lastUpdateTime = Time.time;
}
CheckTimeEvents();
}
public void UpdateTimeOfDay()
{
// Calculate sun and moon positions
float sunAngle = (currentTime - 6f) / 12f * 180f;
float moonAngle = sunAngle + 180f;
if (sunLight != null)
{
sunLight.transform.rotation = Quaternion.Euler(sunAngle - 90f, 170f, 0);
// Sun intensity and color
float sunIntensity = Mathf.Clamp01((sunAngle - 0f) / 180f);
if (sunAngle > 180f) sunIntensity = 0;
sunLight.intensity = sunIntensity;
sunLight.color = GetTimeColor();
sunLight.enabled = sunIntensity > 0.01f;
}
if (moonLight != null)
{
moonLight.transform.rotation = Quaternion.Euler(moonAngle - 90f, 170f, 0);
float moonIntensity = 1f - Mathf.Clamp01((sunAngle - 0f) / 180f);
if (sunAngle < 180f) moonIntensity *= 0.2f;
moonLight.intensity = moonIntensity * 0.3f;
moonLight.enabled = moonIntensity > 0.01f;
}
// Update ambient light
RenderSettings.ambientLight = GetTimeColor() * 0.5f;
// Update fog
float fogDensity = Mathf.Lerp(fogDensityNight, fogDensityDay, sunLight ? sunLight.intensity : 0);
RenderSettings.fogDensity = fogDensity;
RenderSettings.fogColor = GetTimeColor() * 0.7f;
// Update skybox if controller exists
if (skyboxController != null)
{
skyboxController.UpdateBlend(currentTime);
}
}
private Color GetTimeColor()
{
if (currentTime < sunriseTime)
return nightColor;
else if (currentTime < sunriseTime + 2)
return Color.Lerp(nightColor, sunriseColor, (currentTime - sunriseTime) / 2f);
else if (currentTime < 12)
return Color.Lerp(sunriseColor, noonColor, (currentTime - sunriseTime - 2) / (12 - sunriseTime - 2));
else if (currentTime < sunsetTime - 2)
return Color.Lerp(noonColor, sunsetColor, (currentTime - 12) / (sunsetTime - 2 - 12));
else if (currentTime < sunsetTime)
return Color.Lerp(sunsetColor, nightColor, (currentTime - sunsetTime + 2) / 2f);
else
return nightColor;
}
public void TransitionToTime(float targetTime, float duration)
{
StartCoroutine(TransitionCoroutine(targetTime, duration));
}
System.Collections.IEnumerator TransitionCoroutine(float targetTime, float duration)
{
float startTime = currentTime;
float elapsed = 0;
// Stop normal time progression during transition
float originalTimeSpeed = timeSpeed;
timeSpeed = 0;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
// Handle wrap around
float delta = targetTime - startTime;
if (delta > 12) delta -= 24;
if (delta < -12) delta += 24;
currentTime = startTime + delta * t;
if (currentTime >= 24) currentTime -= 24;
if (currentTime < 0) currentTime += 24;
UpdateTimeOfDay();
yield return null;
}
currentTime = targetTime;
timeSpeed = originalTimeSpeed;
}
public void AddTimeEvent(TimeEvent timeEvent)
{
timeEvents.Add(timeEvent);
}
private void CheckTimeEvents()
{
foreach (var evt in timeEvents)
{
if (Mathf.Abs(currentTime - evt.triggerTime) < 0.1f)
{
if (evt.eventType == "Once" && evt.hasTriggered)
continue;
evt.hasTriggered = true;
ExecuteTimeEvent(evt);
}
else
{
evt.hasTriggered = false;
}
}
}
private void ExecuteTimeEvent(TimeEvent evt)
{
SynLog.Info($"[TimeOfDay] Event '{evt.eventName}' triggered at {currentTime:F1}:00");
// Execute action based on event configuration
if (!string.IsNullOrEmpty(evt.targetObject))
{
GameObject target = GameObject.Find(evt.targetObject);
if (target != null)
{
// Example actions
switch (evt.action.ToLower())
{
case "activate":
target.SetActive(true);
break;
case "deactivate":
target.SetActive(false);
break;
case "toggle":
target.SetActive(!target.activeSelf);
break;
}
}
}
}
}
[System.Serializable]
public class TimeEvent
{
public string eventName;
public float triggerTime;
public string eventType = "Once"; // Once, Daily
public string action;
public string targetObject;
public bool hasTriggered = false;
}
[System.Serializable]
public class SkyboxBlendController : MonoBehaviour
{
public Material daySkybox;
public Material nightSkybox;
public float cloudSpeed = 0.1f;
public float cloudOpacity = 0.5f;
public string blendCurve = "Smooth";
private Material currentSkybox;
private float lastBlendFactor = -1f;
private static readonly Color dayTint = new Color(0.5f, 0.5f, 0.5f);
private static readonly Color nightTint = new Color(0.1f, 0.1f, 0.2f);
void Start()
{
if (daySkybox != null)
{
currentSkybox = new Material(daySkybox);
RenderSettings.skybox = currentSkybox;
}
}
public void UpdateBlend(float currentTime)
{
if (currentSkybox == null) return;
// Calculate day/night blend factor
float blendFactor = 0;
if (currentTime < 6 || currentTime > 18)
{
// Night time
blendFactor = 1;
}
else if (currentTime >= 6 && currentTime <= 8)
{
// Sunrise transition
blendFactor = 1 - ((currentTime - 6) / 2f);
}
else if (currentTime >= 16 && currentTime <= 18)
{
// Sunset transition
blendFactor = (currentTime - 16) / 2f;
}
// Apply curve
if (blendCurve == "Sharp")
{
blendFactor = blendFactor > 0.5f ? 1 : 0;
}
else if (blendCurve == "Smooth")
{
blendFactor = Mathf.SmoothStep(0, 1, blendFactor);
}
// Skip update if blend factor hasn't changed significantly
if (Mathf.Abs(blendFactor - lastBlendFactor) < 0.001f) return;
lastBlendFactor = blendFactor;
// Update skybox properties
if (currentSkybox.HasProperty("_Tint"))
{
currentSkybox.SetColor("_Tint", Color.Lerp(dayTint, nightTint, blendFactor));
}
if (currentSkybox.HasProperty("_Exposure"))
{
currentSkybox.SetFloat("_Exposure", Mathf.Lerp(1.3f, 0.3f, blendFactor));
}
// Cloud animation
if (currentSkybox.HasProperty("_Rotation"))
{
float rotation = Time.time * cloudSpeed;
currentSkybox.SetFloat("_Rotation", rotation % 360);
}
}
}
#region Batch Material Operations
/// <summary>
/// Apply material to multiple objects in batch
/// </summary>
private string BatchMaterialApply(Dictionary<string, string> parameters)
{
try
{
var materialPath = parameters.GetValueOrDefault("materialPath", "");
var targetObjects = parameters.GetValueOrDefault("targetObjects", "");
var searchPattern = parameters.GetValueOrDefault("searchPattern", "");
var searchScope = parameters.GetValueOrDefault("searchScope", "scene"); // scene, selected, children
var replaceAll = parameters.GetValueOrDefault("replaceAll", "true") == "true";
// Material load
Material material = null;
string resolvedMaterialPath = materialPath;
if (!string.IsNullOrEmpty(materialPath))
{
// Try exact path first
material = AssetDatabase.LoadAssetAtPath<Material>(materialPath);
// If not found, try common locations
if (material == null)
{
var searchPaths = new[]
{
materialPath,
$"Assets/Synaptic_Generated/{Path.GetFileName(materialPath)}",
$"Assets/Materials/{Path.GetFileName(materialPath)}",
$"Assets/{Path.GetFileName(materialPath)}"
};
foreach (var path in searchPaths)
{
material = AssetDatabase.LoadAssetAtPath<Material>(path);
if (material != null)
{
resolvedMaterialPath = path;
SynLog.Info($"[BatchMaterialApply] Found material at: {path}");
break;
}
}
}
// If still not found, search by name in entire project
if (material == null)
{
var materialName = Path.GetFileNameWithoutExtension(materialPath);
var guids = AssetDatabase.FindAssets($"t:Material {materialName}");
foreach (var guid in guids)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guid);
var foundMat = AssetDatabase.LoadAssetAtPath<Material>(assetPath);
if (foundMat != null && foundMat.name == materialName)
{
material = foundMat;
resolvedMaterialPath = assetPath;
SynLog.Info($"[BatchMaterialApply] Found material by name at: {assetPath}");
break;
}
}
}
if (material == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"Material not found at path: {materialPath}. Searched in: Assets/Synaptic_Generated/, Assets/Materials/, and by name in project."
});
}
}
// TargetObjectGet
List<GameObject> targets = new List<GameObject>();
// Determine base object according to search scope
GameObject[] searchBase = null;
GameObject parentObject = null;
// If targetObjects is not specified and searchScope is children
if (!string.IsNullOrEmpty(targetObjects) && searchScope.ToLower() == "children")
{
// Use specified object as parent, target its children
parentObject = GameObject.Find(targetObjects.Trim());
if (parentObject != null)
{
searchBase = parentObject.GetComponentsInChildren<Transform>()
.Select(t => t.gameObject).ToArray();
targets.AddRange(searchBase);
}
}
else if (!string.IsNullOrEmpty(targetObjects))
{
// Normal list of specified object names
var names = targetObjects.Split(',');
foreach (var name in names)
{
var obj = GameObject.Find(name.Trim());
if (obj != null) targets.Add(obj);
}
}
else if (!string.IsNullOrEmpty(searchPattern))
{
// Search by pattern
switch (searchScope.ToLower())
{
case "selected":
searchBase = Selection.gameObjects;
break;
case "children":
if (Selection.activeGameObject != null)
{
searchBase = Selection.activeGameObject.GetComponentsInChildren<Transform>()
.Select(t => t.gameObject).ToArray();
}
break;
default: // scene
searchBase = GameObject.FindObjectsOfType<GameObject>();
break;
}
if (searchBase != null)
{
var regex = new System.Text.RegularExpressions.Regex(searchPattern);
targets.AddRange(searchBase.Where(obj => regex.IsMatch(obj.name)));
}
}
// Material apply
int modifiedCount = 0;
var results = new List<Dictionary<string, object>>();
foreach (var target in targets)
{
var renderer = target.GetComponent<Renderer>();
if (renderer != null)
{
if (material != null)
{
if (replaceAll)
{
var materials = new Material[renderer.sharedMaterials.Length];
for (int i = 0; i < materials.Length; i++)
{
materials[i] = material;
}
renderer.sharedMaterials = materials;
}
else
{
renderer.sharedMaterial = material;
}
EditorUtility.SetDirty(target);
modifiedCount++;
results.Add(new Dictionary<string, object>
{
["objectName"] = target.name,
["materialApplied"] = material.name,
["materialCount"] = renderer.sharedMaterials.Length
});
}
}
}
if (modifiedCount > 0)
{
AssetDatabase.SaveAssets();
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Applied material to {modifiedCount} objects",
modifiedCount = modifiedCount,
results = results
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
/// <summary>
/// Create prefabs from multiple objects in batch
/// </summary>
private string BatchPrefabCreate(Dictionary<string, string> parameters)
{
try
{
var sourceObjects = parameters.GetValueOrDefault("sourceObjects", "");
var searchPattern = parameters.GetValueOrDefault("searchPattern", "");
var outputFolder = parameters.GetValueOrDefault("outputFolder", "Assets/Prefabs");
var namingPattern = parameters.GetValueOrDefault("namingPattern", "{name}_Prefab");
var createVariants = parameters.GetValueOrDefault("createVariants", "false") == "true";
var deleteOriginal = parameters.GetValueOrDefault("deleteOriginal", "false") == "true";
// Create output folder
if (!AssetDatabase.IsValidFolder(outputFolder))
{
CreateFolderRecursive(outputFolder);
}
// SourceObjectGet
List<GameObject> sources = new List<GameObject>();
if (!string.IsNullOrEmpty(sourceObjects))
{
// List of specified object names
var names = sourceObjects.Split(',');
foreach (var name in names)
{
var obj = GameObject.Find(name.Trim());
if (obj != null) sources.Add(obj);
}
}
else if (!string.IsNullOrEmpty(searchPattern))
{
// Search by pattern
var allObjects = GameObject.FindObjectsOfType<GameObject>();
var regex = new System.Text.RegularExpressions.Regex(searchPattern);
sources.AddRange(allObjects.Where(obj => regex.IsMatch(obj.name)));
}
else
{
// Use selected objects
sources.AddRange(Selection.gameObjects);
}
// PrefabCreate
var createdPrefabs = new List<Dictionary<string, object>>();
int successCount = 0;
foreach (var source in sources)
{
try
{
// Generate prefab name
string prefabName = namingPattern.Replace("{name}", source.name);
if (!prefabName.EndsWith(".prefab"))
{
prefabName += ".prefab";
}
string prefabPath = Path.Combine(outputFolder, prefabName);
// PrefabCreate
GameObject prefab;
if (createVariants && PrefabUtility.IsAnyPrefabInstanceRoot(source))
{
// Create as variant
prefab = PrefabUtility.SaveAsPrefabAsset(source, prefabPath, out bool success);
}
else
{
// Create as normal prefab
prefab = PrefabUtility.SaveAsPrefabAsset(source, prefabPath);
}
if (prefab != null)
{
successCount++;
createdPrefabs.Add(new Dictionary<string, object>
{
["sourceName"] = source.name,
["prefabPath"] = prefabPath,
["prefabName"] = prefab.name,
["isVariant"] = createVariants && PrefabUtility.IsAnyPrefabInstanceRoot(source)
});
// Delete original
if (deleteOriginal)
{
UnityEditor.Undo.DestroyObjectImmediate(source);
}
}
}
catch (Exception innerEx)
{
SynLog.Warn($"Failed to create prefab from {source.name}: {innerEx.Message}");
}
}
AssetDatabase.Refresh();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created {successCount} prefabs",
createdCount = successCount,
prefabs = createdPrefabs,
outputFolder = outputFolder
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = e.Message
});
}
}
/// <summary>
/// Create folder recursively
/// </summary>
private void CreateFolderRecursive(string path)
{
var folders = path.Split('/');
string currentPath = folders[0];
for (int i = 1; i < folders.Length; i++)
{
string nextPath = currentPath + "/" + folders[i];
if (!AssetDatabase.IsValidFolder(nextPath))
{
AssetDatabase.CreateFolder(currentPath, folders[i]);
}
currentPath = nextPath;
}
}
#region Inspector Information Tools
/// <summary>
/// Search GameObject by name (both in Scene and Assets)
/// </summary>
private GameObject FindGameObjectByName(string gameObjectName)
{
// 1. Search active GameObjects in scene
GameObject targetObject = GameObject.Find(gameObjectName);
// 2. If not found, search all GameObjects including inactive
if (targetObject == null)
{
var allObjects = UnityEngine.Object.FindObjectsOfType<GameObject>(true); // includeInactive: true
targetObject = allObjects.FirstOrDefault(go => go.name == gameObjectName);
}
// 3. If still not found, search prefab assets
#if UNITY_EDITOR
if (targetObject == null)
{
// Search as prefab asset
string[] prefabGuids = AssetDatabase.FindAssets($"t:Prefab {gameObjectName}");
foreach (string guid in prefabGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (prefab != null && prefab.name == gameObjectName)
{
targetObject = prefab;
break;
}
}
// Search as GameObject asset
if (targetObject == null)
{
string[] goGuids = AssetDatabase.FindAssets($"t:GameObject {gameObjectName}");
foreach (string guid in goGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GameObject go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (go != null && go.name == gameObjectName)
{
targetObject = go;
break;
}
}
}
// Broad search by name
if (targetObject == null)
{
string[] allGuids = AssetDatabase.FindAssets(gameObjectName);
foreach (string guid in allGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
GameObject go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (go != null && go.name == gameObjectName)
{
targetObject = go;
break;
}
}
}
}
#endif
return targetObject;
}
/// <summary>
/// Get inspector info of specified GameObject
/// </summary>
private string GetInspectorInfo(Dictionary<string, string> parameters)
{
try
{
if (!parameters.TryGetValue("gameObjectName", out var gameObjectName))
{
return CreateErrorResponse("gameObjectName parameter is required");
}
var targetObject = FindGameObjectByName(gameObjectName);
if (targetObject == null)
{
return CreateErrorResponse($"GameObject '{gameObjectName}' not found in scene or assets");
}
bool includePrivateFields = parameters.ContainsKey("includePrivateFields") &&
bool.Parse(parameters.GetValueOrDefault("includePrivateFields", "false"));
bool includeEvents = parameters.ContainsKey("includeEvents") &&
bool.Parse(parameters.GetValueOrDefault("includeEvents", "false"));
string componentFilter = parameters.GetValueOrDefault("componentFilter", "");
var report = GenerateInspectorReport(targetObject, includePrivateFields, includeEvents, componentFilter);
return JsonConvert.SerializeObject(new { success = true, data = report }, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Error getting inspector info: {e.Message}");
}
}
/// <summary>
/// Get inspector info of currently selected GameObject
/// </summary>
private string GetSelectedObjectInfo(Dictionary<string, string> parameters)
{
try
{
var selectedObject = Selection.activeGameObject;
if (selectedObject == null)
{
return CreateErrorResponse("No GameObject is currently selected");
}
bool includePrivateFields = parameters.ContainsKey("includePrivateFields") &&
bool.Parse(parameters.GetValueOrDefault("includePrivateFields", "false"));
bool includeEvents = parameters.ContainsKey("includeEvents") &&
bool.Parse(parameters.GetValueOrDefault("includeEvents", "false"));
string componentFilter = parameters.GetValueOrDefault("componentFilter", "");
var report = GenerateInspectorReport(selectedObject, includePrivateFields, includeEvents, componentFilter);
return JsonConvert.SerializeObject(new { success = true, data = report }, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Error getting selected object info: {e.Message}");
}
}
/// <summary>
/// Get detailed info of specified component
/// </summary>
private string GetComponentDetails(Dictionary<string, string> parameters)
{
try
{
if (!parameters.TryGetValue("gameObjectName", out var gameObjectName))
{
return CreateErrorResponse("gameObjectName parameter is required");
}
if (!parameters.TryGetValue("componentType", out var componentType))
{
return CreateErrorResponse("componentType parameter is required");
}
var targetObject = FindGameObjectByName(gameObjectName);
if (targetObject == null)
{
return CreateErrorResponse($"GameObject '{gameObjectName}' not found in scene or assets");
}
var component = targetObject.GetComponent(componentType);
if (component == null)
{
return CreateErrorResponse($"Component '{componentType}' not found on GameObject '{gameObjectName}'");
}
bool includeSerializedProperties = parameters.ContainsKey("includeSerializedProperties") &&
bool.Parse(parameters.GetValueOrDefault("includeSerializedProperties", "true"));
var report = GenerateComponentReport(component, includeSerializedProperties);
return JsonConvert.SerializeObject(new { success = true, data = report }, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse($"Error getting component details: {e.Message}");
}
}
/// <summary>
/// Generate detailed inspector report for GameObject
/// </summary>
private string GenerateInspectorReport(GameObject gameObject, bool includePrivateFields, bool includeEvents, string componentFilter)
{
var report = new StringBuilder();
report.AppendLine($"=== Inspector Information for '{gameObject.name}' ===");
report.AppendLine();
// Basic info
report.AppendLine("=== Basic Information ===");
report.AppendLine($"Name: {gameObject.name}");
report.AppendLine($"Tag: {gameObject.tag}");
report.AppendLine($"Layer: {LayerMask.LayerToName(gameObject.layer)} ({gameObject.layer})");
report.AppendLine($"Active: {gameObject.activeInHierarchy}");
report.AppendLine($"Static: {gameObject.isStatic}");
report.AppendLine($"Instance ID: {gameObject.GetInstanceID()}");
report.AppendLine();
// TransformInfo
var transform = gameObject.transform;
report.AppendLine("=== Transform ===");
report.AppendLine($"Position: {transform.position}");
report.AppendLine($"Rotation: {transform.rotation.eulerAngles}");
report.AppendLine($"Scale: {transform.localScale}");
report.AppendLine($"Parent: {(transform.parent ? transform.parent.name : "None")}");
report.AppendLine($"Children Count: {transform.childCount}");
report.AppendLine();
// ComponentInfo
var components = gameObject.GetComponents<Component>();
report.AppendLine("=== Components ===");
foreach (var component in components)
{
if (component == null) continue;
var componentType = component.GetType().Name;
// Check if filter is specified
if (!string.IsNullOrEmpty(componentFilter) &&
!componentType.ToLower().Contains(componentFilter.ToLower()))
{
continue;
}
report.AppendLine($"\n--- {componentType} ---");
// Get properties using SerializedObject
var serializedObject = new SerializedObject(component);
serializedObject.Update(); // Update serialized data
var property = serializedObject.GetIterator();
// Debug: Log component type
SynLog.Info($"[NexusExecutor] Inspecting component: {componentType}");
if (property.NextVisible(true))
{
do
{
if (property.name == "m_Script") continue; // Skip script reference
// Debug: Log property info
SynLog.Info($"[NexusExecutor] Property: {property.displayName} ({property.propertyPath}), Type: {property.propertyType}, IsArray: {property.isArray}");
// Show details if array or list
if (property.isArray && property.propertyType != SerializedPropertyType.String)
{
report.AppendLine($" {property.displayName}: Array[{property.arraySize}]");
// Debug logging
SynLog.Info($"[NexusExecutor] Processing array: {property.displayName} with {property.arraySize} elements");
// Show each array element (max 10)
int displayCount = Mathf.Min(property.arraySize, 10);
for (int i = 0; i < displayCount; i++)
{
var element = property.GetArrayElementAtIndex(i);
var elementValue = GetSerializedPropertyValue(element);
report.AppendLine($" [{i}]: {elementValue}");
// Debug logging for each element
SynLog.Info($"[NexusExecutor] Array element [{i}]: {elementValue}, Type: {element.propertyType}");
// Show more details if object reference
if (element.propertyType == SerializedPropertyType.ObjectReference &&
element.objectReferenceValue != null)
{
var objRef = element.objectReferenceValue;
if (objRef is GameObject go)
{
report.AppendLine($" └─ GameObject: {go.name}");
report.AppendLine($" Path: {GetGameObjectPath(go)}");
// PrefabIfAddInfo
#if UNITY_EDITOR
if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(go))
{
report.AppendLine($" Type: Prefab Asset");
var prefabPath = UnityEditor.AssetDatabase.GetAssetPath(go);
report.AppendLine($" Asset Path: {prefabPath}");
}
#endif
}
else if (objRef is Component comp)
{
report.AppendLine($" └─ Component: {comp.GetType().Name}");
report.AppendLine($" GameObject: {comp.gameObject.name}");
}
else
{
report.AppendLine($" └─ Asset: {objRef.GetType().Name}");
report.AppendLine($" Name: {objRef.name}");
}
}
}
if (property.arraySize > 10)
{
report.AppendLine($" ... and {property.arraySize - 10} more items");
}
}
else
{
var propertyValue = GetSerializedPropertyValue(property);
report.AppendLine($" {property.displayName}: {propertyValue}");
}
}
while (property.NextVisible(false));
}
// Get additional info using reflection (optional)
if (includePrivateFields)
{
var fields = component.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length > 0)
{
report.AppendLine(" [Private Fields]:");
foreach (var field in fields.Take(5)) // Only first 5
{
try
{
var value = field.GetValue(component);
report.AppendLine($" {field.Name}: {value}");
}
catch { /* Skip if field access failed */ }
}
}
}
}
report.AppendLine();
return report.ToString();
}
/// <summary>
/// Generate detailed report for specific component
/// </summary>
private string GenerateComponentReport(Component component, bool includeSerializedProperties)
{
var report = new StringBuilder();
var componentType = component.GetType();
report.AppendLine($"=== Component Details: {componentType.Name} ===");
report.AppendLine($"GameObject: {component.gameObject.name}");
report.AppendLine($"Assembly: {componentType.Assembly.GetName().Name}");
report.AppendLine($"Namespace: {componentType.Namespace}");
report.AppendLine();
if (includeSerializedProperties)
{
report.AppendLine("=== Serialized Properties ===");
var serializedObject = new SerializedObject(component);
var property = serializedObject.GetIterator();
if (property.NextVisible(true))
{
do
{
if (property.name == "m_Script") continue;
var propertyType = property.propertyType.ToString();
report.AppendLine($"{property.displayName}:");
report.AppendLine($" Type: {propertyType}");
// Show details if array
if (property.isArray && property.propertyType != SerializedPropertyType.String)
{
report.AppendLine($" Array Size: {property.arraySize}");
// Show each array element (max 20)
int displayCount = Mathf.Min(property.arraySize, 20);
for (int i = 0; i < displayCount; i++)
{
var element = property.GetArrayElementAtIndex(i);
var elementValue = GetSerializedPropertyValue(element);
report.AppendLine($" [{i}]: {elementValue}");
// Object reference details
if (element.propertyType == SerializedPropertyType.ObjectReference &&
element.objectReferenceValue != null)
{
var obj = element.objectReferenceValue;
report.AppendLine($" - Type: {obj.GetType().FullName}");
report.AppendLine($" - Name: {obj.name}");
if (obj is GameObject go)
{
report.AppendLine($" - Path: {GetGameObjectPath(go)}");
report.AppendLine($" - Active: {go.activeInHierarchy}");
report.AppendLine($" - Tag: {go.tag}");
report.AppendLine($" - Layer: {LayerMask.LayerToName(go.layer)}");
}
else if (obj is Component comp)
{
report.AppendLine($" - GameObject: {comp.gameObject.name}");
}
#if UNITY_EDITOR
else
{
string assetPath = AssetDatabase.GetAssetPath(obj);
if (!string.IsNullOrEmpty(assetPath))
{
report.AppendLine($" - Asset Path: {assetPath}");
}
}
#endif
}
}
if (property.arraySize > 20)
{
report.AppendLine($" ... and {property.arraySize - 20} more items");
}
}
else
{
var propertyValue = GetSerializedPropertyValue(property);
report.AppendLine($" Value: {propertyValue}");
}
report.AppendLine($" Path: {property.propertyPath}");
report.AppendLine();
}
while (property.NextVisible(false));
}
}
// Public property info
var properties = componentType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
if (properties.Length > 0)
{
report.AppendLine("=== Public Properties ===");
foreach (var prop in properties.Take(10))
{
try
{
if (prop.CanRead && prop.GetIndexParameters().Length == 0)
{
var value = prop.GetValue(component);
report.AppendLine($"{prop.Name}: {value} ({prop.PropertyType.Name})");
}
}
catch (Exception e)
{
report.AppendLine($"{prop.Name}: [Error reading: {e.Message}]");
}
}
}
return report.ToString();
}
/// <summary>
/// Get GameObject path
/// </summary>
private string GetGameObjectPath(GameObject go)
{
if (go == null) return "null";
#if UNITY_EDITOR
// PrefabIfAssetPathReturn
string assetPath = AssetDatabase.GetAssetPath(go);
if (!string.IsNullOrEmpty(assetPath))
{
return assetPath;
}
#endif
// Return hierarchy path if object in scene
Transform current = go.transform;
string path = current.name;
while (current.parent != null)
{
current = current.parent;
path = current.name + "/" + path;
}
return path;
}
/// <summary>
/// Find GameObject by name or InstanceID
/// Supports both "MyObject" (name) and "12345" (InstanceID) formats
/// </summary>
private GameObject FindGameObjectByNameOrId(string nameOrId)
{
if (string.IsNullOrEmpty(nameOrId))
return null;
// Try by InstanceID first (if it's a number)
if (int.TryParse(nameOrId, out int id))
{
var obj = EditorUtility.InstanceIDToObject(id) as GameObject;
if (obj != null)
return obj;
}
// Try by name
return GameObject.Find(nameOrId);
}
/// <summary>
/// Get SerializedProperty value as string
/// </summary>
private string GetSerializedPropertyValue(SerializedProperty property)
{
switch (property.propertyType)
{
case SerializedPropertyType.Integer:
return property.intValue.ToString();
case SerializedPropertyType.Boolean:
return property.boolValue.ToString();
case SerializedPropertyType.Float:
return property.floatValue.ToString("F3");
case SerializedPropertyType.String:
return $"\"{property.stringValue}\"";
case SerializedPropertyType.Color:
return property.colorValue.ToString();
case SerializedPropertyType.ObjectReference:
if (property.objectReferenceValue == null) return "None";
var objRef = property.objectReferenceValue;
if (objRef is GameObject go)
return $"{go.name} (GameObject)";
else if (objRef is Component comp)
return $"{comp.GetType().Name} on {comp.gameObject.name}";
else
return $"{objRef.name} ({objRef.GetType().Name})";
case SerializedPropertyType.LayerMask:
return property.intValue.ToString();
case SerializedPropertyType.Enum:
return property.enumNames[property.enumValueIndex];
case SerializedPropertyType.Vector2:
return property.vector2Value.ToString();
case SerializedPropertyType.Vector3:
return property.vector3Value.ToString();
case SerializedPropertyType.Vector4:
return property.vector4Value.ToString();
case SerializedPropertyType.Rect:
return property.rectValue.ToString();
case SerializedPropertyType.ArraySize:
return $"Array Size: {property.intValue}";
case SerializedPropertyType.Character:
return property.intValue.ToString();
case SerializedPropertyType.AnimationCurve:
return property.animationCurveValue != null ? "AnimationCurve" : "None";
case SerializedPropertyType.Bounds:
return property.boundsValue.ToString();
case SerializedPropertyType.Quaternion:
return property.quaternionValue.ToString();
default:
return property.isArray ? $"Array[{property.arraySize}]" : property.propertyType.ToString();
}
}
/// <summary>
/// Save shader file and create material
/// </summary>
private string SaveShaderAndCreateMaterial(string shaderName, string shaderCode)
{
try
{
// Create Shaders folder
string shaderFolderPath = "Assets/Synaptic_Generated/Shaders";
if (!AssetDatabase.IsValidFolder(shaderFolderPath))
{
if (!AssetDatabase.IsValidFolder("Assets/Synaptic_Generated"))
{
AssetDatabase.CreateFolder("Assets", "Synaptic_Generated");
}
AssetDatabase.CreateFolder("Assets/Synaptic_Generated", "Shaders");
AssetDatabase.Refresh();
}
// Save shader file
string shaderPath = System.IO.Path.Combine(shaderFolderPath, $"{shaderName}.shader");
System.IO.File.WriteAllText(shaderPath, shaderCode);
AssetDatabase.ImportAsset(shaderPath);
// Load shader
Shader shader = AssetDatabase.LoadAssetAtPath<Shader>(shaderPath);
if (shader == null)
{
return $"Error: Failed to load shader at {shaderPath}";
}
// Create Materials folder
string matFolderPath = "Assets/Synaptic_Generated/Materials";
if (!AssetDatabase.IsValidFolder(matFolderPath))
{
AssetDatabase.CreateFolder("Assets/Synaptic_Generated", "Materials");
AssetDatabase.Refresh();
}
// Create material
Material material = new Material(shader);
material.name = $"{shaderName}_Material";
string materialPath = System.IO.Path.Combine(matFolderPath, $"{shaderName}.mat");
AssetDatabase.CreateAsset(material, materialPath);
AssetDatabase.SaveAssets();
return $"[NexusShader] Created {shaderName} shader at {shaderPath} and material at {materialPath}";
}
catch (System.Exception e)
{
return $"Error creating shader {shaderName}: {e.Message}";
}
}
#endregion
#region Synaptic Pro Tools
/// <summary>
/// Generate SDF face shadow texture for anime characters
/// </summary>
private string GenerateSDFTexture(Dictionary<string, string> parameters)
{
try
{
var meshName = parameters.GetValueOrDefault("meshName", "");
var resolution = Convert.ToInt32(parameters.GetValueOrDefault("resolution", "256"));
var outputPath = parameters.GetValueOrDefault("outputPath", "Assets/Synaptic AI Pro/Textures/SDF");
var outputName = parameters.GetValueOrDefault("outputName", "FaceSDF");
// Create output directory
if (!System.IO.Directory.Exists(outputPath))
{
System.IO.Directory.CreateDirectory(outputPath);
}
// Generate 9-direction SDF lightmaps
var directions = new Vector3[]
{
Vector3.forward, // Front
new Vector3(0.707f, 0, 0.707f), // Front-Right 45
Vector3.right, // Right
new Vector3(0.707f, 0, -0.707f), // Back-Right 45
new Vector3(-0.707f, 0, 0.707f), // Front-Left 45
Vector3.left, // Left
new Vector3(-0.707f, 0, -0.707f), // Back-Left 45
new Vector3(0, 0.707f, 0.707f), // Front-Up 45
new Vector3(0, -0.707f, 0.707f) // Front-Down 45
};
var textures = new List<Texture2D>();
for (int i = 0; i < directions.Length; i++)
{
var tex = new Texture2D(resolution, resolution, TextureFormat.R8, false);
// Generate simple gradient SDF based on direction
for (int y = 0; y < resolution; y++)
{
for (int x = 0; x < resolution; x++)
{
float u = (float)x / resolution * 2f - 1f;
float v = (float)y / resolution * 2f - 1f;
// Simple SDF calculation based on light direction
float sdf = Vector3.Dot(new Vector3(u, v, 0).normalized, directions[i]);
sdf = sdf * 0.5f + 0.5f;
// Apply smoothstep for better shadow transition
sdf = Mathf.SmoothStep(0.3f, 0.7f, sdf);
tex.SetPixel(x, y, new Color(sdf, sdf, sdf, 1));
}
}
tex.Apply();
textures.Add(tex);
}
// Pack into single texture (3x3 grid)
var packedTex = new Texture2D(resolution * 3, resolution * 3, TextureFormat.R8, false);
for (int i = 0; i < 9; i++)
{
int gridX = i % 3;
int gridY = i / 3;
for (int y = 0; y < resolution; y++)
{
for (int x = 0; x < resolution; x++)
{
var color = textures[i].GetPixel(x, y);
packedTex.SetPixel(gridX * resolution + x, gridY * resolution + y, color);
}
}
}
packedTex.Apply();
// Save texture
byte[] pngData = packedTex.EncodeToPNG();
string fullPath = $"{outputPath}/{outputName}.png";
System.IO.File.WriteAllBytes(fullPath, pngData);
AssetDatabase.Refresh();
// Configure texture import settings
TextureImporter importer = AssetImporter.GetAtPath(fullPath) as TextureImporter;
if (importer != null)
{
importer.textureType = TextureImporterType.Default;
importer.sRGBTexture = false;
importer.alphaSource = TextureImporterAlphaSource.None;
importer.mipmapEnabled = false;
importer.SaveAndReimport();
}
// Cleanup
foreach (var tex in textures) UnityEngine.Object.DestroyImmediate(tex);
UnityEngine.Object.DestroyImmediate(packedTex);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Generated SDF texture with 9 directions",
path = fullPath,
resolution = $"{resolution * 3}x{resolution * 3}",
directions = 9
});
}
catch (Exception e)
{
return CreateErrorResponse("GenerateSDFTexture", e, parameters);
}
}
/// <summary>
/// Generate shadow ramp texture for toon shading
/// </summary>
private string GenerateRampTexture(Dictionary<string, string> parameters)
{
try
{
var preset = parameters.GetValueOrDefault("preset", "anime_soft");
var width = Convert.ToInt32(parameters.GetValueOrDefault("width", "256"));
var outputPath = parameters.GetValueOrDefault("outputPath", "Assets/Synaptic AI Pro/Textures/Ramps");
var outputName = parameters.GetValueOrDefault("outputName", "ShadowRamp");
// Create output directory
if (!System.IO.Directory.Exists(outputPath))
{
System.IO.Directory.CreateDirectory(outputPath);
}
// Define presets
Color lightColor, shadowColor;
float midpoint = 0.5f;
float softness = 0.1f;
switch (preset.ToLower())
{
case "genshin_warm":
lightColor = new Color(1f, 0.95f, 0.9f);
shadowColor = new Color(0.7f, 0.4f, 0.5f);
midpoint = 0.45f;
softness = 0.15f;
break;
case "genshin_cool":
lightColor = new Color(0.95f, 0.97f, 1f);
shadowColor = new Color(0.4f, 0.5f, 0.7f);
midpoint = 0.45f;
softness = 0.15f;
break;
case "anime_hard":
lightColor = Color.white;
shadowColor = new Color(0.6f, 0.6f, 0.7f);
midpoint = 0.5f;
softness = 0.02f;
break;
case "anime_soft":
lightColor = Color.white;
shadowColor = new Color(0.7f, 0.7f, 0.8f);
midpoint = 0.5f;
softness = 0.2f;
break;
case "cel_shade":
lightColor = Color.white;
shadowColor = new Color(0.5f, 0.5f, 0.5f);
midpoint = 0.5f;
softness = 0.001f;
break;
case "skin_sss":
lightColor = new Color(1f, 0.9f, 0.85f);
shadowColor = new Color(0.8f, 0.4f, 0.4f);
midpoint = 0.4f;
softness = 0.3f;
break;
case "metal":
lightColor = new Color(0.9f, 0.9f, 0.95f);
shadowColor = new Color(0.2f, 0.2f, 0.3f);
midpoint = 0.5f;
softness = 0.4f;
break;
case "fabric":
lightColor = new Color(1f, 1f, 1f);
shadowColor = new Color(0.5f, 0.5f, 0.6f);
midpoint = 0.55f;
softness = 0.25f;
break;
default:
lightColor = Color.white;
shadowColor = new Color(0.6f, 0.6f, 0.6f);
midpoint = 0.5f;
softness = 0.1f;
break;
}
// Generate ramp texture
var tex = new Texture2D(width, 4, TextureFormat.RGB24, false);
for (int x = 0; x < width; x++)
{
float t = (float)x / width;
// Smoothstep transition
float ramp = Mathf.SmoothStep(midpoint - softness, midpoint + softness, t);
Color color = Color.Lerp(shadowColor, lightColor, ramp);
for (int y = 0; y < 4; y++)
{
tex.SetPixel(x, y, color);
}
}
tex.Apply();
// Save texture
byte[] pngData = tex.EncodeToPNG();
string fullPath = $"{outputPath}/{outputName}_{preset}.png";
System.IO.File.WriteAllBytes(fullPath, pngData);
AssetDatabase.Refresh();
// Configure import settings
TextureImporter importer = AssetImporter.GetAtPath(fullPath) as TextureImporter;
if (importer != null)
{
importer.textureType = TextureImporterType.Default;
importer.wrapMode = TextureWrapMode.Clamp;
importer.filterMode = FilterMode.Bilinear;
importer.mipmapEnabled = false;
importer.SaveAndReimport();
}
UnityEngine.Object.DestroyImmediate(tex);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Generated ramp texture with preset '{preset}'",
path = fullPath,
preset = preset,
lightColor = $"({lightColor.r:F2}, {lightColor.g:F2}, {lightColor.b:F2})",
shadowColor = $"({shadowColor.r:F2}, {shadowColor.g:F2}, {shadowColor.b:F2})"
});
}
catch (Exception e)
{
return CreateErrorResponse("GenerateRampTexture", e, parameters);
}
}
/// <summary>
/// Add DissolveController component to a GameObject
/// </summary>
private string AddDissolveController(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target", "");
var direction = parameters.GetValueOrDefault("direction", "Up");
var duration = float.Parse(parameters.GetValueOrDefault("duration", "2"));
var autoReverse = parameters.GetValueOrDefault("autoReverse", "false") == "true";
if (string.IsNullOrEmpty(targetName))
{
return CreateMissingParameterResponse("AddDissolveController", "target", parameters);
}
var target = GameObject.Find(targetName);
if (target == null)
{
return CreateGameObjectNotFoundResponse("AddDissolveController", targetName, parameters);
}
// Add controller component
var controller = target.GetComponent<SynapticPro.DissolveController>();
if (controller == null)
{
controller = target.AddComponent<SynapticPro.DissolveController>();
}
// Configure
controller.animationDuration = duration;
controller.autoReverse = autoReverse;
// Parse direction
if (System.Enum.TryParse<SynapticPro.DissolveController.DissolveDirection>(direction, true, out var dirEnum))
{
controller.direction = dirEnum;
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Added DissolveController to '{targetName}'",
gameObject = targetName,
direction = direction,
duration = duration,
autoReverse = autoReverse
});
}
catch (Exception e)
{
return CreateErrorResponse("AddDissolveController", e, parameters);
}
}
/// <summary>
/// Add ShieldController component to a GameObject
/// </summary>
private string AddShieldController(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target", "");
var maxRipples = Convert.ToInt32(parameters.GetValueOrDefault("maxRipples", "4"));
var rippleDuration = float.Parse(parameters.GetValueOrDefault("rippleDuration", "1"));
var enableRegeneration = parameters.GetValueOrDefault("enableRegeneration", "true") == "true";
if (string.IsNullOrEmpty(targetName))
{
return CreateMissingParameterResponse("AddShieldController", "target", parameters);
}
var target = GameObject.Find(targetName);
if (target == null)
{
return CreateGameObjectNotFoundResponse("AddShieldController", targetName, parameters);
}
// Add controller component
var controller = target.GetComponent<SynapticPro.ShieldController>();
if (controller == null)
{
controller = target.AddComponent<SynapticPro.ShieldController>();
}
// Configure
controller.maxRipples = maxRipples;
controller.rippleDuration = rippleDuration;
controller.enableRegeneration = enableRegeneration;
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Added ShieldController to '{targetName}'",
gameObject = targetName,
maxRipples = maxRipples,
rippleDuration = rippleDuration,
enableRegeneration = enableRegeneration
});
}
catch (Exception e)
{
return CreateErrorResponse("AddShieldController", e, parameters);
}
}
/// <summary>
/// Add GrassRenderer component for GPU instanced grass
/// </summary>
private string AddGrassRenderer(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target", "");
var density = float.Parse(parameters.GetValueOrDefault("density", "1"));
var heightMax = float.Parse(parameters.GetValueOrDefault("heightMax", "1.5"));
var widthMax = float.Parse(parameters.GetValueOrDefault("widthMax", "0.5"));
var windStrength = float.Parse(parameters.GetValueOrDefault("windStrength", "0.5"));
GameObject target;
if (string.IsNullOrEmpty(targetName))
{
// Create new grass container
target = new GameObject("GrassRenderer");
}
else
{
target = GameObject.Find(targetName);
if (target == null)
{
target = new GameObject(targetName);
}
}
// Add GrassRenderer component
var renderer = target.GetComponent<SynapticPro.GrassRenderer>();
if (renderer == null)
{
renderer = target.AddComponent<SynapticPro.GrassRenderer>();
}
// Configure
renderer.density = density;
renderer.heightMax = heightMax;
renderer.widthMax = widthMax;
renderer.windStrength = windStrength;
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Added GrassRenderer to '{target.name}'",
gameObject = target.name,
density = density,
heightMax = heightMax,
widthMax = widthMax,
windStrength = windStrength,
note = "Assign terrain and grass material, then call Initialize() to start rendering"
});
}
catch (Exception e)
{
return CreateErrorResponse("AddGrassRenderer", e, parameters);
}
}
/// <summary>
/// Trigger dissolve effect on a GameObject
/// </summary>
private string TriggerDissolve(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target", "");
var action = parameters.GetValueOrDefault("action", "dissolve"); // dissolve, appear, toggle
if (string.IsNullOrEmpty(targetName))
{
return CreateMissingParameterResponse("TriggerDissolve", "target", parameters);
}
var target = GameObject.Find(targetName);
if (target == null)
{
return CreateGameObjectNotFoundResponse("TriggerDissolve", targetName, parameters);
}
var controller = target.GetComponent<SynapticPro.DissolveController>();
if (controller == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"'{targetName}' does not have a DissolveController component. Use ADD_DISSOLVE_CONTROLLER first."
});
}
switch (action.ToLower())
{
case "dissolve":
controller.Dissolve();
break;
case "appear":
controller.Appear();
break;
case "toggle":
controller.Toggle();
break;
default:
controller.Dissolve();
break;
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Triggered '{action}' on '{targetName}'",
gameObject = targetName,
action = action
});
}
catch (Exception e)
{
return CreateErrorResponse("TriggerDissolve", e, parameters);
}
}
/// <summary>
/// Trigger shield hit effect
/// </summary>
private string TriggerShieldHit(Dictionary<string, string> parameters)
{
try
{
var targetName = parameters.GetValueOrDefault("target", "");
var hitPositionStr = parameters.GetValueOrDefault("hitPosition", "0,0,0");
var damage = float.Parse(parameters.GetValueOrDefault("damage", "0.1"));
if (string.IsNullOrEmpty(targetName))
{
return CreateMissingParameterResponse("TriggerShieldHit", "target", parameters);
}
var target = GameObject.Find(targetName);
if (target == null)
{
return CreateGameObjectNotFoundResponse("TriggerShieldHit", targetName, parameters);
}
var controller = target.GetComponent<SynapticPro.ShieldController>();
if (controller == null)
{
return JsonConvert.SerializeObject(new
{
success = false,
error = $"'{targetName}' does not have a ShieldController component. Use ADD_SHIELD_CONTROLLER first."
});
}
Vector3 hitPosition = ParseVector3(hitPositionStr);
controller.OnHit(hitPosition, damage);
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Triggered shield hit on '{targetName}'",
gameObject = targetName,
hitPosition = hitPositionStr,
damage = damage,
currentDamageLevel = controller.damageLevel,
shieldActive = controller.shieldActive
});
}
catch (Exception e)
{
return CreateErrorResponse("TriggerShieldHit", e, parameters);
}
}
/// <summary>
/// Generate 3D cloud noise texture using compute shader
/// </summary>
private string GenerateCloudNoiseTexture(Dictionary<string, string> parameters)
{
try
{
var resolution = Convert.ToInt32(parameters.GetValueOrDefault("resolution", "128"));
var frequency = float.Parse(parameters.GetValueOrDefault("frequency", "4"));
var octaves = Convert.ToInt32(parameters.GetValueOrDefault("octaves", "4"));
var persistence = float.Parse(parameters.GetValueOrDefault("persistence", "0.5"));
var lacunarity = float.Parse(parameters.GetValueOrDefault("lacunarity", "2"));
var outputPath = parameters.GetValueOrDefault("outputPath", "Assets/Synaptic AI Pro/Textures/CloudNoise");
var outputName = parameters.GetValueOrDefault("outputName", "CloudNoise3D");
// Create output directory
if (!System.IO.Directory.Exists(outputPath))
{
System.IO.Directory.CreateDirectory(outputPath);
}
// Find compute shader
ComputeShader noiseCompute = null;
string[] guids = AssetDatabase.FindAssets("CloudNoise t:ComputeShader");
if (guids.Length > 0)
{
string path = AssetDatabase.GUIDToAssetPath(guids[0]);
noiseCompute = AssetDatabase.LoadAssetAtPath<ComputeShader>(path);
}
// Generate 3D texture using CPU (fallback if no compute shader)
Texture3D texture3D = new Texture3D(resolution, resolution, resolution, TextureFormat.RGBAHalf, false);
texture3D.wrapMode = TextureWrapMode.Repeat;
texture3D.filterMode = FilterMode.Trilinear;
Color[] colors = new Color[resolution * resolution * resolution];
for (int z = 0; z < resolution; z++)
{
for (int y = 0; y < resolution; y++)
{
for (int x = 0; x < resolution; x++)
{
float u = (float)x / resolution;
float v = (float)y / resolution;
float w = (float)z / resolution;
// Generate FBM noise
float noise = 0;
float amplitude = 0.5f;
float freq = frequency;
float maxValue = 0;
for (int i = 0; i < octaves; i++)
{
// Simple hash-based 3D noise
Vector3 p = new Vector3(u * freq, v * freq, w * freq);
float hash = Mathf.Sin(Vector3.Dot(p, new Vector3(127.1f, 311.7f, 74.7f))) * 43758.5453f;
hash = hash - Mathf.Floor(hash);
noise += hash * amplitude;
maxValue += amplitude;
amplitude *= persistence;
freq *= lacunarity;
}
noise /= maxValue;
// Worley-like detail (inverted for cloud shapes)
float detail = 1f - Mathf.Abs(noise * 2f - 1f);
colors[z * resolution * resolution + y * resolution + x] = new Color(noise, detail, noise * detail, 1);
}
}
}
texture3D.SetPixels(colors);
texture3D.Apply();
// Save as asset
string assetPath = $"{outputPath}/{outputName}.asset";
AssetDatabase.CreateAsset(texture3D, assetPath);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Generated 3D cloud noise texture",
path = assetPath,
resolution = $"{resolution}x{resolution}x{resolution}",
frequency = frequency,
octaves = octaves
});
}
catch (Exception e)
{
return CreateErrorResponse("GenerateCloudNoiseTexture", e, parameters);
}
}
#endregion
#region Pro Material Creation
private string CreateWaterMaterial(Dictionary<string, string> parameters)
{
try
{
string name = parameters.GetValueOrDefault("name", "WaterMaterial");
Shader shader = ShaderPipelineManager.FindShaderForCurrentPipeline("WaterPro");
if (shader == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Synaptic WaterPro shader not found for current pipeline" });
}
Material mat = new Material(shader);
mat.name = name;
// === Color and Depth ===
if (parameters.TryGetValue("shallowColor", out string shallowColor))
mat.SetColor("_ShallowColor", ParseHexColor(shallowColor));
if (parameters.TryGetValue("deepColor", out string deepColor))
mat.SetColor("_DeepColor", ParseHexColor(deepColor));
if (parameters.TryGetValue("horizonColor", out string horizonColor))
mat.SetColor("_HorizonColor", ParseHexColor(horizonColor));
if (parameters.TryGetValue("depthMaxDistance", out string depthMaxDistance))
mat.SetFloat("_DepthMaxDistance", float.Parse(depthMaxDistance));
if (parameters.TryGetValue("depthStrength", out string depthStrength))
mat.SetFloat("_DepthStrength", float.Parse(depthStrength));
// === Transparency and Absorption ===
if (parameters.TryGetValue("absorptionColor", out string absorptionColor))
mat.SetColor("_AbsorptionColor", ParseHexColor(absorptionColor));
if (parameters.TryGetValue("absorptionStrength", out string absorptionStrength))
mat.SetFloat("_AbsorptionStrength", float.Parse(absorptionStrength));
if (parameters.TryGetValue("clarityDistance", out string clarityDistance))
mat.SetFloat("_ClarityDistance", float.Parse(clarityDistance));
if (parameters.TryGetValue("underwaterFogDensity", out string underwaterFogDensity))
mat.SetFloat("_UnderwaterFogDensity", float.Parse(underwaterFogDensity));
if (parameters.TryGetValue("transparencyDepth", out string transparencyDepth))
mat.SetFloat("_TransparencyDepth", float.Parse(transparencyDepth));
// === Ocean Waves ===
if (parameters.TryGetValue("oceanScale", out string oceanScale))
mat.SetFloat("_OceanScale", float.Parse(oceanScale));
if (parameters.TryGetValue("waveSpeed", out string waveSpeed))
mat.SetFloat("_WaveSpeed", float.Parse(waveSpeed));
if (parameters.TryGetValue("waveHeight", out string waveHeight))
mat.SetFloat("_WaveHeight", float.Parse(waveHeight));
if (parameters.TryGetValue("choppiness", out string choppiness))
mat.SetFloat("_Choppiness", float.Parse(choppiness));
// === Extra Small Waves (I-L) ===
if (parameters.TryGetValue("waveI", out string waveI))
mat.SetVector("_WaveI", ParseVector4(waveI));
if (parameters.TryGetValue("waveJ", out string waveJ))
mat.SetVector("_WaveJ", ParseVector4(waveJ));
if (parameters.TryGetValue("waveK", out string waveK))
mat.SetVector("_WaveK", ParseVector4(waveK));
if (parameters.TryGetValue("waveL", out string waveL))
mat.SetVector("_WaveL", ParseVector4(waveL));
// === Micro Detail ===
if (parameters.TryGetValue("microWaveScale", out string microWaveScale))
mat.SetFloat("_MicroWaveScale", float.Parse(microWaveScale));
if (parameters.TryGetValue("microWaveStrength", out string microWaveStrength))
mat.SetFloat("_MicroWaveStrength", float.Parse(microWaveStrength));
// === Tessellation ===
if (parameters.TryGetValue("tessellationEnabled", out string tessellationEnabled))
mat.SetFloat("_TessEnabled", tessellationEnabled == "true" ? 1f : 0f);
if (parameters.TryGetValue("tessellationFactor", out string tessellationFactor))
mat.SetFloat("_TessellationFactor", float.Parse(tessellationFactor));
if (parameters.TryGetValue("tessellationMinDist", out string tessellationMinDist))
mat.SetFloat("_TessellationMinDist", float.Parse(tessellationMinDist));
if (parameters.TryGetValue("tessellationMaxDist", out string tessellationMaxDist))
mat.SetFloat("_TessellationMaxDist", float.Parse(tessellationMaxDist));
// === Foam ===
if (parameters.TryGetValue("foamColor", out string foamColor))
mat.SetColor("_FoamColor", ParseHexColor(foamColor));
if (parameters.TryGetValue("foamDistance", out string foamDistance))
mat.SetFloat("_FoamDistance", float.Parse(foamDistance));
if (parameters.TryGetValue("foamNoiseScale", out string foamNoiseScale))
mat.SetFloat("_FoamNoiseScale", float.Parse(foamNoiseScale));
if (parameters.TryGetValue("foamSpeed", out string foamSpeed))
mat.SetFloat("_FoamSpeed", float.Parse(foamSpeed));
if (parameters.TryGetValue("foamSharpness", out string foamSharpness))
mat.SetFloat("_FoamSharpness", float.Parse(foamSharpness));
if (parameters.TryGetValue("waveFoamThreshold", out string waveFoamThreshold))
mat.SetFloat("_WaveFoamThreshold", float.Parse(waveFoamThreshold));
if (parameters.TryGetValue("waveFoamSoftness", out string waveFoamSoftness))
mat.SetFloat("_WaveFoamSoftness", float.Parse(waveFoamSoftness));
if (parameters.TryGetValue("waveFoamEnabled", out string waveFoamEnabled))
mat.SetFloat("_WaveFoamEnabled", waveFoamEnabled == "true" ? 1f : 0f);
// === Caustics ===
if (parameters.TryGetValue("causticsEnabled", out string causticsEnabled))
mat.SetFloat("_CausticsEnabled", causticsEnabled == "true" ? 1f : 0f);
if (parameters.TryGetValue("causticsStrength", out string causticsStrength))
mat.SetFloat("_CausticsStrength", float.Parse(causticsStrength));
if (parameters.TryGetValue("causticsScale", out string causticsScale))
mat.SetFloat("_CausticsScale", float.Parse(causticsScale));
if (parameters.TryGetValue("causticsSpeed", out string causticsSpeed))
mat.SetFloat("_CausticsSpeed", float.Parse(causticsSpeed));
if (parameters.TryGetValue("causticsDepth", out string causticsDepth))
mat.SetFloat("_CausticsDepth", float.Parse(causticsDepth));
if (parameters.TryGetValue("causticsDistortion", out string causticsDistortion))
mat.SetFloat("_CausticsDistortion", float.Parse(causticsDistortion));
if (parameters.TryGetValue("causticsSplit", out string causticsSplit))
mat.SetFloat("_CausticsSplit", float.Parse(causticsSplit));
// === Specular (Physics-based Wave Reflection) ===
if (parameters.TryGetValue("specularColor", out string specularColor))
mat.SetColor("_SpecularColor", ParseHexColor(specularColor));
if (parameters.TryGetValue("smoothness", out string smoothness))
mat.SetFloat("_Smoothness", float.Parse(smoothness));
if (parameters.TryGetValue("specularIntensity", out string specularIntensity))
mat.SetFloat("_SpecularIntensity", float.Parse(specularIntensity));
// === Sun Disk Reflection ===
if (parameters.TryGetValue("sunDiskSize", out string sunDiskSize))
mat.SetFloat("_SunDiskSize", float.Parse(sunDiskSize));
if (parameters.TryGetValue("sunDiskIntensity", out string sunDiskIntensity))
mat.SetFloat("_SunDiskIntensity", float.Parse(sunDiskIntensity));
if (parameters.TryGetValue("sunDiskSharpness", out string sunDiskSharpness))
mat.SetFloat("_SunDiskSharpness", float.Parse(sunDiskSharpness));
// === Anisotropic Highlights ===
if (parameters.TryGetValue("anisotropyStrength", out string anisotropyStrength))
mat.SetFloat("_AnisotropyStrength", float.Parse(anisotropyStrength));
if (parameters.TryGetValue("anisotropyDirection", out string anisotropyDirection))
mat.SetFloat("_AnisotropyDirection", float.Parse(anisotropyDirection));
// === Micro Facet Glitter ===
if (parameters.TryGetValue("microFacetScale", out string microFacetScale))
mat.SetFloat("_MicroFacetScale", float.Parse(microFacetScale));
if (parameters.TryGetValue("microFacetIntensity", out string microFacetIntensity))
mat.SetFloat("_MicroFacetIntensity", float.Parse(microFacetIntensity));
if (parameters.TryGetValue("microFacetThreshold", out string microFacetThreshold))
mat.SetFloat("_MicroFacetThreshold", float.Parse(microFacetThreshold));
// === Refraction ===
if (parameters.TryGetValue("refractionStrength", out string refractionStrength))
mat.SetFloat("_RefractionStrength", float.Parse(refractionStrength));
if (parameters.TryGetValue("chromaticAberration", out string chromaticAberration))
mat.SetFloat("_ChromaticAberration", float.Parse(chromaticAberration));
// === Fresnel ===
if (parameters.TryGetValue("fresnelPower", out string fresnelPower))
mat.SetFloat("_FresnelPower", float.Parse(fresnelPower));
if (parameters.TryGetValue("fresnelBias", out string fresnelBias))
mat.SetFloat("_FresnelBias", float.Parse(fresnelBias));
// === SSS ===
if (parameters.TryGetValue("sssEnabled", out string sssEnabled))
mat.SetFloat("_SSSEnabled", sssEnabled == "true" ? 1f : 0f);
if (parameters.TryGetValue("sssColor", out string sssColor))
mat.SetColor("_SSSColor", ParseHexColor(sssColor));
if (parameters.TryGetValue("sssStrength", out string sssStrength))
mat.SetFloat("_SSSStrength", float.Parse(sssStrength));
// === Reflection ===
if (parameters.TryGetValue("reflectionStrength", out string reflectionStrength))
mat.SetFloat("_ReflectionStrength", float.Parse(reflectionStrength));
if (parameters.TryGetValue("ssrEnabled", out string ssrEnabled))
mat.SetFloat("_SSREnabled", ssrEnabled == "true" ? 1f : 0f);
// === Normal Maps ===
if (parameters.TryGetValue("normalStrength", out string normalStrength))
mat.SetFloat("_NormalStrength", float.Parse(normalStrength));
if (parameters.TryGetValue("normalScale1", out string normalScale1))
mat.SetFloat("_NormalScale1", float.Parse(normalScale1));
if (parameters.TryGetValue("normalScale2", out string normalScale2))
mat.SetFloat("_NormalScale2", float.Parse(normalScale2));
// === Surface Ripples (constant micro-movement) ===
if (parameters.TryGetValue("rippleScale1", out string rippleScale1))
mat.SetFloat("_RippleScale1", float.Parse(rippleScale1));
if (parameters.TryGetValue("rippleScale2", out string rippleScale2))
mat.SetFloat("_RippleScale2", float.Parse(rippleScale2));
if (parameters.TryGetValue("rippleScale3", out string rippleScale3))
mat.SetFloat("_RippleScale3", float.Parse(rippleScale3));
if (parameters.TryGetValue("rippleStrength", out string rippleStrength))
mat.SetFloat("_RippleStrength", float.Parse(rippleStrength));
if (parameters.TryGetValue("rippleSpeed", out string rippleSpeed))
mat.SetFloat("_RippleSpeed", float.Parse(rippleSpeed));
string path = $"Assets/Materials/{name}.mat";
EnsureDirectoryExists("Assets/Materials");
AssetDatabase.CreateAsset(mat, path);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new { success = true, message = $"Created water material: {name}", path = path });
}
catch (Exception e)
{
return CreateErrorResponse("CreateWaterMaterial", e, parameters);
}
}
private string CreateToonMaterial(Dictionary<string, string> parameters)
{
try
{
string name = parameters.GetValueOrDefault("name", "ToonMaterial");
Shader shader = ShaderPipelineManager.FindShaderForCurrentPipeline("ToonPro");
if (shader == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Synaptic ToonPro shader not found for current pipeline" });
}
Material mat = new Material(shader);
mat.name = name;
if (parameters.TryGetValue("baseColor", out string baseColor))
mat.SetColor("_Color", ParseHexColor(baseColor));
if (parameters.TryGetValue("shadowColor", out string shadowColor))
mat.SetColor("_ShadowColor", ParseHexColor(shadowColor));
if (parameters.TryGetValue("shadowThreshold", out string shadowThreshold))
mat.SetFloat("_ShadowThreshold", float.Parse(shadowThreshold));
if (parameters.TryGetValue("shadowSoftness", out string shadowSoftness))
mat.SetFloat("_ShadowSoftness", float.Parse(shadowSoftness));
if (parameters.TryGetValue("outlineColor", out string outlineColor))
mat.SetColor("_OutlineColor", ParseHexColor(outlineColor));
if (parameters.TryGetValue("outlineWidth", out string outlineWidth))
mat.SetFloat("_OutlineWidth", float.Parse(outlineWidth));
if (parameters.TryGetValue("specularSize", out string specularSize))
mat.SetFloat("_SpecularSize", float.Parse(specularSize));
if (parameters.TryGetValue("rimColor", out string rimColor))
mat.SetColor("_RimColor", ParseHexColor(rimColor));
if (parameters.TryGetValue("rimPower", out string rimPower))
mat.SetFloat("_RimPower", float.Parse(rimPower));
string path = $"Assets/Materials/{name}.mat";
EnsureDirectoryExists("Assets/Materials");
AssetDatabase.CreateAsset(mat, path);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new { success = true, message = $"Created toon material: {name}", path = path });
}
catch (Exception e)
{
return CreateErrorResponse("CreateToonMaterial", e, parameters);
}
}
private string SetMaterialProperty(Dictionary<string, string> parameters)
{
try
{
string materialPath = parameters.GetValueOrDefault("materialPath", "");
string propertyName = parameters.GetValueOrDefault("propertyName", "");
string propertyType = parameters.GetValueOrDefault("propertyType", "float");
string value = parameters.GetValueOrDefault("value", "");
if (string.IsNullOrEmpty(materialPath))
return JsonConvert.SerializeObject(new { success = false, error = "materialPath is required" });
Material mat = AssetDatabase.LoadAssetAtPath<Material>(materialPath);
if (mat == null)
return JsonConvert.SerializeObject(new { success = false, error = $"Material not found at: {materialPath}" });
if (string.IsNullOrEmpty(propertyName))
return JsonConvert.SerializeObject(new { success = false, error = "propertyName is required" });
// Add underscore if not present (common convention)
if (!propertyName.StartsWith("_"))
propertyName = "_" + propertyName;
Undo.RecordObject(mat, $"Set Material Property {propertyName}");
switch (propertyType.ToLower())
{
case "float":
float floatVal = float.Parse(value);
mat.SetFloat(propertyName, floatVal);
break;
case "color":
Color colorVal;
if (value.StartsWith("#"))
{
colorVal = ParseHexColor(value);
}
else
{
var parts = value.Split(',');
colorVal = new Color(
float.Parse(parts[0].Trim()),
float.Parse(parts[1].Trim()),
float.Parse(parts[2].Trim()),
parts.Length > 3 ? float.Parse(parts[3].Trim()) : 1f
);
}
mat.SetColor(propertyName, colorVal);
break;
case "vector":
var vecParts = value.Split(',');
Vector4 vecVal = new Vector4(
float.Parse(vecParts[0].Trim()),
float.Parse(vecParts[1].Trim()),
vecParts.Length > 2 ? float.Parse(vecParts[2].Trim()) : 0f,
vecParts.Length > 3 ? float.Parse(vecParts[3].Trim()) : 0f
);
mat.SetVector(propertyName, vecVal);
break;
case "texture":
Texture tex = AssetDatabase.LoadAssetAtPath<Texture>(value);
if (tex != null)
mat.SetTexture(propertyName, tex);
else
return JsonConvert.SerializeObject(new { success = false, error = $"Texture not found at: {value}" });
break;
default:
return JsonConvert.SerializeObject(new { success = false, error = $"Unknown property type: {propertyType}" });
}
EditorUtility.SetDirty(mat);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new {
success = true,
message = $"Set {propertyName} = {value} on {materialPath}",
materialPath = materialPath,
propertyName = propertyName,
value = value
});
}
catch (Exception e)
{
return CreateErrorResponse("SetMaterialProperty", e, parameters);
}
}
private string GetMaterialProperties(Dictionary<string, string> parameters)
{
try
{
string materialPath = parameters.GetValueOrDefault("materialPath", "");
if (string.IsNullOrEmpty(materialPath))
return JsonConvert.SerializeObject(new { success = false, error = "materialPath is required" });
Material mat = AssetDatabase.LoadAssetAtPath<Material>(materialPath);
if (mat == null)
return JsonConvert.SerializeObject(new { success = false, error = $"Material not found at: {materialPath}" });
Shader shader = mat.shader;
var properties = new List<object>();
int propertyCount = ShaderUtil.GetPropertyCount(shader);
for (int i = 0; i < propertyCount; i++)
{
string propName = ShaderUtil.GetPropertyName(shader, i);
ShaderUtil.ShaderPropertyType propType = ShaderUtil.GetPropertyType(shader, i);
string propDesc = ShaderUtil.GetPropertyDescription(shader, i);
object propValue = null;
string typeStr = "";
switch (propType)
{
case ShaderUtil.ShaderPropertyType.Float:
case ShaderUtil.ShaderPropertyType.Range:
propValue = mat.GetFloat(propName);
typeStr = "float";
break;
case ShaderUtil.ShaderPropertyType.Color:
Color c = mat.GetColor(propName);
propValue = $"#{ColorUtility.ToHtmlStringRGBA(c)}";
typeStr = "color";
break;
case ShaderUtil.ShaderPropertyType.Vector:
Vector4 v = mat.GetVector(propName);
propValue = $"{v.x},{v.y},{v.z},{v.w}";
typeStr = "vector";
break;
case ShaderUtil.ShaderPropertyType.TexEnv:
Texture t = mat.GetTexture(propName);
propValue = t != null ? AssetDatabase.GetAssetPath(t) : "null";
typeStr = "texture";
break;
}
properties.Add(new {
name = propName,
type = typeStr,
description = propDesc,
value = propValue
});
}
return JsonConvert.SerializeObject(new {
success = true,
materialPath = materialPath,
shaderName = shader.name,
propertyCount = propertyCount,
properties = properties
}, Formatting.Indented);
}
catch (Exception e)
{
return CreateErrorResponse("GetMaterialProperties", e, parameters);
}
}
private string CreateHDRPWater(Dictionary<string, string> parameters)
{
try
{
#if UNITY_2022_2_OR_NEWER && USING_HDRP
string name = parameters.GetValueOrDefault("name", "Water");
string waterType = parameters.GetValueOrDefault("waterType", "Ocean");
float size = float.Parse(parameters.GetValueOrDefault("size", "100"));
// Create water surface using HDRP Water System
// Note: This requires the HDRP Water package
var waterType = System.Type.GetType("UnityEngine.Rendering.HighDefinition.WaterSurface, Unity.RenderPipelines.HighDefinition.Runtime");
if (waterType == null)
{
return JsonConvert.SerializeObject(new {
success = false,
error = "HDRP Water System not found. Make sure you're using HDRP 2022.2+ and Water is enabled in HDRP settings."
});
}
GameObject waterGO = new GameObject(name);
var waterComponent = waterGO.AddComponent(waterType);
// Set water type via reflection
var surfaceTypeProperty = waterType.GetProperty("surfaceType");
if (surfaceTypeProperty != null)
{
// 0 = Ocean, 1 = River, 2 = Pool
int typeValue = waterType == "Ocean" ? 0 : (waterType == "River" ? 1 : 2);
surfaceTypeProperty.SetValue(waterComponent, typeValue);
}
// Apply other properties via reflection
if (parameters.TryGetValue("windSpeed", out string windSpeed))
{
var prop = waterType.GetProperty("windSpeed");
if (prop != null) prop.SetValue(waterComponent, float.Parse(windSpeed));
}
Undo.RegisterCreatedObjectUndo(waterGO, "Create HDRP Water");
return JsonConvert.SerializeObject(new {
success = true,
message = $"Created HDRP Water: {name} ({waterType})",
gameObject = name
});
#else
return JsonConvert.SerializeObject(new {
success = false,
error = "HDRP Water System requires Unity 2022.2+ with HDRP package. This project may not have HDRP configured.",
hint = "For URP/Built-in, use unity_create_water_material instead."
});
#endif
}
catch (Exception e)
{
return CreateErrorResponse("CreateHDRPWater", e, parameters);
}
}
private string SetHDRPWaterProperty(Dictionary<string, string> parameters)
{
try
{
#if UNITY_2022_2_OR_NEWER && USING_HDRP
string goName = parameters.GetValueOrDefault("gameObjectName", "");
string propName = parameters.GetValueOrDefault("propertyName", "");
string value = parameters.GetValueOrDefault("value", "");
GameObject waterGO = GameObject.Find(goName);
if (waterGO == null)
return JsonConvert.SerializeObject(new { success = false, error = $"GameObject '{goName}' not found" });
var waterType = System.Type.GetType("UnityEngine.Rendering.HighDefinition.WaterSurface, Unity.RenderPipelines.HighDefinition.Runtime");
var waterComponent = waterGO.GetComponent(waterType);
if (waterComponent == null)
return JsonConvert.SerializeObject(new { success = false, error = $"WaterSurface component not found on '{goName}'" });
var prop = waterType.GetProperty(propName);
if (prop == null)
return JsonConvert.SerializeObject(new { success = false, error = $"Property '{propName}' not found on WaterSurface" });
Undo.RecordObject(waterComponent as UnityEngine.Object, $"Set Water {propName}");
// Set value based on property type
if (prop.PropertyType == typeof(float))
prop.SetValue(waterComponent, float.Parse(value));
else if (prop.PropertyType == typeof(Color))
prop.SetValue(waterComponent, ParseHexColor(value));
else if (prop.PropertyType == typeof(bool))
prop.SetValue(waterComponent, bool.Parse(value));
return JsonConvert.SerializeObject(new {
success = true,
message = $"Set {propName} = {value} on {goName}"
});
#else
return JsonConvert.SerializeObject(new {
success = false,
error = "HDRP Water System requires Unity 2022.2+ with HDRP package.",
hint = "For URP/Built-in materials, use unity_set_material_property instead."
});
#endif
}
catch (Exception e)
{
return CreateErrorResponse("SetHDRPWaterProperty", e, parameters);
}
}
private string CreateHairMaterial(Dictionary<string, string> parameters)
{
try
{
string name = parameters.GetValueOrDefault("name", "HairMaterial");
Shader shader = ShaderPipelineManager.FindShaderForCurrentPipeline("HairPro");
if (shader == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Synaptic HairPro shader not found for current pipeline" });
}
Material mat = new Material(shader);
mat.name = name;
if (parameters.TryGetValue("hairColor", out string hairColor))
mat.SetColor("_Color", ParseHexColor(hairColor));
if (parameters.TryGetValue("specularColor1", out string specularColor1))
mat.SetColor("_SpecularColor1", ParseHexColor(specularColor1));
if (parameters.TryGetValue("specularColor2", out string specularColor2))
mat.SetColor("_SpecularColor2", ParseHexColor(specularColor2));
if (parameters.TryGetValue("specularShift", out string specularShift))
mat.SetFloat("_SpecularShift", float.Parse(specularShift));
if (parameters.TryGetValue("anisotropy", out string anisotropy))
mat.SetFloat("_Anisotropy", float.Parse(anisotropy));
if (parameters.TryGetValue("rimStrength", out string rimStrength))
mat.SetFloat("_RimStrength", float.Parse(rimStrength));
if (parameters.TryGetValue("shadowColor", out string shadowColor))
mat.SetColor("_ShadowColor", ParseHexColor(shadowColor));
string path = $"Assets/Materials/{name}.mat";
EnsureDirectoryExists("Assets/Materials");
AssetDatabase.CreateAsset(mat, path);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new { success = true, message = $"Created hair material: {name}", path = path });
}
catch (Exception e)
{
return CreateErrorResponse("CreateHairMaterial", e, parameters);
}
}
private string CreateEyeMaterial(Dictionary<string, string> parameters)
{
try
{
string name = parameters.GetValueOrDefault("name", "EyeMaterial");
Shader shader = ShaderPipelineManager.FindShaderForCurrentPipeline("EyePro");
if (shader == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Synaptic EyePro shader not found for current pipeline" });
}
Material mat = new Material(shader);
mat.name = name;
if (parameters.TryGetValue("irisColor", out string irisColor))
mat.SetColor("_IrisColor", ParseHexColor(irisColor));
if (parameters.TryGetValue("pupilSize", out string pupilSize))
mat.SetFloat("_PupilSize", float.Parse(pupilSize));
if (parameters.TryGetValue("irisDepth", out string irisDepth))
mat.SetFloat("_IrisDepth", float.Parse(irisDepth));
if (parameters.TryGetValue("scleraColor", out string scleraColor))
mat.SetColor("_ScleraColor", ParseHexColor(scleraColor));
if (parameters.TryGetValue("reflectionStrength", out string reflectionStrength))
mat.SetFloat("_ReflectionStrength", float.Parse(reflectionStrength));
if (parameters.TryGetValue("limbusWidth", out string limbusWidth))
mat.SetFloat("_LimbusWidth", float.Parse(limbusWidth));
if (parameters.TryGetValue("limbusColor", out string limbusColor))
mat.SetColor("_LimbusColor", ParseHexColor(limbusColor));
string path = $"Assets/Materials/{name}.mat";
EnsureDirectoryExists("Assets/Materials");
AssetDatabase.CreateAsset(mat, path);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new { success = true, message = $"Created eye material: {name}", path = path });
}
catch (Exception e)
{
return CreateErrorResponse("CreateEyeMaterial", e, parameters);
}
}
private string CreateSkyMaterial(Dictionary<string, string> parameters)
{
try
{
string name = parameters.GetValueOrDefault("name", "SkyMaterial");
Shader shader = ShaderPipelineManager.FindShaderForCurrentPipeline("SkyPro");
if (shader == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Synaptic SkyPro shader not found for current pipeline" });
}
Material mat = new Material(shader);
mat.name = name;
if (parameters.TryGetValue("topColor", out string topColor))
mat.SetColor("_TopColor", ParseHexColor(topColor));
if (parameters.TryGetValue("bottomColor", out string bottomColor))
mat.SetColor("_BottomColor", ParseHexColor(bottomColor));
if (parameters.TryGetValue("sunColor", out string sunColor))
mat.SetColor("_SunColor", ParseHexColor(sunColor));
if (parameters.TryGetValue("sunSize", out string sunSize))
mat.SetFloat("_SunSize", float.Parse(sunSize));
if (parameters.TryGetValue("cloudDensity", out string cloudDensity))
mat.SetFloat("_CloudDensity", float.Parse(cloudDensity));
if (parameters.TryGetValue("cloudSpeed", out string cloudSpeed))
mat.SetFloat("_CloudSpeed", float.Parse(cloudSpeed));
if (parameters.TryGetValue("starDensity", out string starDensity))
mat.SetFloat("_StarDensity", float.Parse(starDensity));
string path = $"Assets/Materials/{name}.mat";
EnsureDirectoryExists("Assets/Materials");
AssetDatabase.CreateAsset(mat, path);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new { success = true, message = $"Created sky material: {name}", path = path });
}
catch (Exception e)
{
return CreateErrorResponse("CreateSkyMaterial", e, parameters);
}
}
private string CreateDissolveMaterial(Dictionary<string, string> parameters)
{
try
{
string name = parameters.GetValueOrDefault("name", "DissolveMaterial");
Shader shader = ShaderPipelineManager.FindShaderForCurrentPipeline("DissolvePro");
if (shader == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Synaptic DissolvePro shader not found for current pipeline" });
}
Material mat = new Material(shader);
mat.name = name;
if (parameters.TryGetValue("baseColor", out string baseColor))
mat.SetColor("_Color", ParseHexColor(baseColor));
if (parameters.TryGetValue("edgeColor", out string edgeColor))
mat.SetColor("_EdgeColor", ParseHexColor(edgeColor));
if (parameters.TryGetValue("edgeWidth", out string edgeWidth))
mat.SetFloat("_EdgeWidth", float.Parse(edgeWidth));
if (parameters.TryGetValue("noiseScale", out string noiseScale))
mat.SetFloat("_NoiseScale", float.Parse(noiseScale));
if (parameters.TryGetValue("dissolveAmount", out string dissolveAmount))
mat.SetFloat("_DissolveAmount", float.Parse(dissolveAmount));
string path = $"Assets/Materials/{name}.mat";
EnsureDirectoryExists("Assets/Materials");
AssetDatabase.CreateAsset(mat, path);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new { success = true, message = $"Created dissolve material: {name}", path = path });
}
catch (Exception e)
{
return CreateErrorResponse("CreateDissolveMaterial", e, parameters);
}
}
private string CreateShieldMaterial(Dictionary<string, string> parameters)
{
try
{
string name = parameters.GetValueOrDefault("name", "ShieldMaterial");
Shader shader = ShaderPipelineManager.FindShaderForCurrentPipeline("ShieldPro");
if (shader == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Synaptic ShieldPro shader not found for current pipeline" });
}
Material mat = new Material(shader);
mat.name = name;
if (parameters.TryGetValue("shieldColor", out string shieldColor))
mat.SetColor("_ShieldColor", ParseHexColor(shieldColor));
if (parameters.TryGetValue("fresnelPower", out string fresnelPower))
mat.SetFloat("_FresnelPower", float.Parse(fresnelPower));
if (parameters.TryGetValue("patternScale", out string patternScale))
mat.SetFloat("_PatternScale", float.Parse(patternScale));
if (parameters.TryGetValue("pulseSpeed", out string pulseSpeed))
mat.SetFloat("_PulseSpeed", float.Parse(pulseSpeed));
if (parameters.TryGetValue("transparency", out string transparency))
mat.SetFloat("_Transparency", float.Parse(transparency));
string path = $"Assets/Materials/{name}.mat";
EnsureDirectoryExists("Assets/Materials");
AssetDatabase.CreateAsset(mat, path);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new { success = true, message = $"Created shield material: {name}", path = path });
}
catch (Exception e)
{
return CreateErrorResponse("CreateShieldMaterial", e, parameters);
}
}
private string CreateGrassMaterial(Dictionary<string, string> parameters)
{
try
{
string name = parameters.GetValueOrDefault("name", "GrassMaterial");
Shader shader = ShaderPipelineManager.FindShaderForCurrentPipeline("GrassPro");
if (shader == null)
{
return JsonConvert.SerializeObject(new { success = false, error = "Synaptic GrassPro shader not found for current pipeline" });
}
Material mat = new Material(shader);
mat.name = name;
if (parameters.TryGetValue("topColor", out string topColor))
mat.SetColor("_TopColor", ParseHexColor(topColor));
if (parameters.TryGetValue("bottomColor", out string bottomColor))
mat.SetColor("_BottomColor", ParseHexColor(bottomColor));
if (parameters.TryGetValue("windStrength", out string windStrength))
mat.SetFloat("_WindStrength", float.Parse(windStrength));
if (parameters.TryGetValue("windSpeed", out string windSpeed))
mat.SetFloat("_WindSpeed", float.Parse(windSpeed));
if (parameters.TryGetValue("subsurfaceColor", out string subsurfaceColor))
mat.SetColor("_SubsurfaceColor", ParseHexColor(subsurfaceColor));
string path = $"Assets/Materials/{name}.mat";
EnsureDirectoryExists("Assets/Materials");
AssetDatabase.CreateAsset(mat, path);
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new { success = true, message = $"Created grass material: {name}", path = path });
}
catch (Exception e)
{
return CreateErrorResponse("CreateGrassMaterial", e, parameters);
}
}
private string CreateOceanSystem(Dictionary<string, string> parameters)
{
try
{
string name = parameters.GetValueOrDefault("name", "Ocean");
float waterLevel = parameters.TryGetValue("waterLevel", out string wl) ? float.Parse(wl) : 0f;
int gridSize = parameters.TryGetValue("gridSize", out string gs) ? int.Parse(gs) : 128;
float tileSize = parameters.TryGetValue("tileSize", out string ts) ? float.Parse(ts) : 100f;
// Create ocean GameObject
GameObject oceanGO = new GameObject(name);
oceanGO.transform.position = new Vector3(0, waterLevel, 0);
// Add OceanSystem component
var oceanSystemType = System.Type.GetType("Synaptic.Water.OceanSystem, Assembly-CSharp");
if (oceanSystemType != null)
{
var ocean = oceanGO.AddComponent(oceanSystemType);
// Set properties via reflection
oceanSystemType.GetField("gridSize")?.SetValue(ocean, gridSize);
oceanSystemType.GetField("tileSize")?.SetValue(ocean, tileSize);
// Create water material (pipeline-aware)
Shader waterShader = ShaderPipelineManager.FindShaderForCurrentPipeline("WaterPro");
if (waterShader != null)
{
Material waterMat = new Material(waterShader);
waterMat.name = name + "_Material";
// Save material
EnsureDirectoryExists("Assets/Materials");
string matPath = $"Assets/Materials/{name}_Material.mat";
AssetDatabase.CreateAsset(waterMat, matPath);
// Assign material
oceanSystemType.GetField("oceanMaterial")?.SetValue(ocean, waterMat);
}
}
else
{
// Fallback: create simple plane with water material
var meshFilter = oceanGO.AddComponent<MeshFilter>();
var meshRenderer = oceanGO.AddComponent<MeshRenderer>();
// Create large plane mesh
meshFilter.sharedMesh = CreateOceanMesh(gridSize, tileSize * 6);
Shader waterShader = ShaderPipelineManager.FindShaderForCurrentPipeline("WaterPro");
if (waterShader != null)
{
Material waterMat = new Material(waterShader);
waterMat.name = name + "_Material";
EnsureDirectoryExists("Assets/Materials");
string matPath = $"Assets/Materials/{name}_Material.mat";
AssetDatabase.CreateAsset(waterMat, matPath);
meshRenderer.sharedMaterial = waterMat;
}
}
Undo.RegisterCreatedObjectUndo(oceanGO, "Create Ocean System");
Selection.activeGameObject = oceanGO;
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Created ocean system: {name}",
gameObject = name,
waterLevel = waterLevel
});
}
catch (Exception e)
{
return CreateErrorResponse("CreateOceanSystem", e, parameters);
}
}
private Mesh CreateOceanMesh(int resolution, float size)
{
Mesh mesh = new Mesh();
mesh.name = "OceanMesh";
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
int vertCount = (resolution + 1) * (resolution + 1);
Vector3[] vertices = new Vector3[vertCount];
Vector2[] uvs = new Vector2[vertCount];
Vector3[] normals = new Vector3[vertCount];
float halfSize = size * 0.5f;
float step = size / resolution;
for (int z = 0; z <= resolution; z++)
{
for (int x = 0; x <= resolution; x++)
{
int i = z * (resolution + 1) + x;
float xPos = x * step - halfSize;
float zPos = z * step - halfSize;
vertices[i] = new Vector3(xPos, 0, zPos);
uvs[i] = new Vector2((float)x / resolution, (float)z / resolution);
normals[i] = Vector3.up;
}
}
int[] triangles = new int[resolution * resolution * 6];
int t = 0;
for (int z = 0; z < resolution; z++)
{
for (int x = 0; x < resolution; x++)
{
int i = z * (resolution + 1) + x;
triangles[t++] = i;
triangles[t++] = i + resolution + 1;
triangles[t++] = i + 1;
triangles[t++] = i + 1;
triangles[t++] = i + resolution + 1;
triangles[t++] = i + resolution + 2;
}
}
mesh.vertices = vertices;
mesh.uv = uvs;
mesh.normals = normals;
mesh.triangles = triangles;
mesh.RecalculateBounds();
return mesh;
}
private string AddBuoyancy(Dictionary<string, string> parameters)
{
try
{
string targetName = parameters.GetValueOrDefault("target", "");
if (string.IsNullOrEmpty(targetName))
{
return JsonConvert.SerializeObject(new { success = false, error = "Target GameObject name required" });
}
GameObject target = GameObject.Find(targetName);
if (target == null)
{
return JsonConvert.SerializeObject(new { success = false, error = $"GameObject '{targetName}' not found" });
}
float buoyancyForce = parameters.TryGetValue("buoyancyForce", out string bf) ? float.Parse(bf) : 10f;
float waterDrag = parameters.TryGetValue("waterDrag", out string wd) ? float.Parse(wd) : 1f;
// Ensure Rigidbody exists
Rigidbody rb = target.GetComponent<Rigidbody>();
if (rb == null)
{
rb = target.AddComponent<Rigidbody>();
}
// Add Buoyancy component
var buoyancyType = System.Type.GetType("Synaptic.Water.Buoyancy, Assembly-CSharp");
if (buoyancyType != null)
{
var buoyancy = target.AddComponent(buoyancyType);
buoyancyType.GetField("buoyancyForce")?.SetValue(buoyancy, buoyancyForce);
buoyancyType.GetField("waterDrag")?.SetValue(buoyancy, waterDrag);
// Auto-find ocean
var oceanType = System.Type.GetType("Synaptic.Water.OceanSystem, Assembly-CSharp");
if (oceanType != null)
{
var ocean = UnityEngine.Object.FindFirstObjectByType(oceanType);
if (ocean != null)
{
buoyancyType.GetField("ocean")?.SetValue(buoyancy, ocean);
}
}
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Added buoyancy to {targetName}",
buoyancyForce = buoyancyForce,
waterDrag = waterDrag
});
}
else
{
return JsonConvert.SerializeObject(new
{
success = false,
error = "Buoyancy script not found. Make sure Synaptic.Water.Buoyancy exists in the project."
});
}
}
catch (Exception e)
{
return CreateErrorResponse("AddBuoyancy", e, parameters);
}
}
private string FixURPParticleShaders(Dictionary<string, string> parameters)
{
try
{
var fixedMaterials = new List<string>();
var particleShaders = new Dictionary<string, string>
{
{ "Particles/Standard Surface", "Universal Render Pipeline/Particles/Lit" },
{ "Particles/Standard Unlit", "Universal Render Pipeline/Particles/Unlit" },
{ "Particles/Additive", "Universal Render Pipeline/Particles/Unlit" },
{ "Particles/Additive (Soft)", "Universal Render Pipeline/Particles/Unlit" },
{ "Particles/Alpha Blended", "Universal Render Pipeline/Particles/Unlit" },
{ "Particles/Alpha Blended Premultiply", "Universal Render Pipeline/Particles/Unlit" },
{ "Particles/Multiply", "Universal Render Pipeline/Particles/Unlit" },
{ "Particles/Multiply (Double)", "Universal Render Pipeline/Particles/Unlit" },
{ "Legacy Shaders/Particles/Additive", "Universal Render Pipeline/Particles/Unlit" },
{ "Legacy Shaders/Particles/Alpha Blended", "Universal Render Pipeline/Particles/Unlit" },
{ "Mobile/Particles/Additive", "Universal Render Pipeline/Particles/Unlit" },
{ "Mobile/Particles/Alpha Blended", "Universal Render Pipeline/Particles/Unlit" },
};
string[] materialGuids = AssetDatabase.FindAssets("t:Material");
foreach (string guid in materialGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
Material mat = AssetDatabase.LoadAssetAtPath<Material>(path);
if (mat == null || mat.shader == null) continue;
string shaderName = mat.shader.name;
if (particleShaders.TryGetValue(shaderName, out string urpShader))
{
Shader newShader = Shader.Find(urpShader);
if (newShader != null)
{
// Preserve main texture and color
Texture mainTex = mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : null;
Color color = mat.HasProperty("_Color") ? mat.GetColor("_Color") :
mat.HasProperty("_TintColor") ? mat.GetColor("_TintColor") : Color.white;
mat.shader = newShader;
if (mainTex != null && mat.HasProperty("_BaseMap"))
mat.SetTexture("_BaseMap", mainTex);
if (mat.HasProperty("_BaseColor"))
mat.SetColor("_BaseColor", color);
// Set blend mode for additive
if (shaderName.Contains("Additive"))
{
mat.SetFloat("_Surface", 1); // Transparent
mat.SetFloat("_Blend", 1); // Additive
}
EditorUtility.SetDirty(mat);
fixedMaterials.Add(path);
}
}
}
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Fixed {fixedMaterials.Count} particle materials for URP",
fixedMaterials = fixedMaterials
});
}
catch (Exception e)
{
return CreateErrorResponse("FixURPParticleShaders", e, parameters);
}
}
private string FixPinkMaterials(Dictionary<string, string> parameters)
{
try
{
var fixedMaterials = new List<string>();
var failedMaterials = new List<string>();
// Common shader replacements for URP
var shaderReplacements = new Dictionary<string, string>
{
{ "Standard", "Universal Render Pipeline/Lit" },
{ "Standard (Specular setup)", "Universal Render Pipeline/Lit" },
{ "Particles/Standard Surface", "Universal Render Pipeline/Particles/Lit" },
{ "Particles/Standard Unlit", "Universal Render Pipeline/Particles/Unlit" },
{ "Legacy Shaders/Diffuse", "Universal Render Pipeline/Simple Lit" },
{ "Legacy Shaders/Specular", "Universal Render Pipeline/Simple Lit" },
{ "Legacy Shaders/Bumped Diffuse", "Universal Render Pipeline/Simple Lit" },
{ "Legacy Shaders/Bumped Specular", "Universal Render Pipeline/Simple Lit" },
{ "Legacy Shaders/Transparent/Diffuse", "Universal Render Pipeline/Lit" },
{ "Mobile/Diffuse", "Universal Render Pipeline/Simple Lit" },
{ "Unlit/Texture", "Universal Render Pipeline/Unlit" },
{ "Unlit/Color", "Universal Render Pipeline/Unlit" },
{ "Unlit/Transparent", "Universal Render Pipeline/Unlit" },
};
string[] materialGuids = AssetDatabase.FindAssets("t:Material");
foreach (string guid in materialGuids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
if (path.Contains("Packages/")) continue; // Skip packages
Material mat = AssetDatabase.LoadAssetAtPath<Material>(path);
if (mat == null) continue;
// Check if shader is missing (pink/magenta)
bool isBroken = mat.shader == null ||
mat.shader.name == "Hidden/InternalErrorShader" ||
mat.shader.name.Contains("Error");
if (!isBroken && mat.shader != null)
{
// Check if it's a built-in shader in URP project
string shaderName = mat.shader.name;
if (shaderReplacements.ContainsKey(shaderName))
{
isBroken = true;
}
}
if (isBroken && mat.shader != null)
{
string oldShaderName = mat.shader.name;
// Try to find replacement
if (shaderReplacements.TryGetValue(oldShaderName, out string urpShader))
{
Shader newShader = Shader.Find(urpShader);
if (newShader != null)
{
// Preserve textures
Texture mainTex = mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : null;
Texture bumpMap = mat.HasProperty("_BumpMap") ? mat.GetTexture("_BumpMap") : null;
Color color = mat.HasProperty("_Color") ? mat.GetColor("_Color") : Color.white;
mat.shader = newShader;
// Remap properties
if (mainTex != null)
{
if (mat.HasProperty("_BaseMap")) mat.SetTexture("_BaseMap", mainTex);
else if (mat.HasProperty("_MainTex")) mat.SetTexture("_MainTex", mainTex);
}
if (bumpMap != null && mat.HasProperty("_BumpMap"))
mat.SetTexture("_BumpMap", bumpMap);
if (mat.HasProperty("_BaseColor"))
mat.SetColor("_BaseColor", color);
EditorUtility.SetDirty(mat);
fixedMaterials.Add($"{path} ({oldShaderName} -> {urpShader})");
}
else
{
failedMaterials.Add($"{path} (URP shader not found: {urpShader})");
}
}
else
{
failedMaterials.Add($"{path} (Unknown shader: {oldShaderName})");
}
}
}
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new
{
success = true,
message = $"Fixed {fixedMaterials.Count} pink/broken materials",
fixedMaterials = fixedMaterials,
failedMaterials = failedMaterials
});
}
catch (Exception e)
{
return CreateErrorResponse("FixPinkMaterials", e, parameters);
}
}
private Color ParseHexColor(string hex)
{
if (string.IsNullOrEmpty(hex)) return Color.white;
hex = hex.TrimStart('#');
if (hex.Length == 6)
{
byte r = Convert.ToByte(hex.Substring(0, 2), 16);
byte g = Convert.ToByte(hex.Substring(2, 2), 16);
byte b = Convert.ToByte(hex.Substring(4, 2), 16);
return new Color32(r, g, b, 255);
}
return Color.white;
}
private void EnsureDirectoryExists(string path)
{
if (!AssetDatabase.IsValidFolder(path))
{
string[] folders = path.Split('/');
string currentPath = folders[0];
for (int i = 1; i < folders.Length; i++)
{
string newPath = currentPath + "/" + folders[i];
if (!AssetDatabase.IsValidFolder(newPath))
{
AssetDatabase.CreateFolder(currentPath, folders[i]);
}
currentPath = newPath;
}
}
}
#endregion
#endregion
}
}