From 458e6a094df6cc10af4f0d0ceca8a1e54c0f0101 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 21:23:54 +0900 Subject: [PATCH 01/12] Draw raycast annotations as reachable outlines Replace large sampled-cell bounding boxes in the screenshot overlay with outlines built from reachable raycast sample cells. Keep the JSON bounds as a compatibility bbox while preserving SimX/SimY as the click-ready sampled point. Add a perspective raycast annotation demo scene so angled placement areas and UI blockers can be visually checked. --- ...aycastAnnotationPerspectiveDemoScene.unity | 2333 +++++++++++++++++ ...tAnnotationPerspectiveDemoScene.unity.meta | 7 + .../Tests/Editor/RaycastGridAnnotatorTests.cs | 196 +- .../Tests/Editor/UIElementAnnotatorTests.cs | 14 + .../Api/McpTools/Screenshot/Skill/SKILL.md | 6 +- .../Skill/references/annotated-elements.md | 4 +- .../Api/McpTools/Screenshot/UIElementInfo.cs | 3 + .../src/Editor/Utils/RaycastGridAnnotator.cs | 29 +- .../src/Editor/Utils/RaycastHitClusterer.cs | 2 + .../Utils/RaycastSampleOutlineBuilder.cs | 283 ++ .../Utils/RaycastSampleOutlineBuilder.cs.meta | 11 + .../src/Editor/Utils/UIElementAnnotator.cs | 149 +- 12 files changed, 3016 insertions(+), 21 deletions(-) create mode 100644 Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity create mode 100644 Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity.meta create mode 100644 Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs create mode 100644 Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs.meta diff --git a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity new file mode 100644 index 0000000000..8a5c84eccd --- /dev/null +++ b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity @@ -0,0 +1,2333 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &32857930 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 32857931} + - component: {fileID: 32857933} + - component: {fileID: 32857932} + m_Layer: 0 + m_Name: Blocker Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &32857931 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 32857930} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 344266733} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 380, y: 80} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &32857932 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 32857930} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0, g: 0, b: 0, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 26 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'GraphicRaycaster UI blocker + + covered cells should be omitted' +--- !u!222 &32857933 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 32857930} + m_CullTransparentMesh: 1 +--- !u!1 &84949467 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 84949471} + - component: {fileID: 84949470} + - component: {fileID: 84949469} + - component: {fileID: 84949468} + m_Layer: 0 + m_Name: Perspective GraphicRaycaster UI Blocker Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &84949468 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 84949467} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!114 &84949469 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 84949467} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!223 &84949470 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 84949467} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 20 + m_TargetDisplay: 0 +--- !u!224 &84949471 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 84949467} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 344266733} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &114960129 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 114960130} + - component: {fileID: 114960132} + - component: {fileID: 114960131} + m_Layer: 0 + m_Name: PerspectiveSplitGrid_VisualCell_3_1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &114960130 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 114960129} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 2.85, y: 0.08, z: 0.95} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1940938438} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &114960131 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 114960129} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &114960132 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 114960129} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &219972445 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 219972446} + - component: {fileID: 219972448} + - component: {fileID: 219972447} + m_Layer: 0 + m_Name: PerspectiveSplitGrid_VisualCell_4_1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &219972446 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 219972445} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 3.8, y: 0.08, z: 0.95} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1940938438} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &219972447 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 219972445} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &219972448 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 219972445} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &344266732 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 344266733} + - component: {fileID: 344266735} + - component: {fileID: 344266734} + m_Layer: 0 + m_Name: Center Raycast Blocker + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &344266733 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 344266732} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 32857931} + m_Father: {fileID: 84949471} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 70, y: -20} + m_SizeDelta: {x: 420, y: 260} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &344266734 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 344266732} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8, g: 0.9, b: 1, a: 0.42} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &344266735 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 344266732} + m_CullTransparentMesh: 1 +--- !u!1 &612736413 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 612736414} + - component: {fileID: 612736416} + - component: {fileID: 612736415} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_1_2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &612736414 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 612736413} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.95, y: 0.08, z: 1.9} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &612736415 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 612736413} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &612736416 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 612736413} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &668389980 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 668389981} + - component: {fileID: 668389983} + - component: {fileID: 668389982} + m_Layer: 0 + m_Name: PerspectiveSplitGrid_VisualCell_1_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &668389981 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 668389980} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.95, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1940938438} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &668389982 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 668389980} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &668389983 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 668389980} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!21 &703760602 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Demo Reachable Placement Green + m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 0.1, g: 1, b: 0.28, a: 0.82} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] +--- !u!1 &750890959 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 750890961} + - component: {fileID: 750890960} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!20 &750890960 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 750890959} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.06, g: 0.07, b: 0.08, a: 1} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.1 + far clip plane: 100 + field of view: 43 + orthographic: 0 + orthographic size: 5 + m_Depth: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &750890961 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 750890959} + serializedVersion: 2 + m_LocalRotation: {x: 0.23793738, y: -0.32292682, z: 0.084237, w: 0.9121449} + m_LocalPosition: {x: 6.8, y: 6.2, z: -8.4} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &901106301 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 901106304} + - component: {fileID: 901106303} + - component: {fileID: 901106302} + m_Layer: 0 + m_Name: Dark Perspective Platform + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &901106302 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 901106301} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2058516905} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &901106303 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 901106301} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &901106304 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 901106301} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.08, z: 0} + m_LocalScale: {x: 9, y: 0.1, z: 8} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &902889315 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 902889316} + - component: {fileID: 902889318} + - component: {fileID: 902889317} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_0_2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &902889316 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 902889315} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.08, z: 1.9} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &902889317 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 902889315} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &902889318 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 902889315} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &909518690 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 909518691} + - component: {fileID: 909518693} + - component: {fileID: 909518692} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_0_1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &909518691 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 909518690} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.08, z: 0.95} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &909518692 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 909518690} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &909518693 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 909518690} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!21 &1080945992 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Demo Visual Foreground Blocker + m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 0.62, g: 0.67, b: 0.72, a: 1} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] +--- !u!1 &1199636759 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1199636760} + - component: {fileID: 1199636762} + - component: {fileID: 1199636761} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_2_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1199636760 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1199636759} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.9, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &1199636761 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1199636759} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1199636762 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1199636759} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &1241248864 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1241248866} + - component: {fileID: 1241248865} + m_Layer: 0 + m_Name: Key Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &1241248865 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1241248864} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 1.35 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 0 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &1241248866 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1241248864} + serializedVersion: 2 + m_LocalRotation: {x: 0.34016195, y: -0.33200905, z: 0.25709796, w: 0.8414039} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1320790928 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1320790929} + - component: {fileID: 1320790931} + - component: {fileID: 1320790930} + m_Layer: 0 + m_Name: PerspectiveSplitGrid_VisualCell_0_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1320790929 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1320790928} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1940938438} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &1320790930 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1320790928} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1320790931 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1320790928} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &1331696911 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1331696912} + - component: {fileID: 1331696914} + - component: {fileID: 1331696913} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_1_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1331696912 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1331696911} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.95, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &1331696913 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1331696911} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1331696914 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1331696911} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &1808396404 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1808396408} + - component: {fileID: 1808396407} + - component: {fileID: 1808396406} + - component: {fileID: 1808396405} + m_Layer: 2 + m_Name: Foreground Visual Blocker IgnoreRaycast + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &1808396405 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1808396404} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 1080945992} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &1808396406 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1808396404} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!33 &1808396407 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1808396404} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1808396408 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1808396404} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: -0.15643448, z: 0, w: 0.98768836} + m_LocalPosition: {x: 0.5, y: 0.9, z: -1.25} + m_LocalScale: {x: 4.1, y: 1.8, z: 0.22} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1863570092 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1863570095} + - component: {fileID: 1863570094} + - component: {fileID: 1863570093} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1863570093 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1863570092} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1863570094 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1863570092} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &1863570095 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1863570092} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1940938437 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1940938438} + - component: {fileID: 1940938442} + - component: {fileID: 1940938441} + - component: {fileID: 1940938440} + - component: {fileID: 1940938439} + m_Layer: 0 + m_Name: PerspectiveSplitGrid + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1940938438 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0.2079117, z: 0, w: 0.9781476} + m_LocalPosition: {x: 2.6, y: 0, z: 1.2} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1320790929} + - {fileID: 668389981} + - {fileID: 114960130} + - {fileID: 219972446} + m_Father: {fileID: 2095818975} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &1940938439 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 3.8, y: 0.08, z: 0.95} +--- !u!65 &1940938440 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 2.85, y: 0.08, z: 0.95} +--- !u!65 &1940938441 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0.95, y: 0.08, z: 0} +--- !u!65 &1940938442 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0, y: 0.08, z: 0} +--- !u!1 &2054415773 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2054415774} + - component: {fileID: 2054415780} + - component: {fileID: 2054415779} + - component: {fileID: 2054415778} + - component: {fileID: 2054415777} + - component: {fileID: 2054415776} + - component: {fileID: 2054415775} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2054415774 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: -0.26723838, z: 0, w: 0.96363044} + m_LocalPosition: {x: -1.5, y: 0, z: -0.4} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2105131112} + - {fileID: 1331696912} + - {fileID: 1199636760} + - {fileID: 909518691} + - {fileID: 902889316} + - {fileID: 612736414} + m_Father: {fileID: 2095818975} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &2054415775 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0.95, y: 0.08, z: 1.9} +--- !u!65 &2054415776 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0, y: 0.08, z: 1.9} +--- !u!65 &2054415777 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0, y: 0.08, z: 0.95} +--- !u!65 &2054415778 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 1.9, y: 0.08, z: 0} +--- !u!65 &2054415779 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0.95, y: 0.08, z: 0} +--- !u!65 &2054415780 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0, y: 0.08, z: 0} +--- !u!21 &2058516905 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Demo Dark Platform + m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 0.08, g: 0.09, b: 0.1, a: 1} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] +--- !u!1 &2095818974 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2095818975} + m_Layer: 0 + m_Name: Perspective Placement Areas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2095818975 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2095818974} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2054415774} + - {fileID: 1940938438} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2105131111 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2105131112} + - component: {fileID: 2105131114} + - component: {fileID: 2105131113} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_0_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2105131112 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2105131111} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &2105131113 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2105131111} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &2105131114 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2105131111} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 750890961} + - {fileID: 1241248866} + - {fileID: 901106304} + - {fileID: 2095818975} + - {fileID: 1808396408} + - {fileID: 1863570095} + - {fileID: 84949471} diff --git a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity.meta b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity.meta new file mode 100644 index 0000000000..feeb11033b --- /dev/null +++ b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1c9d7b93a4ba34245987ea17f02d8f60 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/Editor/RaycastGridAnnotatorTests.cs b/Assets/Tests/Editor/RaycastGridAnnotatorTests.cs index 371f1c9195..0a1f9bbcab 100644 --- a/Assets/Tests/Editor/RaycastGridAnnotatorTests.cs +++ b/Assets/Tests/Editor/RaycastGridAnnotatorTests.cs @@ -137,6 +137,26 @@ public void CreateClusters_WhenSamplesHitDifferentColliders_ShouldReturnClusterP Assert.That(clusters[1].Representative.ClusterKey, Is.EqualTo(2)); } + [Test] + public void CreateClusterKey_WhenGameObjectHasMultipleColliders_ShouldGroupByGameObject() + { + GameObject gameObject = new GameObject("MultiColliderPlacementArea"); + try + { + BoxCollider boxCollider = gameObject.AddComponent(); + SphereCollider sphereCollider = gameObject.AddComponent(); + + int boxClusterKey = RaycastGridAnnotator.CreateClusterKey(boxCollider); + int sphereClusterKey = RaycastGridAnnotator.CreateClusterKey(sphereCollider); + + Assert.That(boxClusterKey, Is.EqualTo(sphereClusterKey)); + } + finally + { + Object.DestroyImmediate(gameObject); + } + } + [Test] public void CreateClusters_WhenSamplesFormLShape_ShouldChooseActualHitClosestToCentroid() { @@ -361,6 +381,137 @@ public void CreatePhysicsColliderElement_WhenClusterHasSingleSample_ShouldUseOne Assert.That(element.BoundsMaxY, Is.EqualTo(210f)); } + [Test] + public void CreateOutlineSegments_WhenSingleCell_ShouldReturnFourEdges() + { + List samples = new List + { + CreateSample(1, 10f, 20f, 1, 1) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 10f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(4)); + AssertSegment(segments[0], 5f, 10f, 15f, 10f); + AssertSegment(segments[1], 5f, 30f, 15f, 30f); + AssertSegment(segments[2], 5f, 10f, 5f, 30f); + AssertSegment(segments[3], 15f, 10f, 15f, 30f); + } + + [Test] + public void CreateOutlineSegments_WhenCellsAreAdjacent_ShouldMergeSharedEdges() + { + List samples = new List + { + CreateSample(1, 10f, 20f, 1, 1), + CreateSample(1, 20f, 20f, 1, 2) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 10f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(4)); + AssertSegment(segments[0], 5f, 10f, 25f, 10f); + AssertSegment(segments[1], 5f, 30f, 25f, 30f); + AssertSegment(segments[2], 5f, 10f, 5f, 30f); + AssertSegment(segments[3], 25f, 10f, 25f, 30f); + } + + [Test] + public void CreateOutlineSegments_WhenCellsFormLShape_ShouldKeepConcaveOutline() + { + List samples = new List + { + CreateSample(1, 10f, 10f, 1, 1), + CreateSample(1, 20f, 10f, 1, 2), + CreateSample(1, 10f, 20f, 2, 1) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 5f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(6)); + AssertSegment(segments[0], 5f, 5f, 25f, 5f); + AssertSegment(segments[1], 15f, 15f, 25f, 15f); + AssertSegment(segments[2], 5f, 25f, 15f, 25f); + AssertSegment(segments[3], 5f, 5f, 5f, 25f); + AssertSegment(segments[4], 15f, 15f, 15f, 25f); + AssertSegment(segments[5], 25f, 5f, 25f, 15f); + } + + [Test] + public void CreateOutlineSegments_WhenCellsHaveHole_ShouldReturnInnerAndOuterEdges() + { + List samples = new List + { + CreateSample(1, 10f, 10f, 1, 1), + CreateSample(1, 20f, 10f, 1, 2), + CreateSample(1, 30f, 10f, 1, 3), + CreateSample(1, 10f, 20f, 2, 1), + CreateSample(1, 30f, 20f, 2, 3), + CreateSample(1, 10f, 30f, 3, 1), + CreateSample(1, 20f, 30f, 3, 2), + CreateSample(1, 30f, 30f, 3, 3) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 5f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(8)); + Assert.That(ContainsSegment(segments, 15f, 15f, 25f, 15f), Is.True); + Assert.That(ContainsSegment(segments, 15f, 25f, 25f, 25f), Is.True); + Assert.That(ContainsSegment(segments, 15f, 15f, 15f, 25f), Is.True); + Assert.That(ContainsSegment(segments, 25f, 15f, 25f, 25f), Is.True); + } + + [Test] + public void CreateOutlineSegments_WhenCellsAreDisconnected_ShouldKeepSeparateComponents() + { + List samples = new List + { + CreateSample(1, 10f, 10f, 1, 1), + CreateSample(1, 30f, 10f, 1, 3) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 5f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(8)); + Assert.That(ContainsSegment(segments, 5f, 5f, 15f, 5f), Is.True); + Assert.That(ContainsSegment(segments, 25f, 5f, 35f, 5f), Is.True); + } + + [Test] + public void CreatePhysicsColliderElement_WhenSamplesHaveGridCells_ShouldAttachOutlineSegments() + { + RaycastClusterInfo cluster = new RaycastClusterInfo + { + Representative = CreateSample(1, 10f, 10f, 1, 1), + SampleCount = 2, + Samples = new List + { + CreateSample(1, 10f, 10f, 1, 1), + CreateSample(1, 20f, 10f, 1, 2) + } + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 5f, 0f, 0f, 100f, 100f); + + UIElementInfo element = RaycastGridAnnotator.CreatePhysicsColliderElement( + "R1", + cluster, + CreateMetadata(), + coverage); + + Assert.That(element.RaycastOutlineSegments.Count, Is.EqualTo(4)); + AssertSegment(element.RaycastOutlineSegments[0], 5f, 5f, 25f, 5f); + } + [Test] public void IsUiOcclusionRaycastResult_WhenGraphicRaycasterHit_ShouldReturnTrue() { @@ -492,16 +643,57 @@ public void CreateLayerSummaries_WhenNoHits_ShouldReturnEmptyList() Assert.That(summaries, Is.Empty); } - private static RaycastClusterSample CreateSample(int clusterKey, float inputX, float inputY) + private static RaycastClusterSample CreateSample( + int clusterKey, + float inputX, + float inputY, + int row = 0, + int column = 0) { return new RaycastClusterSample { ClusterKey = clusterKey, InputX = inputX, - InputY = inputY + InputY = inputY, + Row = row, + Column = column }; } + private static bool ContainsSegment( + List segments, + float startX, + float startY, + float endX, + float endY) + { + foreach (RaycastOutlineSegment segment in segments) + { + if (segment.StartX == startX && + segment.StartY == startY && + segment.EndX == endX && + segment.EndY == endY) + { + return true; + } + } + + return false; + } + + private static void AssertSegment( + RaycastOutlineSegment segment, + float startX, + float startY, + float endX, + float endY) + { + Assert.That(segment.StartX, Is.EqualTo(startX)); + Assert.That(segment.StartY, Is.EqualTo(startY)); + Assert.That(segment.EndX, Is.EqualTo(endX)); + Assert.That(segment.EndY, Is.EqualTo(endY)); + } + private static RaycastGridPointInfo CreateLayerHitPoint( string layer, int layerIndex, diff --git a/Assets/Tests/Editor/UIElementAnnotatorTests.cs b/Assets/Tests/Editor/UIElementAnnotatorTests.cs index 7a3cf20bdc..9b4072e40a 100644 --- a/Assets/Tests/Editor/UIElementAnnotatorTests.cs +++ b/Assets/Tests/Editor/UIElementAnnotatorTests.cs @@ -126,6 +126,20 @@ public void CalculateBorderEdgeRects_WhenBoundsAreProvided_ShouldPlaceEdgesInsid Assert.That(edgeRects.Right, Is.EqualTo(new Rect(108f, 22f, 2f, 46f))); } + [Test] + public void ConvertTopLeftOutlineSegmentToScreenSegment_ShouldFlipYForCanvasSpace() + { + RaycastOutlineSegment inputSegment = new RaycastOutlineSegment(10f, 20f, 30f, 20f); + + RaycastOutlineSegment screenSegment = + UIElementAnnotator.ConvertTopLeftOutlineSegmentToScreenSegment(inputSegment, 100f); + + Assert.That(screenSegment.StartX, Is.EqualTo(10f)); + Assert.That(screenSegment.StartY, Is.EqualTo(80f)); + Assert.That(screenSegment.EndX, Is.EqualTo(30f)); + Assert.That(screenSegment.EndY, Is.EqualTo(80f)); + } + [Test] public void GetAnnotationBorderColors_WhenAnnotationColorIsProvided_ShouldPutAnnotationColorInTheMiddle() { diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md index cfea5d46a3..cb277b21be 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md +++ b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md @@ -118,7 +118,7 @@ When multiple windows match (e.g., multiple Inspector windows or when using `con - Use `ScreenshotToInputFormula` before passing raw image pixels to mouse tools. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates. - To discover a useful physics layer, first run `--annotate-raycast-grid` without `--raycast-layer-mask`, inspect `RaycastLayerSummaries`, then rerun with `--raycast-layer-mask `. - Use `--raycast-layer-mask` with layer names from `find-game-objects` or project code when the game input code raycasts only specific layers. Layers hidden by Camera.main.cullingMask are not reported because they are not visible to the screenshot camera. -- Clustered `PhysicsCollider` entries avoid points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element, including world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. `BoundsMinX/Y` and `BoundsMaxX/Y` show the axis-aligned sampled-cell coverage bbox from reachable hits; use `SimX/SimY` for the actual click point. If every sampled hit in the cluster is covered, that collider is omitted. +- Clustered `PhysicsCollider` entries avoid points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element, including world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. The screenshot overlay draws the reachable sampled-cell outline, while `BoundsMinX/Y` and `BoundsMaxX/Y` remain the axis-aligned sampled-cell bbox for JSON consumers. Use `SimX/SimY` for the actual click point. If every sampled hit in the cluster is covered, that collider is omitted. ### Raycast Annotation Demo Scene @@ -128,5 +128,7 @@ Use `Assets/Scenes/RaycastAnnotationDemoScene.unity` as the maintained visual fi uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 ``` -The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile frames should shrink around the blocker, frames should wrap the reachable sampled-cell bbox, and `SimX/SimY` should stay inside each frame. +The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile outlines should shrink around the blocker, outlines should follow reachable sampled cells, and `SimX/SimY` should stay inside `BoundsMinX/Y` and `BoundsMaxX/Y`. + +Use `Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity` as the perspective visual fixture. It contains rotated multi-collider placement areas, separated cell groups, a visual foreground blocker, and a center `GraphicRaycaster` UI blocker. Enter PlayMode, then run the same command above. Verify the outline follows the visible angled placement areas without reverting to a large axis-aligned rectangle. - Do not use `window` captures as mouse-input coordinates because they include Unity Editor chrome. diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md index da1083ff51..d4fe2fffc6 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md +++ b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md @@ -44,9 +44,9 @@ For `PhysicsCollider` entries, `SimX/SimY` is a real sampled raycast hit nearest `--raycast-layer-mask` filters by the requested physics layers and Camera.main.cullingMask. A layer that is requested but hidden from the active camera is treated as not visible and will not produce `PhysicsCollider` entries. -For clustered `PhysicsCollider` entries, points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element are treated as covered by UI. This includes world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. Bounds and `SimX/SimY` are both derived from the remaining reachable samples; if every sampled hit in that collider cluster is covered, the collider is omitted from `AnnotatedElements`. +For clustered `PhysicsCollider` entries, points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element are treated as covered by UI. This includes world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. Bounds, screenshot outlines, and `SimX/SimY` are derived from the remaining reachable samples; if every sampled hit in that collider cluster is covered, the collider is omitted from `AnnotatedElements`. -`PhysicsCollider` bounds expand each reachable sample by half the dense raycast sampling step in X and Y, then clamp the result to the captured Game View area. This makes the frame approximate the covered raycast cells instead of shrinking to the sample centers. It may extend up to half a sample step past the visible collider edge, and it still does not guarantee that every interior point is clickable. +`PhysicsCollider` bounds expand each reachable sample by half the dense raycast sampling step in X and Y, then clamp the result to the captured Game View area. The screenshot overlay draws only the outer edges of those reachable sample cells, so angled, L-shaped, separated, and partially UI-covered hit regions do not become one large rectangle. `BoundsMinX/Y` and `BoundsMaxX/Y` are still an axis-aligned bbox for JSON consumers, may extend up to half a sample step past the visible collider edge, and do not guarantee that every interior point is clickable. The mouse input tools convert internally to Unity Input System coordinates: diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/UIElementInfo.cs b/Packages/src/Editor/Api/McpTools/Screenshot/UIElementInfo.cs index 1127409204..483379aab4 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/UIElementInfo.cs +++ b/Packages/src/Editor/Api/McpTools/Screenshot/UIElementInfo.cs @@ -19,5 +19,8 @@ public class UIElementInfo public string Label { get; set; } = ""; public int SortingOrder { get; set; } public int SiblingIndex { get; set; } + + internal List RaycastOutlineSegments { get; set; } = + new List(); } } diff --git a/Packages/src/Editor/Utils/RaycastGridAnnotator.cs b/Packages/src/Editor/Utils/RaycastGridAnnotator.cs index 40ce38cfd5..8a2065957e 100644 --- a/Packages/src/Editor/Utils/RaycastGridAnnotator.cs +++ b/Packages/src/Editor/Utils/RaycastGridAnnotator.cs @@ -13,8 +13,8 @@ internal static class RaycastGridAnnotator { private const int GRID_COLUMNS = 5; private const int GRID_ROWS = 5; - private const int CLUSTERED_GRID_COLUMNS = 20; - private const int CLUSTERED_GRID_ROWS = 20; + private const int CLUSTERED_GRID_COLUMNS = 40; + private const int CLUSTERED_GRID_ROWS = 40; private const float MARKER_SIZE = 18f; internal static List CollectRaycastGridPoints( @@ -353,7 +353,7 @@ private static RaycastClusterCollection CollectClusterSamples( RaycastHit hit = raycastResult.Hits[0]; Collider collider = hit.collider; - int clusterKey = collider.GetInstanceID(); + int clusterKey = CreateClusterKey(collider); if (!clusterCollection.MetadataByClusterKey.ContainsKey(clusterKey)) { clusterCollection.MetadataByClusterKey.Add( @@ -361,22 +361,34 @@ private static RaycastClusterCollection CollectClusterSamples( CreateColliderMetadata(collider)); } - clusterCollection.Samples.Add(CreateClusterSample(inputPosition, clusterKey)); + clusterCollection.Samples.Add(CreateClusterSample(inputPosition, clusterKey, row, column)); } } return clusterCollection; } + internal static int CreateClusterKey(Collider collider) + { + Debug.Assert(collider != null, "Collider is required for raycast clustering."); + + // A placement area can use several colliders on one object; one annotation should describe the clickable object. + return collider!.gameObject.GetInstanceID(); + } + private static RaycastClusterSample CreateClusterSample( Vector2 inputPosition, - int clusterKey) + int clusterKey, + int row, + int column) { return new RaycastClusterSample { ClusterKey = clusterKey, InputX = inputPosition.x, - InputY = inputPosition.y + InputY = inputPosition.y, + Row = row, + Column = column }; } @@ -401,6 +413,8 @@ internal static UIElementInfo CreatePhysicsColliderElement( Debug.Assert(cluster.Samples.Count > 0, "Physics collider cluster must contain sampled hits."); RaycastClusterSample representative = cluster.Representative; RaycastSampleBounds sampleBounds = CalculateSampleCellBounds(cluster.Samples, sampleCoverage); + List outlineSegments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(cluster.Samples, sampleCoverage); UIElementInfo element = new UIElementInfo { @@ -418,7 +432,8 @@ internal static UIElementInfo CreatePhysicsColliderElement( SortingOrder = 0, SiblingIndex = 0, Layer = metadata.Layer, - Components = new List(metadata.Components) + Components = new List(metadata.Components), + RaycastOutlineSegments = outlineSegments }; Debug.Assert( diff --git a/Packages/src/Editor/Utils/RaycastHitClusterer.cs b/Packages/src/Editor/Utils/RaycastHitClusterer.cs index 0116c7f51f..9f1b10c09f 100644 --- a/Packages/src/Editor/Utils/RaycastHitClusterer.cs +++ b/Packages/src/Editor/Utils/RaycastHitClusterer.cs @@ -150,6 +150,8 @@ internal sealed class RaycastClusterSample public int ClusterKey { get; set; } public float InputX { get; set; } public float InputY { get; set; } + public int Row { get; set; } + public int Column { get; set; } } /// diff --git a/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs b/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs new file mode 100644 index 0000000000..134c8b2a49 --- /dev/null +++ b/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs @@ -0,0 +1,283 @@ +#nullable enable +using System.Collections.Generic; +using UnityEngine; + +namespace io.github.hatayama.uLoopMCP +{ + /// + /// Builds screen-space outlines from sampled raycast hit cells. + /// + internal static class RaycastSampleOutlineBuilder + { + internal static List CreateOutlineSegments( + List samples, + RaycastSampleCoverage sampleCoverage) + { + Debug.Assert(samples != null, "Raycast samples must not be null."); + List validSamples = samples!; + HashSet occupiedCells = CreateOccupiedCells(validSamples); + List segments = new List(); + + foreach (RaycastClusterSample sample in validSamples) + { + if (!HasGridCell(sample)) + { + continue; + } + + RaycastSampleCellBounds cellBounds = CalculateCellBounds(sample, sampleCoverage); + AddBoundarySegments(sample, cellBounds, occupiedCells, segments); + } + + return MergeCollinearSegments(segments); + } + + private static HashSet CreateOccupiedCells(List samples) + { + HashSet occupiedCells = new HashSet(); + foreach (RaycastClusterSample sample in samples) + { + if (!HasGridCell(sample)) + { + continue; + } + + occupiedCells.Add(new RaycastSampleCellKey(sample.Row, sample.Column)); + } + + return occupiedCells; + } + + private static bool HasGridCell(RaycastClusterSample sample) + { + return sample.Row > 0 && sample.Column > 0; + } + + private static void AddBoundarySegments( + RaycastClusterSample sample, + RaycastSampleCellBounds cellBounds, + HashSet occupiedCells, + List segments) + { + if (!occupiedCells.Contains(new RaycastSampleCellKey(sample.Row - 1, sample.Column))) + { + segments.Add(new RaycastOutlineSegment( + cellBounds.MinX, + cellBounds.MinY, + cellBounds.MaxX, + cellBounds.MinY)); + } + + if (!occupiedCells.Contains(new RaycastSampleCellKey(sample.Row + 1, sample.Column))) + { + segments.Add(new RaycastOutlineSegment( + cellBounds.MinX, + cellBounds.MaxY, + cellBounds.MaxX, + cellBounds.MaxY)); + } + + if (!occupiedCells.Contains(new RaycastSampleCellKey(sample.Row, sample.Column - 1))) + { + segments.Add(new RaycastOutlineSegment( + cellBounds.MinX, + cellBounds.MinY, + cellBounds.MinX, + cellBounds.MaxY)); + } + + if (!occupiedCells.Contains(new RaycastSampleCellKey(sample.Row, sample.Column + 1))) + { + segments.Add(new RaycastOutlineSegment( + cellBounds.MaxX, + cellBounds.MinY, + cellBounds.MaxX, + cellBounds.MaxY)); + } + } + + private static RaycastSampleCellBounds CalculateCellBounds( + RaycastClusterSample sample, + RaycastSampleCoverage sampleCoverage) + { + return new RaycastSampleCellBounds( + Mathf.Clamp(sample.InputX - sampleCoverage.HalfStepX, sampleCoverage.MinX, sampleCoverage.MaxX), + Mathf.Clamp(sample.InputY - sampleCoverage.HalfStepY, sampleCoverage.MinY, sampleCoverage.MaxY), + Mathf.Clamp(sample.InputX + sampleCoverage.HalfStepX, sampleCoverage.MinX, sampleCoverage.MaxX), + Mathf.Clamp(sample.InputY + sampleCoverage.HalfStepY, sampleCoverage.MinY, sampleCoverage.MaxY)); + } + + private static List MergeCollinearSegments(List segments) + { + segments.Sort(CompareSegments); + List mergedSegments = new List(); + + foreach (RaycastOutlineSegment segment in segments) + { + if (mergedSegments.Count == 0) + { + mergedSegments.Add(segment); + continue; + } + + int lastIndex = mergedSegments.Count - 1; + RaycastOutlineSegment lastSegment = mergedSegments[lastIndex]; + if (!CanMerge(lastSegment, segment)) + { + mergedSegments.Add(segment); + continue; + } + + mergedSegments[lastIndex] = MergeSegments(lastSegment, segment); + } + + return mergedSegments; + } + + private static int CompareSegments(RaycastOutlineSegment left, RaycastOutlineSegment right) + { + int orientationComparison = GetOrientationIndex(left).CompareTo(GetOrientationIndex(right)); + if (orientationComparison != 0) + { + return orientationComparison; + } + + if (IsHorizontal(left)) + { + int yComparison = left.StartY.CompareTo(right.StartY); + if (yComparison != 0) + { + return yComparison; + } + + return left.StartX.CompareTo(right.StartX); + } + + int xComparison = left.StartX.CompareTo(right.StartX); + if (xComparison != 0) + { + return xComparison; + } + + return left.StartY.CompareTo(right.StartY); + } + + private static bool CanMerge(RaycastOutlineSegment left, RaycastOutlineSegment right) + { + if (IsHorizontal(left) && IsHorizontal(right)) + { + return Approximately(left.StartY, right.StartY) && + Approximately(left.EndY, right.EndY) && + Approximately(left.EndX, right.StartX); + } + + if (IsVertical(left) && IsVertical(right)) + { + return Approximately(left.StartX, right.StartX) && + Approximately(left.EndX, right.EndX) && + Approximately(left.EndY, right.StartY); + } + + return false; + } + + private static RaycastOutlineSegment MergeSegments( + RaycastOutlineSegment left, + RaycastOutlineSegment right) + { + if (IsHorizontal(left)) + { + return new RaycastOutlineSegment(left.StartX, left.StartY, right.EndX, right.EndY); + } + + return new RaycastOutlineSegment(left.StartX, left.StartY, right.EndX, right.EndY); + } + + private static int GetOrientationIndex(RaycastOutlineSegment segment) + { + if (IsHorizontal(segment)) + { + return 0; + } + + return 1; + } + + private static bool IsHorizontal(RaycastOutlineSegment segment) + { + return Approximately(segment.StartY, segment.EndY); + } + + private static bool IsVertical(RaycastOutlineSegment segment) + { + return Approximately(segment.StartX, segment.EndX); + } + + private static bool Approximately(float left, float right) + { + return Mathf.Abs(left - right) <= 0.001f; + } + + private readonly struct RaycastSampleCellKey + { + private readonly int _row; + private readonly int _column; + + public RaycastSampleCellKey(int row, int column) + { + _row = row; + _column = column; + } + + public override bool Equals(object obj) + { + if (!(obj is RaycastSampleCellKey other)) + { + return false; + } + + return _row == other._row && _column == other._column; + } + + public override int GetHashCode() + { + return (_row * 397) ^ _column; + } + } + + private readonly struct RaycastSampleCellBounds + { + public readonly float MinX; + public readonly float MinY; + public readonly float MaxX; + public readonly float MaxY; + + public RaycastSampleCellBounds(float minX, float minY, float maxX, float maxY) + { + MinX = minX; + MinY = minY; + MaxX = maxX; + MaxY = maxY; + } + } + } + + /// + /// Represents one axis-aligned outline segment in top-left Game View input coordinates. + /// + internal readonly struct RaycastOutlineSegment + { + public readonly float StartX; + public readonly float StartY; + public readonly float EndX; + public readonly float EndY; + + public RaycastOutlineSegment(float startX, float startY, float endX, float endY) + { + StartX = startX; + StartY = startY; + EndX = endX; + EndY = endY; + } + } +} diff --git a/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs.meta b/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs.meta new file mode 100644 index 0000000000..c914aecd5d --- /dev/null +++ b/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 227bc5572603b47b9b72c96096c285bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/Utils/UIElementAnnotator.cs b/Packages/src/Editor/Utils/UIElementAnnotator.cs index 00a98bddbf..3d33f5e4a2 100644 --- a/Packages/src/Editor/Utils/UIElementAnnotator.cs +++ b/Packages/src/Editor/Utils/UIElementAnnotator.cs @@ -114,10 +114,11 @@ public static GameObject CreateAnnotationOverlay(List elements, f Font font = Resources.GetBuiltinResource("LegacyRuntime.ttf"); List drawInfos = new List(elements.Count); + float physicsAnnotationScreenHeight = CalculatePhysicsAnnotationScreenHeight(elements); foreach (UIElementInfo element in elements) { - drawInfos.Add(CreateAnnotationDrawInfo(element)); + drawInfos.Add(CreateAnnotationDrawInfo(element, physicsAnnotationScreenHeight)); } foreach (AnnotationDrawInfo drawInfo in drawInfos) @@ -373,6 +374,12 @@ private static void CreateAnnotationBorderForElement( AnnotationDrawInfo drawInfo, AnnotationBorderMetrics borderMetrics) { + if (drawInfo.OutlineSegments.Count > 0) + { + CreateAnnotationOutlineForElement(parent, drawInfo, borderMetrics); + return; + } + CreateBorder( parent, "LightOuter", @@ -402,6 +409,32 @@ private static void CreateAnnotationBorderForElement( drawInfo.BorderColors.Inner); } + private static void CreateAnnotationOutlineForElement( + Transform parent, + AnnotationDrawInfo drawInfo, + AnnotationBorderMetrics borderMetrics) + { + float outerThickness = borderMetrics.ColorThickness + borderMetrics.NeutralThickness * 2f; + CreateOutline( + parent, + "LightOuter", + drawInfo.OutlineSegments, + outerThickness, + drawInfo.BorderColors.Outer); + CreateOutline( + parent, + "ColorMiddle", + drawInfo.OutlineSegments, + borderMetrics.ColorThickness, + drawInfo.BorderColors.Middle); + CreateOutline( + parent, + "DarkInner", + drawInfo.OutlineSegments, + borderMetrics.NeutralThickness, + drawInfo.BorderColors.Inner); + } + private static void CreateAnnotationLabelForElement( Transform parent, AnnotationDrawInfo drawInfo, @@ -419,22 +452,86 @@ private static void CreateAnnotationLabelForElement( borderMetrics.LabelOutlineDistance); } - private static AnnotationDrawInfo CreateAnnotationDrawInfo(UIElementInfo element) + private static AnnotationDrawInfo CreateAnnotationDrawInfo( + UIElementInfo element, + float physicsAnnotationScreenHeight) { Color color = GetAnnotationColorForElement(element); Color contrastColor = GetContrastingTextColor(color); AnnotationBorderColors borderColors = GetAnnotationBorderColors(color); string displayLabel = CreateDisplayLabel(element); + float screenMinX = element.BoundsMinX; + float screenMinY = element.BoundsMinY; + float screenMaxX = element.BoundsMaxX; + float screenMaxY = element.BoundsMaxY; + List outlineSegments = element.RaycastOutlineSegments; + + if (IsPhysicsColliderElement(element)) + { + Debug.Assert( + physicsAnnotationScreenHeight >= 0f, + "Physics collider annotations require a non-negative Game View height."); + screenMinY = physicsAnnotationScreenHeight - element.BoundsMaxY; + screenMaxY = physicsAnnotationScreenHeight - element.BoundsMinY; + outlineSegments = ConvertTopLeftOutlineSegmentsToScreenSegments( + element.RaycastOutlineSegments, + physicsAnnotationScreenHeight); + } return new AnnotationDrawInfo( - element.BoundsMinX, - element.BoundsMinY, - element.BoundsMaxX, - element.BoundsMaxY, + screenMinX, + screenMinY, + screenMaxX, + screenMaxY, color, contrastColor, borderColors, - displayLabel); + displayLabel, + outlineSegments); + } + + private static float CalculatePhysicsAnnotationScreenHeight(List elements) + { + foreach (UIElementInfo element in elements) + { + if (!IsPhysicsColliderElement(element)) + { + continue; + } + + return GameViewCoordinateUtility.GetMainGameViewSize().y; + } + + return 0f; + } + + private static bool IsPhysicsColliderElement(UIElementInfo element) + { + return element.Type == "PhysicsCollider"; + } + + private static List ConvertTopLeftOutlineSegmentsToScreenSegments( + List outlineSegments, + float screenHeight) + { + List screenSegments = new List(outlineSegments.Count); + foreach (RaycastOutlineSegment segment in outlineSegments) + { + screenSegments.Add(ConvertTopLeftOutlineSegmentToScreenSegment(segment, screenHeight)); + } + + return screenSegments; + } + + internal static RaycastOutlineSegment ConvertTopLeftOutlineSegmentToScreenSegment( + RaycastOutlineSegment segment, + float screenHeight) + { + return new RaycastOutlineSegment( + segment.StartX, + screenHeight - segment.StartY, + segment.EndX, + screenHeight - segment.EndY); } private static void CreateBorder( @@ -450,6 +547,39 @@ private static void CreateBorder( CreateBorderEdge(parent, $"{name}_Right", borderEdgeRects.Right, color); } + private static void CreateOutline( + Transform parent, + string name, + List outlineSegments, + float thickness, + Color color) + { + for (int i = 0; i < outlineSegments.Count; i++) + { + Rect rect = CalculateOutlineSegmentRect(outlineSegments[i], thickness); + CreateBorderEdge(parent, $"{name}_Outline_{i}", rect, color); + } + } + + internal static Rect CalculateOutlineSegmentRect(RaycastOutlineSegment segment, float thickness) + { + Debug.Assert(thickness >= 0f, "Outline thickness must not be negative."); + bool horizontal = Mathf.Approximately(segment.StartY, segment.EndY); + bool vertical = Mathf.Approximately(segment.StartX, segment.EndX); + Debug.Assert(horizontal || vertical, "Raycast outline segments must be axis-aligned."); + + if (horizontal) + { + float minX = Mathf.Min(segment.StartX, segment.EndX); + float width = Mathf.Abs(segment.EndX - segment.StartX); + return new Rect(minX, segment.StartY - thickness / 2f, width, thickness); + } + + float minY = Mathf.Min(segment.StartY, segment.EndY); + float height = Mathf.Abs(segment.EndY - segment.StartY); + return new Rect(segment.StartX - thickness / 2f, minY, thickness, height); + } + internal static BorderEdgeRects CalculateBorderEdgeRects( float minX, float minY, float maxX, float maxY, float thickness) { @@ -701,6 +831,7 @@ private readonly struct AnnotationDrawInfo public readonly Color ContrastColor; public readonly AnnotationBorderColors BorderColors; public readonly string DisplayLabel; + public readonly List OutlineSegments; public AnnotationDrawInfo( float screenMinX, @@ -710,7 +841,8 @@ public AnnotationDrawInfo( Color color, Color contrastColor, AnnotationBorderColors borderColors, - string displayLabel) + string displayLabel, + List outlineSegments) { ScreenMinX = screenMinX; ScreenMinY = screenMinY; @@ -720,6 +852,7 @@ public AnnotationDrawInfo( ContrastColor = contrastColor; BorderColors = borderColors; DisplayLabel = displayLabel; + OutlineSegments = outlineSegments; } } From ac4a3af95176dafc8ea95c06e355eb3d6e36821e Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 21:43:03 +0900 Subject: [PATCH 02/12] Clarify perspective demo blocker colors Use distinct UI and visual blocker colors so the raycast annotation demo is easier to inspect from an angled camera. --- .../Scenes/RaycastAnnotationPerspectiveDemoScene.unity | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity index 8a5c84eccd..10fd91a4ee 100644 --- a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity +++ b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity @@ -522,7 +522,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.8, g: 0.9, b: 1, a: 0.42} + m_Color: {r: 0.05, g: 0.55, b: 1, a: 0.38} m_RaycastTarget: 1 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 @@ -1179,7 +1179,8 @@ Material: m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 m_CustomRenderQueue: -1 - stringTagMap: {} + stringTagMap: + RenderType: Opaque disabledShaderPasses: [] m_LockedProperties: m_SavedProperties: @@ -1274,8 +1275,8 @@ Material: - _WorkflowMode: 1 - _ZWrite: 1 m_Colors: - - _BaseColor: {r: 0.62, g: 0.67, b: 0.72, a: 1} - - _Color: {r: 1, g: 1, b: 1, a: 1} + - _BaseColor: {r: 0.95, g: 0.05, b: 0.65, a: 1} + - _Color: {r: 0.95, g: 0.049999982, b: 0.65, a: 1} - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} m_BuildTextureStacks: [] From f0e843b53dac0ba24bd8f5ed11899135c24612b1 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 21:46:00 +0900 Subject: [PATCH 03/12] Use amber visual blocker in raycast demo Avoid a magenta blocker color that looks like a shader error while keeping the visual blocker distinct from the blue UI blocker. --- Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity index 10fd91a4ee..5977093b0f 100644 --- a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity +++ b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity @@ -1275,8 +1275,8 @@ Material: - _WorkflowMode: 1 - _ZWrite: 1 m_Colors: - - _BaseColor: {r: 0.95, g: 0.05, b: 0.65, a: 1} - - _Color: {r: 0.95, g: 0.049999982, b: 0.65, a: 1} + - _BaseColor: {r: 1, g: 0.68, b: 0.05, a: 1} + - _Color: {r: 1, g: 0.68, b: 0.049999982, a: 1} - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} m_BuildTextureStacks: [] From 37a0346c3c764dc790a27d43e1dd87ee68384038 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 21:51:35 +0900 Subject: [PATCH 04/12] Make raycast demo blocker transparent Use a transparent amber material so the foreground blocker remains visible while revealing the placement cells behind it. --- ...aycastAnnotationPerspectiveDemoScene.unity | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity index 5977093b0f..51e4e1dbc5 100644 --- a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity +++ b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity @@ -1173,14 +1173,15 @@ Material: m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} m_Parent: {fileID: 0} m_ModifiedSerializedProperties: 0 - m_ValidKeywords: [] + m_ValidKeywords: + - _SURFACE_TYPE_TRANSPARENT m_InvalidKeywords: [] m_LightmapFlags: 4 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 - m_CustomRenderQueue: -1 + m_CustomRenderQueue: 3000 stringTagMap: - RenderType: Opaque + RenderType: Transparent disabledShaderPasses: [] m_LockedProperties: m_SavedProperties: @@ -1255,8 +1256,8 @@ Material: - _Cutoff: 0.5 - _DetailAlbedoMapScale: 1 - _DetailNormalMapScale: 1 - - _DstBlend: 0 - - _DstBlendAlpha: 0 + - _DstBlend: 10 + - _DstBlendAlpha: 10 - _EnvironmentReflections: 1 - _GlossMapScale: 0 - _Glossiness: 0 @@ -1269,14 +1270,14 @@ Material: - _Smoothness: 0.5 - _SmoothnessTextureChannel: 0 - _SpecularHighlights: 1 - - _SrcBlend: 1 + - _SrcBlend: 5 - _SrcBlendAlpha: 1 - - _Surface: 0 + - _Surface: 1 - _WorkflowMode: 1 - - _ZWrite: 1 + - _ZWrite: 0 m_Colors: - - _BaseColor: {r: 1, g: 0.68, b: 0.05, a: 1} - - _Color: {r: 1, g: 0.68, b: 0.049999982, a: 1} + - _BaseColor: {r: 1, g: 0.68, b: 0.05, a: 0.38} + - _Color: {r: 1, g: 0.68, b: 0.049999982, a: 0.38} - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} m_BuildTextureStacks: [] From f5e9c117e56952461a947e7f92dc0cd3672d2a33 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 21:56:03 +0900 Subject: [PATCH 05/12] Document screenshot boolean values Clarify that screenshot boolean options require explicit true or false values so skill examples match the CLI option parser. --- .../Api/McpTools/Screenshot/Skill/SKILL.md | 33 ++++++++++--------- .../Skill/references/annotated-elements.md | 6 ++-- .../McpTools/SimulateMouseUi/Skill/SKILL.md | 6 ++-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md index cb277b21be..92bede01d8 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md +++ b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md @@ -10,7 +10,7 @@ Take a screenshot of any Unity EditorWindow by name and save as PNG. ## Usage ```bash -uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements] [--annotate-raycast-grid] [--raycast-layer-mask ] [--elements-only] [--output-directory ] +uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] ``` ## Parameters @@ -22,10 +22,10 @@ uloop screenshot [--window-name ] [--resolution-scale ] [--match-mo | `--match-mode` | enum | `exact` | Window name matching mode: `exact`, `prefix`, or `contains`. Ignored when `--capture-mode rendering`. | | `--capture-mode` | enum | `window` | `window`=capture EditorWindow including toolbar, `rendering`=capture game rendering only (PlayMode required, coordinates match simulate-mouse) | | `--output-directory` | string | `""` | Output directory path for saving screenshots. When empty, uses default path (.uloop/outputs/Screenshots/). Accepts absolute paths. | -| `--annotate-elements` | boolean | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Only works with `--capture-mode rendering` in PlayMode. | -| `--annotate-raycast-grid` | boolean | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | -| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by collider and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | -| `--elements-only` | boolean | `false` | Return only annotation JSON without capturing a screenshot image. Requires `--annotate-elements` or `--annotate-raycast-grid`, and `--capture-mode rendering` in PlayMode. | +| `--annotate-elements` | boolean value | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering` in PlayMode. | +| `--annotate-raycast-grid` | boolean value | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | +| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by collider and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | +| `--elements-only` | boolean value | `false` | Return only annotation JSON without capturing a screenshot image. Pass `true` or `false`; bare flags are not accepted. Requires `--annotate-elements true` or `--annotate-raycast-grid true`, and `--capture-mode rendering` in PlayMode. | ## Match Modes @@ -55,23 +55,23 @@ uloop screenshot uloop screenshot --capture-mode rendering # Annotate interactive UI elements with index labels (for simulate-mouse workflow) -uloop screenshot --capture-mode rendering --annotate-elements +uloop screenshot --capture-mode rendering --annotate-elements true # Annotate 3D physics raycast candidate points -uloop screenshot --capture-mode rendering --annotate-raycast-grid +uloop screenshot --capture-mode rendering --annotate-raycast-grid true # Inspect hit layers, then rerun with the layer used by game input -uloop screenshot --capture-mode rendering --annotate-raycast-grid --elements-only -uloop screenshot --capture-mode rendering --annotate-raycast-grid --raycast-layer-mask Default --elements-only +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true # Annotate clustered 3D collider candidates on selected layers -uloop screenshot --capture-mode rendering --annotate-raycast-grid --raycast-layer-mask Ground,Clickable +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable # Get UI element coordinates without capturing an image (fastest) -uloop screenshot --capture-mode rendering --annotate-elements --elements-only +uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true # Get clustered 3D collider coordinates without capturing an image -uloop screenshot --capture-mode rendering --annotate-raycast-grid --raycast-layer-mask Default --elements-only +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true # Take a screenshot of Scene View uloop screenshot --window-name Scene @@ -101,9 +101,9 @@ Returns JSON with: - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` - - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid --raycast-layer-mask ` is used. - - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid` is used without `--raycast-layer-mask`. - - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid` is used without `--raycast-layer-mask`. Empty when a mask is provided. + - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid true --raycast-layer-mask ` is used. + - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. + - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. Empty when a mask is provided. For `AnnotatedElements` fields and Game View coordinates, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. @@ -114,9 +114,10 @@ When multiple windows match (e.g., multiple Inspector windows or when using `con - Use `uloop focus-window` first if needed - Target window must be open in Unity Editor - Window name matching is always case-insensitive +- Boolean options use value syntax. Write `--annotate-elements true`, `--annotate-raycast-grid true`, and `--elements-only true`; do not use bare boolean flags. - Use `--capture-mode rendering` for coordinates that should be passed to `simulate-mouse-input`, `simulate-mouse-ui`, or `raycast`. - Use `ScreenshotToInputFormula` before passing raw image pixels to mouse tools. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates. -- To discover a useful physics layer, first run `--annotate-raycast-grid` without `--raycast-layer-mask`, inspect `RaycastLayerSummaries`, then rerun with `--raycast-layer-mask `. +- To discover a useful physics layer, first run `--annotate-raycast-grid true` without `--raycast-layer-mask`, inspect `RaycastLayerSummaries`, then rerun with `--raycast-layer-mask `. - Use `--raycast-layer-mask` with layer names from `find-game-objects` or project code when the game input code raycasts only specific layers. Layers hidden by Camera.main.cullingMask are not reported because they are not visible to the screenshot camera. - Clustered `PhysicsCollider` entries avoid points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element, including world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. The screenshot overlay draws the reachable sampled-cell outline, while `BoundsMinX/Y` and `BoundsMaxX/Y` remain the axis-aligned sampled-cell bbox for JSON consumers. Use `SimX/SimY` for the actual click point. If every sampled hit in the cluster is covered, that collider is omitted. diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md index d4fe2fffc6..58616dc26d 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md +++ b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md @@ -1,10 +1,10 @@ # Annotated Elements and Coordinates -Read this when using `uloop screenshot --capture-mode rendering --annotate-elements` or clustered `--annotate-raycast-grid --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. +Read this when using `uloop screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. ## AnnotatedElements Fields -`AnnotatedElements` is empty unless `--annotate-elements` is used, or unless `--annotate-raycast-grid --raycast-layer-mask ` adds clustered 3D collider candidates. UI entries are sorted by z-order, frontmost first. Each item contains: +`AnnotatedElements` is empty unless `--annotate-elements true` is used, or unless `--annotate-raycast-grid true --raycast-layer-mask ` adds clustered 3D collider candidates. UI entries are sorted by z-order, frontmost first. Each item contains: - `Label`: Index label in JSON (`A` = frontmost, `B` = next, ...). Screenshot labels also include the interaction hint, such as `A / CLICK` or `B / DRAG`. - `Name`: Element name @@ -20,7 +20,7 @@ Read this when using `uloop screenshot --capture-mode rendering --annotate-eleme ## RaycastLayerSummaries Fields -`RaycastLayerSummaries` is populated when `--annotate-raycast-grid` is used without `--raycast-layer-mask`. It is built from dense raycast samples, while `RaycastGridPoints` remains the coarse 5x5 annotated grid. +`RaycastLayerSummaries` is populated when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. It is built from dense raycast samples, while `RaycastGridPoints` remains the coarse 5x5 annotated grid. - `Layer`: Physics layer name to pass to `--raycast-layer-mask` - `LayerIndex`: Unity physics layer index diff --git a/Packages/src/Editor/Api/McpTools/SimulateMouseUi/Skill/SKILL.md b/Packages/src/Editor/Api/McpTools/SimulateMouseUi/Skill/SKILL.md index 451178c995..95f6185a18 100644 --- a/Packages/src/Editor/Api/McpTools/SimulateMouseUi/Skill/SKILL.md +++ b/Packages/src/Editor/Api/McpTools/SimulateMouseUi/Skill/SKILL.md @@ -11,10 +11,10 @@ Simulate mouse interaction on Unity PlayMode UI: $ARGUMENTS ## Workflow 1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` +2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true` 3. Use the `AnnotatedElements` array to find the target element by `Label`, `Name`, or `Path` (A=frontmost, B=next, ...). Use `Interaction` to distinguish click targets from drag/drop/text targets, then use `SimX`/`SimY` directly as `--x`/`--y` coordinates. 4. Execute the appropriate `uloop simulate-mouse-ui` command -5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements` +5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements true` 6. Report what happened ## Tool Reference @@ -127,6 +127,6 @@ Returns JSON with: - `EndPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `EndPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. +These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements true`. Note: Click and LongPress on empty space (no UI element) still return `Success = true` with `HitGameObjectName = null`. Drag actions on empty space return `Success = false`. From 007c13acf5b7be5aae42cf2a1a7a593080299bb2 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 21:57:44 +0900 Subject: [PATCH 06/12] Refresh generated uloop skills Regenerate the checked-in Claude and agent skill copies so screenshot examples include explicit boolean values. --- .agents/skills/uloop-clear-console/SKILL.md | 10 +-- .agents/skills/uloop-compile/SKILL.md | 22 +++--- .../skills/uloop-control-play-mode/SKILL.md | 12 +-- .../uloop-execute-dynamic-code/SKILL.md | 10 +-- .../references/asset-operations.md | 2 + .../playmode-automation-powershell.md | 32 ++++---- .../references/playmode-automation-zsh.md | 28 +++---- .../skills/uloop-find-game-objects/SKILL.md | 18 ++--- .agents/skills/uloop-focus-window/SKILL.md | 10 +-- .agents/skills/uloop-get-hierarchy/SKILL.md | 16 ++-- .agents/skills/uloop-get-logs/SKILL.md | 14 ++-- .agents/skills/uloop-hello-world/SKILL.md | 12 +-- .agents/skills/uloop-launch/SKILL.md | 18 ++--- .agents/skills/uloop-raycast/SKILL.md | 61 +++++++++++++++ .agents/skills/uloop-record-input/SKILL.md | 14 ++-- .agents/skills/uloop-replay-input/SKILL.md | 20 +++-- .agents/skills/uloop-run-tests/SKILL.md | 24 +++--- .agents/skills/uloop-screenshot/SKILL.md | 77 ++++++++++++++----- .../references/annotated-elements.md | 49 +++++++++--- .../skills/uloop-simulate-keyboard/SKILL.md | 30 ++++---- .../uloop-simulate-mouse-input/SKILL.md | 63 +++++++++------ .../skills/uloop-simulate-mouse-ui/SKILL.md | 50 ++++++------ .claude/skills/uloop-clear-console/SKILL.md | 10 +-- .claude/skills/uloop-compile/SKILL.md | 22 +++--- .../skills/uloop-control-play-mode/SKILL.md | 12 +-- .../uloop-execute-dynamic-code/SKILL.md | 10 +-- .../references/asset-operations.md | 2 + .../playmode-automation-powershell.md | 32 ++++---- .../references/playmode-automation-zsh.md | 28 +++---- .../skills/uloop-find-game-objects/SKILL.md | 18 ++--- .claude/skills/uloop-focus-window/SKILL.md | 10 +-- .claude/skills/uloop-get-hierarchy/SKILL.md | 16 ++-- .claude/skills/uloop-get-logs/SKILL.md | 14 ++-- .claude/skills/uloop-hello-world/SKILL.md | 12 +-- .claude/skills/uloop-launch/SKILL.md | 18 ++--- .claude/skills/uloop-raycast/SKILL.md | 61 +++++++++++++++ .claude/skills/uloop-record-input/SKILL.md | 14 ++-- .claude/skills/uloop-replay-input/SKILL.md | 20 +++-- .claude/skills/uloop-run-tests/SKILL.md | 24 +++--- .claude/skills/uloop-screenshot/SKILL.md | 77 ++++++++++++++----- .../references/annotated-elements.md | 49 +++++++++--- .../skills/uloop-simulate-keyboard/SKILL.md | 30 ++++---- .../uloop-simulate-mouse-input/SKILL.md | 63 +++++++++------ .../skills/uloop-simulate-mouse-ui/SKILL.md | 50 ++++++------ 44 files changed, 746 insertions(+), 438 deletions(-) create mode 100644 .agents/skills/uloop-raycast/SKILL.md create mode 100644 .claude/skills/uloop-raycast/SKILL.md diff --git a/.agents/skills/uloop-clear-console/SKILL.md b/.agents/skills/uloop-clear-console/SKILL.md index 6ba18c90f3..5f9c1daf66 100644 --- a/.agents/skills/uloop-clear-console/SKILL.md +++ b/.agents/skills/uloop-clear-console/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-clear-console -description: "Clear all Unity Console log entries. Use when you need to: (1) Clear console before running tests or compilation, (2) Start a fresh debugging session, (3) Remove noisy logs to isolate specific output." +description: "Clear Unity Console entries. Use before compile, tests, or debugging when stale logs would hide the current result." --- -# uloop clear-console +# npx --yes uloop-cli@2.2.0 clear-console Clear Unity console logs. ## Usage ```bash -uloop clear-console [--add-confirmation-message] +npx --yes uloop-cli@2.2.0 clear-console [--add-confirmation-message] ``` ## Parameters @@ -29,10 +29,10 @@ uloop clear-console [--add-confirmation-message] ```bash # Clear console -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console # Clear with confirmation -uloop clear-console --add-confirmation-message +npx --yes uloop-cli@2.2.0 clear-console --add-confirmation-message ``` ## Output diff --git a/.agents/skills/uloop-compile/SKILL.md b/.agents/skills/uloop-compile/SKILL.md index f13d06abd1..f8df207d0b 100644 --- a/.agents/skills/uloop-compile/SKILL.md +++ b/.agents/skills/uloop-compile/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-compile -description: "Compile Unity project and report errors/warnings. Use when you need to: (1) Verify code compiles after C# file edits, (2) Check for compile errors before testing, (3) Force full recompilation with Domain Reload. Returns error and warning counts." +description: "Compile the Unity project and report errors/warnings. Use after C# edits or when a full Domain Reload compile is needed." --- -# uloop compile +# npx --yes uloop-cli@2.2.0 compile Execute Unity project compilation. ## Usage ```bash -uloop compile [--force-recompile] [--wait-for-domain-reload] +npx --yes uloop-cli@2.2.0 compile [--force-recompile] [--wait-for-domain-reload] ``` ## Parameters @@ -30,16 +30,16 @@ uloop compile [--force-recompile] [--wait-for-domain-reload] ```bash # Check compilation -uloop compile +npx --yes uloop-cli@2.2.0 compile # Force full recompilation -uloop compile --force-recompile +npx --yes uloop-cli@2.2.0 compile --force-recompile # Force recompilation and wait for Domain Reload completion -uloop compile --force-recompile true --wait-for-domain-reload true +npx --yes uloop-cli@2.2.0 compile --force-recompile true --wait-for-domain-reload true # Wait for Domain Reload completion even without force recompilation -uloop compile --force-recompile false --wait-for-domain-reload true +npx --yes uloop-cli@2.2.0 compile --force-recompile false --wait-for-domain-reload true ``` ## Output @@ -56,15 +56,15 @@ Diagnose the failure mode before retrying. **Stale lock files** (CLI hangs or shows "Unity is busy" while Unity Editor *is* running): ```bash -uloop fix +npx --yes uloop-cli@2.2.0 fix ``` -This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `uloop compile`. +This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `npx --yes uloop-cli@2.2.0 compile`. **Unity Editor not running** (CLI returns a connection failure and no Unity process is alive): ```bash -uloop launch +npx --yes uloop-cli@2.2.0 launch ``` -`uloop launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `uloop compile`. +`npx --yes uloop-cli@2.2.0 launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `npx --yes uloop-cli@2.2.0 compile`. diff --git a/.agents/skills/uloop-control-play-mode/SKILL.md b/.agents/skills/uloop-control-play-mode/SKILL.md index 220eeebe91..d95f5a6baa 100644 --- a/.agents/skills/uloop-control-play-mode/SKILL.md +++ b/.agents/skills/uloop-control-play-mode/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-control-play-mode -description: "Control Unity Editor play mode (play/stop/pause). Use when you need to: (1) Start play mode to test game behavior, (2) Stop play mode to return to edit mode, (3) Pause play mode for frame-by-frame inspection." +description: "Control Unity Editor Play Mode. Use to start, stop, or pause Play Mode for runtime behavior checks and frame inspection." --- -# uloop control-play-mode +# npx --yes uloop-cli@2.2.0 control-play-mode Control Unity Editor play mode (play/stop/pause). ## Usage ```bash -uloop control-play-mode [options] +npx --yes uloop-cli@2.2.0 control-play-mode [options] ``` ## Parameters @@ -29,13 +29,13 @@ uloop control-play-mode [options] ```bash # Start play mode -uloop control-play-mode --action Play +npx --yes uloop-cli@2.2.0 control-play-mode --action Play # Stop play mode -uloop control-play-mode --action Stop +npx --yes uloop-cli@2.2.0 control-play-mode --action Stop # Pause play mode -uloop control-play-mode --action Pause +npx --yes uloop-cli@2.2.0 control-play-mode --action Pause ``` ## Output diff --git a/.agents/skills/uloop-execute-dynamic-code/SKILL.md b/.agents/skills/uloop-execute-dynamic-code/SKILL.md index 189ed4d1c2..7d1c49e5d7 100644 --- a/.agents/skills/uloop-execute-dynamic-code/SKILL.md +++ b/.agents/skills/uloop-execute-dynamic-code/SKILL.md @@ -6,7 +6,7 @@ context: fork # Task -Execute the following request using `uloop execute-dynamic-code`: $ARGUMENTS +Execute the following request using `npx --yes uloop-cli@2.2.0 execute-dynamic-code`: $ARGUMENTS For basic selected GameObject discovery or property inspection, use `find-game-objects --search-mode Selected` before this tool. Use this tool after the built-in inspection tools are not enough or when you need to modify Unity state. @@ -14,8 +14,8 @@ For basic selected GameObject discovery or property inspection, use `find-game-o 1. Read the relevant reference file(s) from the Code Examples section below 2. Construct C# code based on the reference examples -3. For multiline snippets, write the C# statements to a temporary `.csx` file and execute `uloop execute-dynamic-code --code-file ` -4. Use `uloop execute-dynamic-code --code ''` only for short one-line snippets +3. For multiline snippets, write the C# statements to a temporary `.csx` file and execute `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code-file ` +4. Use `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code ''` only for short one-line snippets 5. If execution fails, adjust code and retry 6. Report the execution result @@ -23,7 +23,7 @@ For basic selected GameObject discovery or property inspection, use `find-game-o - `--code ''`: Inline C# statements to execute. Use this for one-line snippets only. - `--code-file `: Read C# statements from a UTF-8 file. Prefer this for multiline snippets, especially on PowerShell, because Windows `.cmd` shims can lose lines from multiline inline arguments before `uloop` receives them. -- **Shell quoting**: bash/zsh uses single quotes, for example `uloop execute-dynamic-code --code 'using UnityEngine; return Mathf.PI;'`. PowerShell single-quoted strings can contain normal double quotes, for example `uloop execute-dynamic-code --code 'Debug.Log("Hello!");'`. +- **Shell quoting**: bash/zsh uses single quotes, for example `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'using UnityEngine; return Mathf.PI;'`. PowerShell single-quoted strings can contain normal double quotes, for example `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'Debug.Log("Hello!");'`. - `--parameters {}` (advanced, optional): Pass an object when reusing a snippet with varying data or when keeping values outside the code. Values are exposed as `parameters["param0"]`, `parameters["param1"]`, and so on. Omit this flag for most snippets, and pass an object instead of a JSON string. - `--compile-only true` (optional): Compile the snippet without executing it. Use this when you want Roslyn diagnostics before running new code. @@ -53,7 +53,7 @@ Returns JSON: - `DiagnosticsSummary`: string|null — compact summary when diagnostics are available - `Diagnostics`: object[] — structured diagnostics; same shape as `CompilationErrors`, usually populated together with it -Use `uloop get-logs` to retrieve `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` messages emitted by the snippet. On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, security violation, cancellation, or an "execution in progress" rejection, all of which return empty `CompilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. +Use `npx --yes uloop-cli@2.2.0 get-logs` to retrieve `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` messages emitted by the snippet. On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, security violation, cancellation, or an "execution in progress" rejection, all of which return empty `CompilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. ## Code Examples by Category diff --git a/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md index de0ed73fc1..31ddf7adc1 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md @@ -184,6 +184,8 @@ return $"Asset has {dependencies.Length} dependencies"; ## Refresh AssetDatabase +Use this after terminal-based asset file changes when Unity needs to import them and generate `.meta` files. + ```csharp using UnityEditor; diff --git a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index e14ceca3d9..6a764d5c2d 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -31,7 +31,7 @@ Use these patterns when you need shell-safe inline code: Single-quote the whole snippet and keep C# string literals unchanged. ```powershell -uloop execute-dynamic-code --code 'return "Hello from PowerShell";' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from PowerShell";' ``` ### Single quotes inside inline C# code @@ -39,7 +39,7 @@ uloop execute-dynamic-code --code 'return "Hello from PowerShell";' If the C# snippet itself contains a single quote, double it inside the PowerShell single-quoted string. ```powershell -uloop execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' ``` ### JSON-like values passed via `--parameters` @@ -47,7 +47,7 @@ uloop execute-dynamic-code --code 'char initial = ''A''; return initial.ToString Wrap the whole expression in single quotes so PowerShell passes the inner double quotes through unchanged. ```powershell -uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' ``` ### Multi-line C# snippets @@ -64,7 +64,7 @@ if (obj == null) return "Player not found"; return obj.name; '@ -uloop execute-dynamic-code --code $code +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code $code ``` You can combine multi-line code with multi-line parameters the same way. @@ -78,7 +78,7 @@ $parameters = @' {"param0":"Hello from PowerShell"} '@ -uloop execute-dynamic-code --code $code --parameters $parameters +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code $code --parameters $parameters ``` ## Click UI Button by Path @@ -168,7 +168,7 @@ return $"Moved {player.name} to {targetPos}"; # Tool Combination Workflows -Examples of combining `execute-dynamic-code` with other uloop tools for multi-step PlayMode automation. +Examples of combining `execute-dynamic-code` with other npx --yes uloop-cli@2.2.0 tools for multi-step PlayMode automation. ## find-game-objects → Click Button @@ -177,7 +177,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```powershell -uloop find-game-objects --required-components UnityEngine.UI.Button --include-inactive false +npx --yes uloop-cli@2.2.0 find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-components true +npx --yes uloop-cli@2.2.0 get-hierarchy --root-path 'Canvas' --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button @@ -243,7 +243,7 @@ return "Clicked PlayButton"; **Step 2**: Capture Game View to verify the result ```powershell -uloop screenshot --window-name Game +npx --yes uloop-cli@2.2.0 screenshot --window-name Game ``` ## Execute Action → Check Logs for Side Effects @@ -253,7 +253,7 @@ Run an action then inspect Unity Console logs to verify expected behavior. **Step 1**: Clear console before the action ```powershell -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console ``` **Step 2**: Perform the action @@ -277,7 +277,7 @@ return "Invoked TakeDamage(50)"; **Step 3**: Check logs for expected output ```powershell -uloop get-logs --log-type Log --search-text 'damage' +npx --yes uloop-cli@2.2.0 get-logs --log-type Log --search-text 'damage' ``` ## Full Automation: Play → Act → Capture → Stop @@ -287,13 +287,13 @@ End-to-end test flow: start Play mode, perform actions, capture evidence, stop. **Step 0**: Clear console to isolate this run ```powershell -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console ``` **Step 1**: Start Play mode ```powershell -uloop control-play-mode --action Play +npx --yes uloop-cli@2.2.0 control-play-mode --action Play ``` **Step 2**: Wait for scene initialization, then find and click a button @@ -314,17 +314,17 @@ return $"Clicked {startBtn.gameObject.name}"; **Step 3**: Capture screenshot as evidence ```powershell -uloop screenshot --window-name Game +npx --yes uloop-cli@2.2.0 screenshot --window-name Game ``` **Step 4**: Check logs for errors ```powershell -uloop get-logs --log-type Error +npx --yes uloop-cli@2.2.0 get-logs --log-type Error ``` **Step 5**: Stop Play mode ```powershell -uloop control-play-mode --action Stop +npx --yes uloop-cli@2.2.0 control-play-mode --action Stop ``` diff --git a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md index d82b7c6149..c44ea92235 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md @@ -31,7 +31,7 @@ Use these patterns when you need shell-safe inline code: Single-quote the whole snippet and keep C# string literals unchanged. ```zsh -uloop execute-dynamic-code --code 'return "Hello from zsh";' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from zsh";' ``` ### Single quotes inside inline C# code @@ -39,7 +39,7 @@ uloop execute-dynamic-code --code 'return "Hello from zsh";' If the C# snippet itself contains a single quote, close and reopen the shell string with `'\''`. ```zsh -uloop execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToString();' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToString();' ``` ### JSON-like values passed via `--parameters` @@ -47,7 +47,7 @@ uloop execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToSt Wrap the whole expression in single quotes so the shell does not interpret double quotes. ```zsh -uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from zsh"}' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from zsh"}' ``` ## Click UI Button by Path @@ -137,7 +137,7 @@ return $"Moved {player.name} to {targetPos}"; # Tool Combination Workflows -Examples of combining `execute-dynamic-code` with other uloop tools for multi-step PlayMode automation. +Examples of combining `execute-dynamic-code` with other npx --yes uloop-cli@2.2.0 tools for multi-step PlayMode automation. ## find-game-objects → Click Button @@ -146,7 +146,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```zsh -uloop find-game-objects --required-components UnityEngine.UI.Button --include-inactive false +npx --yes uloop-cli@2.2.0 find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -172,7 +172,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```zsh -uloop get-hierarchy --root-path "Canvas" --max-depth 3 --include-components true +npx --yes uloop-cli@2.2.0 get-hierarchy --root-path "Canvas" --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button @@ -212,7 +212,7 @@ return "Clicked PlayButton"; **Step 2**: Capture Game View to verify the result ```zsh -uloop screenshot --window-name Game +npx --yes uloop-cli@2.2.0 screenshot --window-name Game ``` ## Execute Action → Check Logs for Side Effects @@ -222,7 +222,7 @@ Run an action then inspect Unity Console logs to verify expected behavior. **Step 1**: Clear console before the action ```zsh -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console ``` **Step 2**: Perform the action @@ -246,7 +246,7 @@ return "Invoked TakeDamage(50)"; **Step 3**: Check logs for expected output ```zsh -uloop get-logs --log-type Log --search-text "damage" +npx --yes uloop-cli@2.2.0 get-logs --log-type Log --search-text "damage" ``` ## Full Automation: Play → Act → Capture → Stop @@ -256,13 +256,13 @@ End-to-end test flow: start Play mode, perform actions, capture evidence, stop. **Step 0**: Clear console to isolate this run ```zsh -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console ``` **Step 1**: Start Play mode ```zsh -uloop control-play-mode --action Play +npx --yes uloop-cli@2.2.0 control-play-mode --action Play ``` **Step 2**: Wait for scene initialization, then find and click a button @@ -283,17 +283,17 @@ return $"Clicked {startBtn.gameObject.name}"; **Step 3**: Capture screenshot as evidence ```zsh -uloop screenshot --window-name Game +npx --yes uloop-cli@2.2.0 screenshot --window-name Game ``` **Step 4**: Check logs for errors ```zsh -uloop get-logs --log-type Error +npx --yes uloop-cli@2.2.0 get-logs --log-type Error ``` **Step 5**: Stop Play mode ```zsh -uloop control-play-mode --action Stop +npx --yes uloop-cli@2.2.0 control-play-mode --action Stop ``` diff --git a/.agents/skills/uloop-find-game-objects/SKILL.md b/.agents/skills/uloop-find-game-objects/SKILL.md index 9c809e658c..3d80fce320 100644 --- a/.agents/skills/uloop-find-game-objects/SKILL.md +++ b/.agents/skills/uloop-find-game-objects/SKILL.md @@ -1,9 +1,9 @@ --- name: uloop-find-game-objects -description: "Use first when the user asks about the currently selected GameObject in the Unity Hierarchy. Inspect selected object details with `--search-mode Selected` before using `execute-dynamic-code`. Use when you need to: (1) Get details and component properties for selected GameObject(s), (2) Search for objects by name, regex, or path, (3) Find objects with specific components, tags, or layers. Use get-hierarchy when the child tree under the selection is needed. Returns hierarchy paths, active state, tags, layers, and components (or writes to a file when multiple GameObjects are selected)." +description: "Find or inspect Unity GameObjects, especially objects the user currently selected in the Hierarchy. Use for details, components, tags, layers, or name/path searches." --- -# uloop find-game-objects +# npx --yes uloop-cli@2.2.0 find-game-objects Find GameObjects with search criteria or get details for currently selected Hierarchy objects. @@ -12,7 +12,7 @@ Use this before `execute-dynamic-code` when identifying or inspecting selected G ## Usage ```bash -uloop find-game-objects [options] +npx --yes uloop-cli@2.2.0 find-game-objects [options] ``` ## Parameters @@ -48,22 +48,22 @@ uloop find-game-objects [options] ```bash # Find by name -uloop find-game-objects --name-pattern "Player" +npx --yes uloop-cli@2.2.0 find-game-objects --name-pattern "Player" # Find with component -uloop find-game-objects --required-components Rigidbody +npx --yes uloop-cli@2.2.0 find-game-objects --required-components Rigidbody # Find by tag -uloop find-game-objects --tag "Enemy" +npx --yes uloop-cli@2.2.0 find-game-objects --tag "Enemy" # Regex search -uloop find-game-objects --name-pattern "UI_.*" --search-mode Regex +npx --yes uloop-cli@2.2.0 find-game-objects --name-pattern "UI_.*" --search-mode Regex # Get selected GameObjects -uloop find-game-objects --search-mode Selected +npx --yes uloop-cli@2.2.0 find-game-objects --search-mode Selected # Get selected including inactive -uloop find-game-objects --search-mode Selected --include-inactive +npx --yes uloop-cli@2.2.0 find-game-objects --search-mode Selected --include-inactive ``` ## Output diff --git a/.agents/skills/uloop-focus-window/SKILL.md b/.agents/skills/uloop-focus-window/SKILL.md index 87dbc35876..5552b76e86 100644 --- a/.agents/skills/uloop-focus-window/SKILL.md +++ b/.agents/skills/uloop-focus-window/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-focus-window -description: "Bring Unity Editor window to front via uloop CLI. Use when you need to: (1) Focus Unity Editor before capturing screenshots, (2) Ensure Unity window is visible for visual checks, (3) Bring Unity to foreground for user interaction." +description: "Bring the Unity Editor window to front. Use when Unity must be visible for visual checks or user-facing interaction." --- -# uloop focus-window +# npx --yes uloop-cli@2.2.0 focus-window Bring Unity Editor window to front using OS-level commands. ## Usage ```bash -uloop focus-window +npx --yes uloop-cli@2.2.0 focus-window ``` ## Parameters @@ -27,7 +27,7 @@ None. ```bash # Focus Unity Editor -uloop focus-window +npx --yes uloop-cli@2.2.0 focus-window ``` ## Output @@ -42,5 +42,5 @@ These are the only two fields. There is no PID, window-handle, or platform field - **Works even when Unity is busy** (compiling, domain reload, etc.) - Uses OS-level commands (osascript on macOS, PowerShell on Windows) -- Useful before `uloop capture-unity-window` to ensure the target window is visible +- Useful before `npx --yes uloop-cli@2.2.0 capture-unity-window` to ensure the target window is visible - Brings the main Unity Editor window to the foreground diff --git a/.agents/skills/uloop-get-hierarchy/SKILL.md b/.agents/skills/uloop-get-hierarchy/SKILL.md index 976e0de5a6..a460089e65 100644 --- a/.agents/skills/uloop-get-hierarchy/SKILL.md +++ b/.agents/skills/uloop-get-hierarchy/SKILL.md @@ -1,9 +1,9 @@ --- name: uloop-get-hierarchy -description: "Get Unity scene hierarchy as a structured tree from all roots, a root path, or the current Hierarchy selection. Use this when you need the child tree, parent-child structure, or descendants under selected GameObject(s) with `--use-selection`. Use find-game-objects for selected object details and component properties. Hierarchy data is written to a JSON file on disk and the response returns the file path (not the tree inline) — open the file to read the structure." +description: "Get the Unity scene hierarchy as a structured tree. Use for parent-child structure, descendants, roots, or subtrees under objects the user currently selected." --- -# uloop get-hierarchy +# npx --yes uloop-cli@2.2.0 get-hierarchy Get Unity Hierarchy structure from the whole scene, a root path, or selected Hierarchy objects. @@ -12,7 +12,7 @@ Use this for hierarchy structure, especially descendants under the current selec ## Usage ```bash -uloop get-hierarchy [options] +npx --yes uloop-cli@2.2.0 get-hierarchy [options] ``` ## Parameters @@ -37,19 +37,19 @@ uloop get-hierarchy [options] ```bash # Get entire hierarchy -uloop get-hierarchy +npx --yes uloop-cli@2.2.0 get-hierarchy # Get hierarchy from specific root -uloop get-hierarchy --root-path "Canvas/UI" +npx --yes uloop-cli@2.2.0 get-hierarchy --root-path "Canvas/UI" # Limit depth -uloop get-hierarchy --max-depth 2 +npx --yes uloop-cli@2.2.0 get-hierarchy --max-depth 2 # Without components -uloop get-hierarchy --include-components false +npx --yes uloop-cli@2.2.0 get-hierarchy --include-components false # Get hierarchy from currently selected GameObjects -uloop get-hierarchy --use-selection +npx --yes uloop-cli@2.2.0 get-hierarchy --use-selection ``` ## Output diff --git a/.agents/skills/uloop-get-logs/SKILL.md b/.agents/skills/uloop-get-logs/SKILL.md index 1ca03fc116..ab8ddeda45 100644 --- a/.agents/skills/uloop-get-logs/SKILL.md +++ b/.agents/skills/uloop-get-logs/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-get-logs -description: "Retrieve current Unity Console entries via uloop CLI. Use when you need to: (1) inspect errors, warnings, or logs after compile, tests, PlayMode, or dynamic code execution, (2) search current Console messages or stack traces, (3) confirm whether a recent Unity operation emitted logs. Prefer this over reading Editor.log or Unity log files for normal Console contents; use log files only for startup, crash, freeze, or uloop connection failures." +description: "Read current Unity Console entries from a running Editor. Use during bug investigation after compile, tests, PlayMode, or dynamic code to inspect logs, warnings, errors, and stack traces." --- -# uloop get-logs +# npx --yes uloop-cli@2.2.0 get-logs Retrieve logs from Unity Console. ## Usage ```bash -uloop get-logs [options] +npx --yes uloop-cli@2.2.0 get-logs [options] ``` ## Parameters @@ -34,16 +34,16 @@ uloop get-logs [options] ```bash # Get all logs -uloop get-logs +npx --yes uloop-cli@2.2.0 get-logs # Get only errors -uloop get-logs --log-type Error +npx --yes uloop-cli@2.2.0 get-logs --log-type Error # Search for specific text -uloop get-logs --search-text "NullReference" +npx --yes uloop-cli@2.2.0 get-logs --search-text "NullReference" # Regex search -uloop get-logs --search-text "Missing.*Component" --use-regex +npx --yes uloop-cli@2.2.0 get-logs --search-text "Missing.*Component" --use-regex ``` ## Output diff --git a/.agents/skills/uloop-hello-world/SKILL.md b/.agents/skills/uloop-hello-world/SKILL.md index c1b7a61717..041630f302 100644 --- a/.agents/skills/uloop-hello-world/SKILL.md +++ b/.agents/skills/uloop-hello-world/SKILL.md @@ -3,14 +3,14 @@ name: uloop-hello-world description: "Sample hello world tool via uloop CLI. Use when you need to test the MCP tool system or see an example of custom tool implementation." --- -# uloop hello-world +# npx --yes uloop-cli@2.2.0 hello-world Personalized hello world tool with multi-language support. ## Usage ```bash -uloop hello-world [options] +npx --yes uloop-cli@2.2.0 hello-world [options] ``` ## Parameters @@ -25,16 +25,16 @@ uloop hello-world [options] ```bash # Default greeting -uloop hello-world +npx --yes uloop-cli@2.2.0 hello-world # Greet with custom name -uloop hello-world --name "Alice" +npx --yes uloop-cli@2.2.0 hello-world --name "Alice" # Japanese greeting -uloop hello-world --name "太郎" --language japanese +npx --yes uloop-cli@2.2.0 hello-world --name "太郎" --language japanese # Spanish greeting without timestamp -uloop hello-world --name "Carlos" --language spanish --include-timestamp false +npx --yes uloop-cli@2.2.0 hello-world --name "Carlos" --language spanish --include-timestamp false ``` ## Output diff --git a/.agents/skills/uloop-launch/SKILL.md b/.agents/skills/uloop-launch/SKILL.md index 411f481ba0..7bbcd969c3 100644 --- a/.agents/skills/uloop-launch/SKILL.md +++ b/.agents/skills/uloop-launch/SKILL.md @@ -1,19 +1,19 @@ --- name: uloop-launch -description: "Launch Unity project with matching Editor version via uloop CLI. Use when you need to: (1) Open a Unity project with the correct Editor version, (2) Restart Unity to apply changes, (3) Switch build target when launching." +description: "Use when Unity Editor is not running or needs a clean restart." --- -# uloop launch +# npx --yes uloop-cli@2.2.0 launch Launch Unity Editor with the correct version for a project. -`uloop launch` is not fire-and-forget. When Unity needs to start or restart, the command waits +`npx --yes uloop-cli@2.2.0 launch` is not fire-and-forget. When Unity needs to start or restart, the command waits until Unity is actually ready for CLI operations before it exits. ## Usage ```bash -uloop launch [project-path] [options] +npx --yes uloop-cli@2.2.0 launch [project-path] [options] ``` ## Parameters @@ -31,19 +31,19 @@ uloop launch [project-path] [options] ```bash # Search for Unity project in current directory and launch -uloop launch +npx --yes uloop-cli@2.2.0 launch # Launch specific project -uloop launch /path/to/project +npx --yes uloop-cli@2.2.0 launch /path/to/project # Restart Unity (kill existing and relaunch) -uloop launch -r +npx --yes uloop-cli@2.2.0 launch -r # Launch with build target -uloop launch -p Android +npx --yes uloop-cli@2.2.0 launch -p Android # Add project to Unity Hub without launching -uloop launch -a +npx --yes uloop-cli@2.2.0 launch -a ``` ## Output diff --git a/.agents/skills/uloop-raycast/SKILL.md b/.agents/skills/uloop-raycast/SKILL.md new file mode 100644 index 0000000000..6098734304 --- /dev/null +++ b/.agents/skills/uloop-raycast/SKILL.md @@ -0,0 +1,61 @@ +--- +name: uloop-raycast +description: "Raycast from Camera.main through a Game View coordinate. Use when you need to check what a screenshot coordinate would hit in 3D physics before clicking or long-pressing with simulate-mouse-input." +--- + +# npx --yes uloop-cli@2.2.0 raycast + +Check what a top-left Game View coordinate hits in 3D physics. + +## Usage + +```bash +npx --yes uloop-cli@2.2.0 raycast --x --y [--layer-mask ] [--max-distance ] +``` + +## Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Use `AnnotatedElements[].SimX`, `RaycastGridPoints[].InputX`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Use `AnnotatedElements[].SimY`, `RaycastGridPoints[].InputY`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--layer-mask` | number | Unity default raycast layers | Physics layer mask used by the raycast. | +| `--max-distance` | number | `1000` | Maximum raycast distance in world units. | + +## Coordinate System + +- `--x` / `--y` use the same top-left Game View input coordinates as `simulate-mouse-input`. +- Raw image pixels from `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. +- Do not flip Y in the caller. The tool converts internally: + +```text +unity_x = input_x +unity_y = gameViewHeight - input_y +``` + +## Examples + +```bash +# Check what is under a screenshot coordinate +npx --yes uloop-cli@2.2.0 raycast --x 960 --y 540 + +# Check only specific layers +npx --yes uloop-cli@2.2.0 raycast --x 960 --y 540 --layer-mask 1 +``` + +## Output + +Returns JSON with: +- `Success`: Whether the command completed +- `Message`: Status message +- `Hit`: Whether physics hit anything +- `HitGameObjectName` / `HitGameObjectPath`: Hit object identity when `Hit` is true +- `HitLayer` / `HitLayerName`: Hit object layer when `Hit` is true +- `Distance`, `HitPointX/Y/Z`, `HitNormalX/Y/Z`: Hit details when `Hit` is true +- `InputCoordinateSystem`, `UnityCoordinateSystem`, `GameViewWidth/Height`, `InputPositionX/Y`, `InjectedUnityPositionX/Y`, `CoordinateConversionFormula`: Coordinate conversion details + +## Notes + +- Requires an active `Camera.main`. +- Uses Unity Physics raycasts, not UI EventSystem raycasts. diff --git a/.agents/skills/uloop-record-input/SKILL.md b/.agents/skills/uloop-record-input/SKILL.md index 9e00ebcfcb..4833ac060f 100644 --- a/.agents/skills/uloop-record-input/SKILL.md +++ b/.agents/skills/uloop-record-input/SKILL.md @@ -1,9 +1,9 @@ --- name: uloop-record-input -description: "Record keyboard and mouse input during PlayMode into a JSON file. Use when you need to: (1) Capture human gameplay input for later replay, (2) Record input sequences for E2E testing, (3) Save input for bug reproduction. Captures Input System device-state diffs frame-by-frame in PlayMode and serializes them to JSON when stopped. Requires PlayMode and the New Input System." +description: "Record PlayMode keyboard and mouse input to JSON. Use to capture gameplay, bug repro, or E2E input sequences for replay." --- -# uloop record-input +# npx --yes uloop-cli@2.2.0 record-input Record keyboard and mouse input during PlayMode frame-by-frame into a JSON file. Captures key presses, mouse movement, clicks, and scroll events via Input System device state diffing. @@ -11,16 +11,16 @@ Record keyboard and mouse input during PlayMode frame-by-frame into a JSON file. ```bash # Start recording -uloop record-input --action Start +npx --yes uloop-cli@2.2.0 record-input --action Start # Start recording with key filter -uloop record-input --action Start --keys "W,A,S,D,Space" +npx --yes uloop-cli@2.2.0 record-input --action Start --keys "W,A,S,D,Space" # Stop recording and save -uloop record-input --action Stop +npx --yes uloop-cli@2.2.0 record-input --action Stop # Stop and save to specific path -uloop record-input --action Stop --output-path scripts/my-play.json +npx --yes uloop-cli@2.2.0 record-input --action Stop --output-path scripts/my-play.json ``` ## Parameters @@ -39,7 +39,7 @@ Replay injects input frame-by-frame, so the game must also be deterministic to p - Unity must be in **PlayMode** - **Input System package** must be installed (`com.unity.inputsystem`) -- Active Input Handling must be set to `Input System Package (New)` or `Both` in Player Settings +- Use this only when the project already uses the New Input System. ## Output diff --git a/.agents/skills/uloop-replay-input/SKILL.md b/.agents/skills/uloop-replay-input/SKILL.md index 1c9cb00081..41734194e1 100644 --- a/.agents/skills/uloop-replay-input/SKILL.md +++ b/.agents/skills/uloop-replay-input/SKILL.md @@ -1,9 +1,9 @@ --- name: uloop-replay-input -description: "Replay recorded input during PlayMode with frame-precise injection. Use when you need to: (1) Reproduce recorded gameplay exactly, (2) Run E2E tests from recorded input, (3) Generate demo videos with consistent input. Deserializes the JSON recording and pushes captured device states back into Mouse.current / Keyboard.current frame-by-frame in PlayMode. Requires PlayMode and the New Input System." +description: "Replay recorded PlayMode keyboard and mouse input. Use for exact gameplay reproduction, E2E runs, or consistent demos from JSON recordings." --- -# uloop replay-input +# npx --yes uloop-cli@2.2.0 replay-input Replay recorded keyboard and mouse input during PlayMode. Loads a JSON recording and injects input frame-by-frame via Input System with zero CLI overhead. Supports looping and progress monitoring. @@ -11,19 +11,19 @@ Replay recorded keyboard and mouse input during PlayMode. Loads a JSON recording ```bash # Start replay (auto-detect latest recording) -uloop replay-input --action Start +npx --yes uloop-cli@2.2.0 replay-input --action Start # Start replay with specific file -uloop replay-input --action Start --input-path scripts/my-play.json +npx --yes uloop-cli@2.2.0 replay-input --action Start --input-path scripts/my-play.json # Start replay with looping -uloop replay-input --action Start --loop true +npx --yes uloop-cli@2.2.0 replay-input --action Start --loop true # Check replay progress -uloop replay-input --action Status +npx --yes uloop-cli@2.2.0 replay-input --action Status # Stop replay -uloop replay-input --action Stop +npx --yes uloop-cli@2.2.0 replay-input --action Stop ``` ## Parameters @@ -39,6 +39,12 @@ uloop replay-input --action Stop Replay injects the exact same input frame-by-frame, but the game must also be deterministic to produce identical results. If replay output must be compared across runs, read [references/deterministic-replay.md](references/deterministic-replay.md) before interpreting failures. +## Prerequisites + +- Unity must be in **PlayMode** +- **Input System package** must be installed (`com.unity.inputsystem`) +- Use this only when the project already uses the New Input System. + ## Output Returns JSON with: diff --git a/.agents/skills/uloop-run-tests/SKILL.md b/.agents/skills/uloop-run-tests/SKILL.md index 9f4547659e..b4160d0e30 100644 --- a/.agents/skills/uloop-run-tests/SKILL.md +++ b/.agents/skills/uloop-run-tests/SKILL.md @@ -1,18 +1,20 @@ --- name: uloop-run-tests -description: "Execute Unity Test Runner and get detailed results. Use when you need to: (1) Run EditMode or PlayMode unit tests, (2) Verify code changes pass all tests, (3) Diagnose test failures with error messages and stack traces. Single-flight only — never run multiple `uloop run-tests` in parallel." +description: "Run Unity Test Runner and report detailed results. Use for EditMode/PlayMode tests, change verification, or failure diagnosis." --- -# uloop run-tests +# npx --yes uloop-cli@2.2.0 run-tests -Execute Unity Test Runner. When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. +Execute Unity Test Runner. This command requires the Unity Test Framework package (`com.unity.test-framework`). If that package is not installed, the command returns `Success: false` with an unsupported message and does not affect the other Unity CLI Loop tools. -Before executing tests, `uloop run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. +When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. + +Before executing tests, `npx --yes uloop-cli@2.2.0 run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. ## Usage ```bash -uloop run-tests [options] +npx --yes uloop-cli@2.2.0 run-tests [options] ``` ## Parameters @@ -34,19 +36,19 @@ uloop run-tests [options] ```bash # Run all EditMode tests -uloop run-tests +npx --yes uloop-cli@2.2.0 run-tests # Run PlayMode tests -uloop run-tests --test-mode PlayMode +npx --yes uloop-cli@2.2.0 run-tests --test-mode PlayMode # Save explicitly approved editor changes before running tests -uloop run-tests --save-before-run true +npx --yes uloop-cli@2.2.0 run-tests --save-before-run true # Run specific test -uloop run-tests --filter-type exact --filter-value "MyTest.TestMethod" +npx --yes uloop-cli@2.2.0 run-tests --filter-type exact --filter-value "MyTest.TestMethod" # Run tests matching pattern -uloop run-tests --filter-type regex --filter-value ".*Integration.*" +npx --yes uloop-cli@2.2.0 run-tests --filter-type regex --filter-value ".*Integration.*" ``` ## Output @@ -59,7 +61,7 @@ Returns JSON with: - `PassedCount` (number): Passed tests - `FailedCount` (number): Failed tests - `SkippedCount` (number): Skipped tests -- `XmlPath` (string): Path to NUnit XML result file. Empty string when no XML was saved (typically on `Success: true`); populated only when tests failed and the XML file exists on disk. +- `XmlPath` (string | null): Path to NUnit XML result file. `null` when no XML was saved; populated only when tests failed and the XML file exists on disk. ### XML Result File diff --git a/.agents/skills/uloop-screenshot/SKILL.md b/.agents/skills/uloop-screenshot/SKILL.md index 724ecedaa3..7295d521cc 100644 --- a/.agents/skills/uloop-screenshot/SKILL.md +++ b/.agents/skills/uloop-screenshot/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-screenshot -description: "Capture screenshots of Unity Editor windows as PNG files. Use when you need to: (1) Screenshot Game View, Scene View, Console, Inspector, or other windows, (2) Capture current visual state for debugging or documentation, (3) Save editor window appearance as PNG files with optional UI element annotations. Writes PNG files via uloop CLI." +description: "Capture Unity Editor windows or Game View rendering as PNG. Use for visual checks, debugging, documentation, or annotated UI element coordinates." --- -# uloop screenshot +# npx --yes uloop-cli@2.2.0 screenshot Take a screenshot of any Unity EditorWindow by name and save as PNG. ## Usage ```bash -uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements] [--elements-only] [--output-directory ] +npx --yes uloop-cli@2.2.0 screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] ``` ## Parameters @@ -22,8 +22,10 @@ uloop screenshot [--window-name ] [--resolution-scale ] [--match-mo | `--match-mode` | enum | `exact` | Window name matching mode: `exact`, `prefix`, or `contains`. Ignored when `--capture-mode rendering`. | | `--capture-mode` | enum | `window` | `window`=capture EditorWindow including toolbar, `rendering`=capture game rendering only (PlayMode required, coordinates match simulate-mouse) | | `--output-directory` | string | `""` | Output directory path for saving screenshots. When empty, uses default path (.uloop/outputs/Screenshots/). Accepts absolute paths. | -| `--annotate-elements` | boolean | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Only works with `--capture-mode rendering` in PlayMode. | -| `--elements-only` | boolean | `false` | Return only annotated element JSON without capturing a screenshot image. Requires `--annotate-elements` and `--capture-mode rendering` in PlayMode. | +| `--annotate-elements` | boolean value | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering` in PlayMode. | +| `--annotate-raycast-grid` | boolean value | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | +| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by collider and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | +| `--elements-only` | boolean value | `false` | Return only annotation JSON without capturing a screenshot image. Pass `true` or `false`; bare flags are not accepted. Requires `--annotate-elements true` or `--annotate-raycast-grid true`, and `--capture-mode rendering` in PlayMode. | ## Match Modes @@ -47,28 +49,41 @@ The window name is the text displayed in the window's title bar (tab). Common na ```bash # Take a screenshot of Game View (default) -uloop screenshot +npx --yes uloop-cli@2.2.0 screenshot -# Capture game rendering (coordinates match simulate-mouse, PlayMode required) -uloop screenshot --capture-mode rendering +# Capture game rendering (default scale coordinates match simulate-mouse, PlayMode required) +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering # Annotate interactive UI elements with index labels (for simulate-mouse workflow) -uloop screenshot --capture-mode rendering --annotate-elements +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true + +# Annotate 3D physics raycast candidate points +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true + +# Inspect hit layers, then rerun with the layer used by game input +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true + +# Annotate clustered 3D collider candidates on selected layers +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable # Get UI element coordinates without capturing an image (fastest) -uloop screenshot --capture-mode rendering --annotate-elements --elements-only +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true --elements-only true + +# Get clustered 3D collider coordinates without capturing an image +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true # Take a screenshot of Scene View -uloop screenshot --window-name Scene +npx --yes uloop-cli@2.2.0 screenshot --window-name Scene # Capture all windows starting with "Project" (prefix match) -uloop screenshot --window-name Project --match-mode prefix +npx --yes uloop-cli@2.2.0 screenshot --window-name Project --match-mode prefix # Save screenshot to a specific directory -uloop screenshot --output-directory /tmp/screenshots +npx --yes uloop-cli@2.2.0 screenshot --output-directory /tmp/screenshots # Combine options -uloop screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots +npx --yes uloop-cli@2.2.0 screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots ``` ## Output @@ -80,17 +95,41 @@ Returns JSON with: - `FileSizeBytes`: Size of the saved file in bytes - `Width`: Captured image width in pixels - `Height`: Captured image height in pixels - - `CoordinateSystem`: `"gameView"` or `"window"` + - `ImageCoordinateSystem`: `"top-left-game-view"` for `--capture-mode rendering`, or `"top-left-window"` for window captures - `ResolutionScale`: Resolution scale used for capture - - `YOffset`: Y offset used for gameView coordinate conversion - - `AnnotatedElements`: Array of annotated UI element metadata. Empty unless `--annotate-elements` is used. + - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates + - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools + - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates + - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` + - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid true --raycast-layer-mask ` is used. + - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. + - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. Empty when a mask is provided. -For `AnnotatedElements` fields and gameView coordinate conversion, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. +For `AnnotatedElements` fields and Game View coordinates, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. When multiple windows match (e.g., multiple Inspector windows or when using `contains` mode), all matching windows are captured with numbered filenames (e.g., `Inspector_1_*.png`, `Inspector_2_*.png`). ## Notes -- Use `uloop focus-window` first if needed +- Use `npx --yes uloop-cli@2.2.0 focus-window` first if needed - Target window must be open in Unity Editor - Window name matching is always case-insensitive +- Boolean options use value syntax. Write `--annotate-elements true`, `--annotate-raycast-grid true`, and `--elements-only true`; do not use bare boolean flags. +- Use `--capture-mode rendering` for coordinates that should be passed to `simulate-mouse-input`, `simulate-mouse-ui`, or `raycast`. +- Use `ScreenshotToInputFormula` before passing raw image pixels to mouse tools. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates. +- To discover a useful physics layer, first run `--annotate-raycast-grid true` without `--raycast-layer-mask`, inspect `RaycastLayerSummaries`, then rerun with `--raycast-layer-mask `. +- Use `--raycast-layer-mask` with layer names from `find-game-objects` or project code when the game input code raycasts only specific layers. Layers hidden by Camera.main.cullingMask are not reported because they are not visible to the screenshot camera. +- Clustered `PhysicsCollider` entries avoid points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element, including world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. The screenshot overlay draws the reachable sampled-cell outline, while `BoundsMinX/Y` and `BoundsMaxX/Y` remain the axis-aligned sampled-cell bbox for JSON consumers. Use `SimX/SimY` for the actual click point. If every sampled hit in the cluster is covered, that collider is omitted. + +### Raycast Annotation Demo Scene + +Use `Assets/Scenes/RaycastAnnotationDemoScene.unity` as the maintained visual fixture for raycast annotation checks. Enter PlayMode, then run: + +```bash +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 +``` + +The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile outlines should shrink around the blocker, outlines should follow reachable sampled cells, and `SimX/SimY` should stay inside `BoundsMinX/Y` and `BoundsMaxX/Y`. + +Use `Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity` as the perspective visual fixture. It contains rotated multi-collider placement areas, separated cell groups, a visual foreground blocker, and a center `GraphicRaycaster` UI blocker. Enter PlayMode, then run the same command above. Verify the outline follows the visible angled placement areas without reverting to a large axis-aligned rectangle. +- Do not use `window` captures as mouse-input coordinates because they include Unity Editor chrome. diff --git a/.agents/skills/uloop-screenshot/references/annotated-elements.md b/.agents/skills/uloop-screenshot/references/annotated-elements.md index 283109ccc2..f6aba22c84 100644 --- a/.agents/skills/uloop-screenshot/references/annotated-elements.md +++ b/.agents/skills/uloop-screenshot/references/annotated-elements.md @@ -1,37 +1,62 @@ # Annotated Elements and Coordinates -Read this when using `uloop screenshot --capture-mode rendering --annotate-elements` to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. +Read this when using `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. ## AnnotatedElements Fields -`AnnotatedElements` is empty unless `--annotate-elements` is used. Entries are sorted by z-order, frontmost first. Each item contains: +`AnnotatedElements` is empty unless `--annotate-elements true` is used, or unless `--annotate-raycast-grid true --raycast-layer-mask ` adds clustered 3D collider candidates. UI entries are sorted by z-order, frontmost first. Each item contains: - `Label`: Index label in JSON (`A` = frontmost, `B` = next, ...). Screenshot labels also include the interaction hint, such as `A / CLICK` or `B / DRAG`. - `Name`: Element name - `Path`: Hierarchy path from the scene root, for example `Canvas/Panel/Button`. Use this as `simulate-mouse-ui --target-path` when bypassing raycast blockers. -- `Type`: Element type (`Button`, `Toggle`, `Slider`, `Dropdown`, `InputField`, `Scrollbar`, `Draggable`, `DropTarget`, `Selectable`) -- `Interaction`: Derived interaction category (`Click`, `Drag`, `Drop`, `Text`). Use this to choose between `simulate-mouse-ui --action Click` and drag actions. -- `SimX`, `SimY`: Center position in simulate-mouse coordinates. Use these directly with `--x` and `--y`. -- `BoundsMinX`, `BoundsMinY`, `BoundsMaxX`, `BoundsMaxY`: Bounding box in simulate-mouse coordinates +- `Type`: Element type (`Button`, `Toggle`, `Slider`, `Dropdown`, `InputField`, `Scrollbar`, `Draggable`, `DropTarget`, `Selectable`, `PhysicsCollider`) +- `Interaction`: Derived interaction category (`Click`, `Drag`, `Drop`, `Text`) or `Raycast` for clustered physics collider entries. Use this to choose between `simulate-mouse-ui --action Click`, drag actions, or `simulate-mouse-input`/`raycast`. +- `Layer`: Physics layer name for `PhysicsCollider` entries. Empty for UI entries. +- `Components`: Collider and MonoBehaviour component type names from the hit GameObject for `PhysicsCollider` entries. Empty for UI entries. +- `SimX`, `SimY`: Target click position in top-left Game View coordinates. For UI entries this is the element center; for `PhysicsCollider` entries this is a representative sampled hit. Use these directly with `simulate-mouse-ui --x/--y`, `simulate-mouse-input --x/--y`, or `raycast --x/--y`. +- `BoundsMinX`, `BoundsMinY`, `BoundsMaxX`, `BoundsMaxY`: Bounding box in simulate-mouse coordinates. For `PhysicsCollider` entries, this is the axis-aligned sampled-cell coverage box from reachable raycast hits, not a guarantee that every interior point is clickable. - `SortingOrder`: Canvas sorting order. Higher values are in front. - `SiblingIndex`: Transform sibling index under the element's direct parent. Do not use it as a reliable z-order signal across nested UI hierarchies. +## RaycastLayerSummaries Fields + +`RaycastLayerSummaries` is populated when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. It is built from dense raycast samples, while `RaycastGridPoints` remains the coarse 5x5 annotated grid. + +- `Layer`: Physics layer name to pass to `--raycast-layer-mask` +- `LayerIndex`: Unity physics layer index +- `HitCount`: Dense raycast hit count for the layer +- `RepresentativeObjectPath`: Hierarchy path for the object with the most hits on that layer. Ties are resolved alphabetically by path. + +Entries are sorted by `HitCount` descending, then `LayerIndex` ascending. + ## Coordinate Conversion -When `CoordinateSystem` is `"gameView"`, convert image pixel coordinates to simulate-mouse coordinates: +When `ImageCoordinateSystem` is `"top-left-game-view"`, convert raw image pixel coordinates from `screenshot --capture-mode rendering` with the formula returned in `ScreenshotToInputFormula`: ```text -sim_x = image_x / ResolutionScale -sim_y = image_y / ResolutionScale + YOffset +input_x = image_x / resolutionScale +input_y = image_y / resolutionScale + imageToInputOffsetY ``` -When `ResolutionScale` is `1.0`, this simplifies to: +When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0`, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are always returned as mouse-input coordinates, so pass those values directly. + +For `PhysicsCollider` entries, `SimX/SimY` is a real sampled raycast hit nearest to the reachable cluster centroid. This avoids synthetic center points that may fall into empty space for L-shaped or ring-shaped collider coverage. Always use `SimX/SimY` for clicking; use `BoundsMinX/Y` and `BoundsMaxX/Y` only as a sampled coverage guide. + +`--raycast-layer-mask` filters by the requested physics layers and Camera.main.cullingMask. A layer that is requested but hidden from the active camera is treated as not visible and will not produce `PhysicsCollider` entries. + +For clustered `PhysicsCollider` entries, points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element are treated as covered by UI. This includes world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. Bounds, screenshot outlines, and `SimX/SimY` are derived from the remaining reachable samples; if every sampled hit in that collider cluster is covered, the collider is omitted from `AnnotatedElements`. + +`PhysicsCollider` bounds expand each reachable sample by half the dense raycast sampling step in X and Y, then clamp the result to the captured Game View area. The screenshot overlay draws only the outer edges of those reachable sample cells, so angled, L-shaped, separated, and partially UI-covered hit regions do not become one large rectangle. `BoundsMinX/Y` and `BoundsMaxX/Y` are still an axis-aligned bbox for JSON consumers, may extend up to half a sample step past the visible collider edge, and do not guarantee that every interior point is clickable. + +The mouse input tools convert internally to Unity Input System coordinates: ```text -sim_x = image_x -sim_y = image_y + YOffset +unity_x = input_x +unity_y = gameViewHeight - input_y ``` +Do not flip Y in the caller. + ## Annotation Readability Annotated screenshots compensate border thickness for `ResolutionScale`, so the saved PNG keeps the intended outline width after downscaling. The neutral contrast borders are 2 output pixels each, and the colored middle border is 4 output pixels. Label outlines are also compensated and are separated from element borders by a 4 output pixel gap. diff --git a/.agents/skills/uloop-simulate-keyboard/SKILL.md b/.agents/skills/uloop-simulate-keyboard/SKILL.md index 574794c166..8a6dddc613 100644 --- a/.agents/skills/uloop-simulate-keyboard/SKILL.md +++ b/.agents/skills/uloop-simulate-keyboard/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-keyboard -description: "Simulate keyboard key input in PlayMode via Input System. Use when you need to: (1) Press game control keys like WASD, Space, or Shift during PlayMode, (2) Hold keys down for continuous movement or actions, (3) Combine multiple held keys for complex input like Shift+W for sprint. Injects into Unity Input System (`Keyboard.current`); requires PlayMode and the New Input System." +description: "Simulate keyboard input in PlayMode through Unity Input System. Use for key presses, holds, releases, and game controls such as WASD or Space." context: fork --- @@ -10,15 +10,15 @@ Simulate keyboard input on Unity PlayMode: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. Execute the appropriate `uloop simulate-keyboard` command -3. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` +1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) +2. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-keyboard` command +3. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` 4. Report what happened ## Tool Reference ```bash -uloop simulate-keyboard --action --key [options] +npx --yes uloop-cli@2.2.0 simulate-keyboard --action --key [options] ``` ### Parameters @@ -55,20 +55,20 @@ uloop simulate-keyboard --action --key [options] ```bash # One-shot key press (tap W once) -uloop simulate-keyboard --action Press --key W +npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key W # Jump (tap Space) -uloop simulate-keyboard --action Press --key Space +npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key Space # Hold W for 2 seconds (move forward) -uloop simulate-keyboard --action Press --key W --duration 2.0 +npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key W --duration 2.0 # Sprint forward (hold Shift + W, then release) -uloop simulate-keyboard --action KeyDown --key LeftShift -uloop simulate-keyboard --action KeyDown --key W -uloop screenshot --capture-mode rendering -uloop simulate-keyboard --action KeyUp --key W -uloop simulate-keyboard --action KeyUp --key LeftShift +npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyDown --key LeftShift +npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyDown --key W +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering +npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyUp --key W +npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyUp --key LeftShift ``` ## Output @@ -82,6 +82,6 @@ Returns JSON with: ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** (`com.unity.inputsystem`) must be installed -- Active Input Handling must be set to **Input System Package (New)** or **Both** in Player Settings +- **Input System package** must be installed (`com.unity.inputsystem`) +- Use this only when the project already uses the New Input System. - Game code must read input via Input System API (e.g. `Keyboard.current[Key.W].isPressed`), not legacy `Input.GetKey()` diff --git a/.agents/skills/uloop-simulate-mouse-input/SKILL.md b/.agents/skills/uloop-simulate-mouse-input/SKILL.md index 8a4f9e0981..c7b8c4ae2f 100644 --- a/.agents/skills/uloop-simulate-mouse-input/SKILL.md +++ b/.agents/skills/uloop-simulate-mouse-input/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-mouse-input -description: "Simulate mouse input in PlayMode for gameplay code that reads Unity Input System Mouse.current. Use when you need to: (1) Click or right-click in games that read Mouse.current button state, (2) Inject mouse delta for FPS camera control, (3) Inject scroll wheel for hotbar switching or zoom. Requires PlayMode and the New Input System; for EventSystem UI elements, use simulate-mouse-ui instead." +description: "Simulate Mouse.current input in PlayMode through Unity Input System. Use for gameplay clicks, mouse delta, or scroll; use simulate-mouse-ui for EventSystem UI elements." context: fork --- @@ -10,16 +10,16 @@ Simulate mouse input via Input System in Unity PlayMode: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. For Click/LongPress: determine the target screen position (use `uloop screenshot` to find coordinates) -3. Execute the appropriate `uloop simulate-mouse-input` command -4. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` +1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) +2. For Click/LongPress: determine the target Game View input position from annotated `SimX`/`SimY`, raycast-grid `InputX`/`InputY`, or raw image pixels converted with `ScreenshotToInputFormula` +3. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-mouse-input` command +4. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` 5. Report what happened ## Tool Reference ```bash -uloop simulate-mouse-input --action [options] +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action [options] ``` ### Parameters @@ -27,8 +27,8 @@ uloop simulate-mouse-input --action [options] | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--action` | enum | `Click` | `Click`, `LongPress`, `MoveDelta`, `SmoothDelta`, `Scroll` | -| `--x` | number | `0` | Target X position (origin: top-left). Used by Click and LongPress. | -| `--y` | number | `0` | Target Y position (origin: top-left). Used by Click and LongPress. | +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Used by Click and LongPress. Use `AnnotatedElements[].SimX`, `RaycastGridPoints[].InputX`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Used by Click and LongPress. Use `AnnotatedElements[].SimY`, `RaycastGridPoints[].InputY`, or raw image pixels converted with `ScreenshotToInputFormula`. | | `--button` | enum | `Left` | Mouse button: `Left`, `Right`, `Middle`. Used by Click and LongPress. | | `--duration` | number | `0` | Hold duration for LongPress, or interpolation duration for SmoothDelta (seconds). For Click, 0 = one-shot tap. | | `--delta-x` | number | `0` | Delta X in pixels for MoveDelta/SmoothDelta. Positive = right. | @@ -58,7 +58,7 @@ uloop simulate-mouse-input --action [options] | Scenario | Tool | |----------|------| | Click a Unity UI Button (IPointerClickHandler) | `simulate-mouse-ui` | -| Destroy a block in Minecraft (reads `Mouse.current.leftButton`) | `simulate-mouse-input` when the project uses the New Input System; otherwise prefer `execute-dynamic-code` for a project-specific workaround | +| Destroy a block in Minecraft (reads `Mouse.current.leftButton`) | `simulate-mouse-input` when the project uses the New Input System | | Place a block with right-click | `simulate-mouse-input --button Right` when the project uses the New Input System | | Drag a UI slider | `simulate-mouse-ui --action Drag` | | Look around with mouse (FPS camera) | `simulate-mouse-input --action MoveDelta` when the project uses the New Input System | @@ -67,34 +67,48 @@ uloop simulate-mouse-input --action [options] ## Examples ```bash -# Left-click at screen center (for game logic) -uloop simulate-mouse-input --action Click --x 400 --y 300 +# Left-click at the Game View center (for game logic) +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Click --x 400 --y 300 # Right-click at screen center (e.g. place block) -uloop simulate-mouse-input --action Click --x 400 --y 300 --button Right +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Click --x 400 --y 300 --button Right # Hold left-click for 2 seconds (e.g. mine block) -uloop simulate-mouse-input --action LongPress --x 400 --y 300 --duration 2.0 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action LongPress --x 400 --y 300 --duration 2.0 # Look right (FPS camera) -uloop simulate-mouse-input --action MoveDelta --delta-x 100 --delta-y 0 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action MoveDelta --delta-x 100 --delta-y 0 # Scroll up (e.g. previous hotbar slot) -uloop simulate-mouse-input --action Scroll --scroll-y 120 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Scroll --scroll-y 120 # Scroll down (e.g. next hotbar slot) -uloop simulate-mouse-input --action Scroll --scroll-y -120 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Scroll --scroll-y -120 # Smooth camera pan right over 0.5 seconds -uloop simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 ``` +## Coordinate System + +- `--x` / `--y` use **top-left Game View coordinates**. +- Raw image pixels from `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. +- Do not flip Y in the caller. The tool converts internally for Unity Input System: + +```text +unity_x = input_x +unity_y = gameViewHeight - input_y +``` + +- `Mouse.current.position` uses bottom-left Unity coordinates, so the value read inside Unity may show the converted Y. + ## Prerequisites - Unity must be in **PlayMode** - **Input System package** must be installed (`com.unity.inputsystem`) - Game code must read input via Input System API (e.g. `Mouse.current.leftButton.wasPressedThisFrame`) -- If the target project cannot use the New Input System, prefer `execute-dynamic-code` for a project-specific workaround instead of changing project settings just to use this tool +- Use this only when the project already uses the New Input System. ## Output @@ -103,7 +117,12 @@ Returns JSON with: - `Message`: Status message - `Action`: Echoes which action was executed (`Click`, `LongPress`, `MoveDelta`, `SmoothDelta`, or `Scroll`) - `Button`: Which button was used (nullable string; populated for `Click` / `LongPress`, null otherwise) -- `PositionX`: Target X coordinate (nullable float; populated for `Click` / `LongPress`) -- `PositionY`: Target Y coordinate (nullable float; populated for `Click` / `LongPress`) - -These are the only six fields. There is no `DeltaX`, `DeltaY`, `ScrollX`, `ScrollY`, `Duration`, or hit-element field in the response — only the issued action, button, and target position are echoed back. Verify visual outcome with a follow-up screenshot. +- `PositionX` / `PositionY`: Target top-left Game View coordinates (nullable float; populated for `Click` / `LongPress`) +- `InputCoordinateSystem`: `"top-left-game-view"` for click/long-press coordinates +- `UnityCoordinateSystem`: `"bottom-left-game-view"` for the injected `Mouse.current.position` +- `GameViewWidth` / `GameViewHeight`: Game View size used for conversion +- `InputPositionX` / `InputPositionY`: Coordinates received from the caller +- `InjectedUnityPositionX` / `InjectedUnityPositionY`: Coordinates injected into `Mouse.current.position` +- `CoordinateConversionFormula`: Conversion formula used by the tool + +Verify visual outcome with a follow-up screenshot. diff --git a/.agents/skills/uloop-simulate-mouse-ui/SKILL.md b/.agents/skills/uloop-simulate-mouse-ui/SKILL.md index ffd6160091..89a174cf13 100644 --- a/.agents/skills/uloop-simulate-mouse-ui/SKILL.md +++ b/.agents/skills/uloop-simulate-mouse-ui/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-mouse-ui -description: "Simulate mouse click, long-press, and drag on PlayMode UI elements via EventSystem screen coordinates from annotated screenshots. Use when you need to: (1) Click buttons or interactive UI elements during PlayMode testing, (2) Drag UI elements between annotated screen positions, (3) Long-press or hold a drag for sustained pointer interactions. First get target coordinates with `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` and use `AnnotatedElements[].SimX` / `SimY`; for gameplay code that reads Mouse.current, use simulate-mouse-input instead." +description: "Simulate PlayMode EventSystem UI mouse actions using top-left Game View coordinates. Use for UI clicks, long-presses, or drags from annotated screenshots." context: fork --- @@ -10,17 +10,17 @@ Simulate mouse interaction on Unity PlayMode UI: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` +1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) +2. Get UI element info: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true --elements-only true` 3. Use the `AnnotatedElements` array to find the target element by `Label`, `Name`, or `Path` (A=frontmost, B=next, ...). Use `Interaction` to distinguish click targets from drag/drop/text targets, then use `SimX`/`SimY` directly as `--x`/`--y` coordinates. -4. Execute the appropriate `uloop simulate-mouse-ui` command -5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements` +4. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-mouse-ui` command +5. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true` 6. Report what happened ## Tool Reference ```bash -uloop simulate-mouse-ui --action --x --y [options] +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action --x --y [options] ``` ### Parameters @@ -28,10 +28,10 @@ uloop simulate-mouse-ui --action --x --y [options] | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--action` | enum | `Click` | `Click`, `Drag`, `DragStart`, `DragMove`, `DragEnd`, `LongPress` | -| `--x` | number | `0` | Target X position in screen pixels (origin: top-left). For Drag action, this is the destination. | -| `--y` | number | `0` | Target Y position in screen pixels (origin: top-left). For Drag action, this is the destination. | -| `--from-x` | number | `0` | Start X position for Drag action. Drag starts here and moves to x,y. | -| `--from-y` | number | `0` | Start Y position for Drag action. Drag starts here and moves to x,y. | +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Used by Click, LongPress, DragStart, DragMove, and DragEnd; for Drag, this is the destination. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Used by Click, LongPress, DragStart, DragMove, and DragEnd; for Drag, this is the destination. | +| `--from-x` | number | `0` | Start X position in Game View pixels for Drag action. Drag starts here and moves to x,y. | +| `--from-y` | number | `0` | Start Y position in Game View pixels for Drag action. Drag starts here and moves to x,y. | | `--drag-speed` | number | `2000` | Drag speed in pixels per second (0 for instant). 2000 is fast (default), 200 is slow enough to watch. Applies to Drag, DragMove, and DragEnd actions. | | `--duration` | number | `0.5` | Hold duration in seconds for LongPress action. | | `--button` | enum | `Left` | Mouse button. `Click` and `LongPress` support `Left`, `Right`, and `Middle`. Drag actions support `Left` only; other buttons return an error. | @@ -67,7 +67,7 @@ uloop simulate-mouse-ui --action --x --y [options] ## Coordinate System - Origin is **top-left** (0, 0) -- All positions are in **screen pixels** +- All positions are in **top-left Game View pixels** - Get coordinates from `AnnotatedElements` JSON (`SimX`/`SimY`) — do NOT look up GameObject positions - Clicking or long-pressing on empty space (no UI element) still succeeds with a message indicating no element was hit - Dragging on empty space (no draggable UI element) returns `Success = false` @@ -77,35 +77,35 @@ uloop simulate-mouse-ui --action --x --y [options] ## Examples ```bash -# Click a button at screen position -uloop simulate-mouse-ui --action Click --x 400 --y 300 +# Click a button at a Game View position +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Click --x 400 --y 300 # Force-click a button behind a raycast blocker by path -uloop simulate-mouse-ui --action Click --x 400 --y 300 --bypass-raycast true --target-path "Canvas/Panel/Button" +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Click --x 400 --y 300 --bypass-raycast true --target-path "Canvas/Panel/Button" # Force-long-press a button behind a raycast blocker by path -uloop simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 --bypass-raycast true --target-path "Canvas/Panel/Button" +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 --bypass-raycast true --target-path "Canvas/Panel/Button" # Force-drag an item behind a raycast blocker by path -uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" # Force-drag and dispatch Drop to a blocked drop zone -uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" --drop-target-path "Canvas/DropZone" +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" --drop-target-path "Canvas/DropZone" # Long-press a button for 3 seconds -uloop simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 # One-shot drag (start to end in one call) -uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 # Slow drag for visual inspection -uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --drag-speed 200 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --drag-speed 200 # Split drag with hold (for inspection between steps) -uloop simulate-mouse-ui --action DragStart --x 400 --y 300 -uloop screenshot --window-name Game -uloop simulate-mouse-ui --action DragMove --x 500 --y 300 -uloop simulate-mouse-ui --action DragEnd --x 600 --y 300 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragStart --x 400 --y 300 +npx --yes uloop-cli@2.2.0 screenshot --window-name Game +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragMove --x 500 --y 300 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragEnd --x 600 --y 300 ``` ## Prerequisites @@ -127,6 +127,6 @@ Returns JSON with: - `EndPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `EndPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. +These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true`. Note: Click and LongPress on empty space (no UI element) still return `Success = true` with `HitGameObjectName = null`. Drag actions on empty space return `Success = false`. diff --git a/.claude/skills/uloop-clear-console/SKILL.md b/.claude/skills/uloop-clear-console/SKILL.md index 6ba18c90f3..5f9c1daf66 100644 --- a/.claude/skills/uloop-clear-console/SKILL.md +++ b/.claude/skills/uloop-clear-console/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-clear-console -description: "Clear all Unity Console log entries. Use when you need to: (1) Clear console before running tests or compilation, (2) Start a fresh debugging session, (3) Remove noisy logs to isolate specific output." +description: "Clear Unity Console entries. Use before compile, tests, or debugging when stale logs would hide the current result." --- -# uloop clear-console +# npx --yes uloop-cli@2.2.0 clear-console Clear Unity console logs. ## Usage ```bash -uloop clear-console [--add-confirmation-message] +npx --yes uloop-cli@2.2.0 clear-console [--add-confirmation-message] ``` ## Parameters @@ -29,10 +29,10 @@ uloop clear-console [--add-confirmation-message] ```bash # Clear console -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console # Clear with confirmation -uloop clear-console --add-confirmation-message +npx --yes uloop-cli@2.2.0 clear-console --add-confirmation-message ``` ## Output diff --git a/.claude/skills/uloop-compile/SKILL.md b/.claude/skills/uloop-compile/SKILL.md index f13d06abd1..f8df207d0b 100644 --- a/.claude/skills/uloop-compile/SKILL.md +++ b/.claude/skills/uloop-compile/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-compile -description: "Compile Unity project and report errors/warnings. Use when you need to: (1) Verify code compiles after C# file edits, (2) Check for compile errors before testing, (3) Force full recompilation with Domain Reload. Returns error and warning counts." +description: "Compile the Unity project and report errors/warnings. Use after C# edits or when a full Domain Reload compile is needed." --- -# uloop compile +# npx --yes uloop-cli@2.2.0 compile Execute Unity project compilation. ## Usage ```bash -uloop compile [--force-recompile] [--wait-for-domain-reload] +npx --yes uloop-cli@2.2.0 compile [--force-recompile] [--wait-for-domain-reload] ``` ## Parameters @@ -30,16 +30,16 @@ uloop compile [--force-recompile] [--wait-for-domain-reload] ```bash # Check compilation -uloop compile +npx --yes uloop-cli@2.2.0 compile # Force full recompilation -uloop compile --force-recompile +npx --yes uloop-cli@2.2.0 compile --force-recompile # Force recompilation and wait for Domain Reload completion -uloop compile --force-recompile true --wait-for-domain-reload true +npx --yes uloop-cli@2.2.0 compile --force-recompile true --wait-for-domain-reload true # Wait for Domain Reload completion even without force recompilation -uloop compile --force-recompile false --wait-for-domain-reload true +npx --yes uloop-cli@2.2.0 compile --force-recompile false --wait-for-domain-reload true ``` ## Output @@ -56,15 +56,15 @@ Diagnose the failure mode before retrying. **Stale lock files** (CLI hangs or shows "Unity is busy" while Unity Editor *is* running): ```bash -uloop fix +npx --yes uloop-cli@2.2.0 fix ``` -This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `uloop compile`. +This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `npx --yes uloop-cli@2.2.0 compile`. **Unity Editor not running** (CLI returns a connection failure and no Unity process is alive): ```bash -uloop launch +npx --yes uloop-cli@2.2.0 launch ``` -`uloop launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `uloop compile`. +`npx --yes uloop-cli@2.2.0 launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `npx --yes uloop-cli@2.2.0 compile`. diff --git a/.claude/skills/uloop-control-play-mode/SKILL.md b/.claude/skills/uloop-control-play-mode/SKILL.md index 220eeebe91..d95f5a6baa 100644 --- a/.claude/skills/uloop-control-play-mode/SKILL.md +++ b/.claude/skills/uloop-control-play-mode/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-control-play-mode -description: "Control Unity Editor play mode (play/stop/pause). Use when you need to: (1) Start play mode to test game behavior, (2) Stop play mode to return to edit mode, (3) Pause play mode for frame-by-frame inspection." +description: "Control Unity Editor Play Mode. Use to start, stop, or pause Play Mode for runtime behavior checks and frame inspection." --- -# uloop control-play-mode +# npx --yes uloop-cli@2.2.0 control-play-mode Control Unity Editor play mode (play/stop/pause). ## Usage ```bash -uloop control-play-mode [options] +npx --yes uloop-cli@2.2.0 control-play-mode [options] ``` ## Parameters @@ -29,13 +29,13 @@ uloop control-play-mode [options] ```bash # Start play mode -uloop control-play-mode --action Play +npx --yes uloop-cli@2.2.0 control-play-mode --action Play # Stop play mode -uloop control-play-mode --action Stop +npx --yes uloop-cli@2.2.0 control-play-mode --action Stop # Pause play mode -uloop control-play-mode --action Pause +npx --yes uloop-cli@2.2.0 control-play-mode --action Pause ``` ## Output diff --git a/.claude/skills/uloop-execute-dynamic-code/SKILL.md b/.claude/skills/uloop-execute-dynamic-code/SKILL.md index 189ed4d1c2..7d1c49e5d7 100644 --- a/.claude/skills/uloop-execute-dynamic-code/SKILL.md +++ b/.claude/skills/uloop-execute-dynamic-code/SKILL.md @@ -6,7 +6,7 @@ context: fork # Task -Execute the following request using `uloop execute-dynamic-code`: $ARGUMENTS +Execute the following request using `npx --yes uloop-cli@2.2.0 execute-dynamic-code`: $ARGUMENTS For basic selected GameObject discovery or property inspection, use `find-game-objects --search-mode Selected` before this tool. Use this tool after the built-in inspection tools are not enough or when you need to modify Unity state. @@ -14,8 +14,8 @@ For basic selected GameObject discovery or property inspection, use `find-game-o 1. Read the relevant reference file(s) from the Code Examples section below 2. Construct C# code based on the reference examples -3. For multiline snippets, write the C# statements to a temporary `.csx` file and execute `uloop execute-dynamic-code --code-file ` -4. Use `uloop execute-dynamic-code --code ''` only for short one-line snippets +3. For multiline snippets, write the C# statements to a temporary `.csx` file and execute `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code-file ` +4. Use `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code ''` only for short one-line snippets 5. If execution fails, adjust code and retry 6. Report the execution result @@ -23,7 +23,7 @@ For basic selected GameObject discovery or property inspection, use `find-game-o - `--code ''`: Inline C# statements to execute. Use this for one-line snippets only. - `--code-file `: Read C# statements from a UTF-8 file. Prefer this for multiline snippets, especially on PowerShell, because Windows `.cmd` shims can lose lines from multiline inline arguments before `uloop` receives them. -- **Shell quoting**: bash/zsh uses single quotes, for example `uloop execute-dynamic-code --code 'using UnityEngine; return Mathf.PI;'`. PowerShell single-quoted strings can contain normal double quotes, for example `uloop execute-dynamic-code --code 'Debug.Log("Hello!");'`. +- **Shell quoting**: bash/zsh uses single quotes, for example `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'using UnityEngine; return Mathf.PI;'`. PowerShell single-quoted strings can contain normal double quotes, for example `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'Debug.Log("Hello!");'`. - `--parameters {}` (advanced, optional): Pass an object when reusing a snippet with varying data or when keeping values outside the code. Values are exposed as `parameters["param0"]`, `parameters["param1"]`, and so on. Omit this flag for most snippets, and pass an object instead of a JSON string. - `--compile-only true` (optional): Compile the snippet without executing it. Use this when you want Roslyn diagnostics before running new code. @@ -53,7 +53,7 @@ Returns JSON: - `DiagnosticsSummary`: string|null — compact summary when diagnostics are available - `Diagnostics`: object[] — structured diagnostics; same shape as `CompilationErrors`, usually populated together with it -Use `uloop get-logs` to retrieve `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` messages emitted by the snippet. On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, security violation, cancellation, or an "execution in progress" rejection, all of which return empty `CompilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. +Use `npx --yes uloop-cli@2.2.0 get-logs` to retrieve `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` messages emitted by the snippet. On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, security violation, cancellation, or an "execution in progress" rejection, all of which return empty `CompilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. ## Code Examples by Category diff --git a/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md b/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md index de0ed73fc1..31ddf7adc1 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md @@ -184,6 +184,8 @@ return $"Asset has {dependencies.Length} dependencies"; ## Refresh AssetDatabase +Use this after terminal-based asset file changes when Unity needs to import them and generate `.meta` files. + ```csharp using UnityEditor; diff --git a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index e14ceca3d9..6a764d5c2d 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -31,7 +31,7 @@ Use these patterns when you need shell-safe inline code: Single-quote the whole snippet and keep C# string literals unchanged. ```powershell -uloop execute-dynamic-code --code 'return "Hello from PowerShell";' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from PowerShell";' ``` ### Single quotes inside inline C# code @@ -39,7 +39,7 @@ uloop execute-dynamic-code --code 'return "Hello from PowerShell";' If the C# snippet itself contains a single quote, double it inside the PowerShell single-quoted string. ```powershell -uloop execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' ``` ### JSON-like values passed via `--parameters` @@ -47,7 +47,7 @@ uloop execute-dynamic-code --code 'char initial = ''A''; return initial.ToString Wrap the whole expression in single quotes so PowerShell passes the inner double quotes through unchanged. ```powershell -uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' ``` ### Multi-line C# snippets @@ -64,7 +64,7 @@ if (obj == null) return "Player not found"; return obj.name; '@ -uloop execute-dynamic-code --code $code +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code $code ``` You can combine multi-line code with multi-line parameters the same way. @@ -78,7 +78,7 @@ $parameters = @' {"param0":"Hello from PowerShell"} '@ -uloop execute-dynamic-code --code $code --parameters $parameters +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code $code --parameters $parameters ``` ## Click UI Button by Path @@ -168,7 +168,7 @@ return $"Moved {player.name} to {targetPos}"; # Tool Combination Workflows -Examples of combining `execute-dynamic-code` with other uloop tools for multi-step PlayMode automation. +Examples of combining `execute-dynamic-code` with other npx --yes uloop-cli@2.2.0 tools for multi-step PlayMode automation. ## find-game-objects → Click Button @@ -177,7 +177,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```powershell -uloop find-game-objects --required-components UnityEngine.UI.Button --include-inactive false +npx --yes uloop-cli@2.2.0 find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-components true +npx --yes uloop-cli@2.2.0 get-hierarchy --root-path 'Canvas' --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button @@ -243,7 +243,7 @@ return "Clicked PlayButton"; **Step 2**: Capture Game View to verify the result ```powershell -uloop screenshot --window-name Game +npx --yes uloop-cli@2.2.0 screenshot --window-name Game ``` ## Execute Action → Check Logs for Side Effects @@ -253,7 +253,7 @@ Run an action then inspect Unity Console logs to verify expected behavior. **Step 1**: Clear console before the action ```powershell -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console ``` **Step 2**: Perform the action @@ -277,7 +277,7 @@ return "Invoked TakeDamage(50)"; **Step 3**: Check logs for expected output ```powershell -uloop get-logs --log-type Log --search-text 'damage' +npx --yes uloop-cli@2.2.0 get-logs --log-type Log --search-text 'damage' ``` ## Full Automation: Play → Act → Capture → Stop @@ -287,13 +287,13 @@ End-to-end test flow: start Play mode, perform actions, capture evidence, stop. **Step 0**: Clear console to isolate this run ```powershell -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console ``` **Step 1**: Start Play mode ```powershell -uloop control-play-mode --action Play +npx --yes uloop-cli@2.2.0 control-play-mode --action Play ``` **Step 2**: Wait for scene initialization, then find and click a button @@ -314,17 +314,17 @@ return $"Clicked {startBtn.gameObject.name}"; **Step 3**: Capture screenshot as evidence ```powershell -uloop screenshot --window-name Game +npx --yes uloop-cli@2.2.0 screenshot --window-name Game ``` **Step 4**: Check logs for errors ```powershell -uloop get-logs --log-type Error +npx --yes uloop-cli@2.2.0 get-logs --log-type Error ``` **Step 5**: Stop Play mode ```powershell -uloop control-play-mode --action Stop +npx --yes uloop-cli@2.2.0 control-play-mode --action Stop ``` diff --git a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md index d82b7c6149..c44ea92235 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md @@ -31,7 +31,7 @@ Use these patterns when you need shell-safe inline code: Single-quote the whole snippet and keep C# string literals unchanged. ```zsh -uloop execute-dynamic-code --code 'return "Hello from zsh";' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from zsh";' ``` ### Single quotes inside inline C# code @@ -39,7 +39,7 @@ uloop execute-dynamic-code --code 'return "Hello from zsh";' If the C# snippet itself contains a single quote, close and reopen the shell string with `'\''`. ```zsh -uloop execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToString();' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToString();' ``` ### JSON-like values passed via `--parameters` @@ -47,7 +47,7 @@ uloop execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToSt Wrap the whole expression in single quotes so the shell does not interpret double quotes. ```zsh -uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from zsh"}' +npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from zsh"}' ``` ## Click UI Button by Path @@ -137,7 +137,7 @@ return $"Moved {player.name} to {targetPos}"; # Tool Combination Workflows -Examples of combining `execute-dynamic-code` with other uloop tools for multi-step PlayMode automation. +Examples of combining `execute-dynamic-code` with other npx --yes uloop-cli@2.2.0 tools for multi-step PlayMode automation. ## find-game-objects → Click Button @@ -146,7 +146,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```zsh -uloop find-game-objects --required-components UnityEngine.UI.Button --include-inactive false +npx --yes uloop-cli@2.2.0 find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -172,7 +172,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```zsh -uloop get-hierarchy --root-path "Canvas" --max-depth 3 --include-components true +npx --yes uloop-cli@2.2.0 get-hierarchy --root-path "Canvas" --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button @@ -212,7 +212,7 @@ return "Clicked PlayButton"; **Step 2**: Capture Game View to verify the result ```zsh -uloop screenshot --window-name Game +npx --yes uloop-cli@2.2.0 screenshot --window-name Game ``` ## Execute Action → Check Logs for Side Effects @@ -222,7 +222,7 @@ Run an action then inspect Unity Console logs to verify expected behavior. **Step 1**: Clear console before the action ```zsh -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console ``` **Step 2**: Perform the action @@ -246,7 +246,7 @@ return "Invoked TakeDamage(50)"; **Step 3**: Check logs for expected output ```zsh -uloop get-logs --log-type Log --search-text "damage" +npx --yes uloop-cli@2.2.0 get-logs --log-type Log --search-text "damage" ``` ## Full Automation: Play → Act → Capture → Stop @@ -256,13 +256,13 @@ End-to-end test flow: start Play mode, perform actions, capture evidence, stop. **Step 0**: Clear console to isolate this run ```zsh -uloop clear-console +npx --yes uloop-cli@2.2.0 clear-console ``` **Step 1**: Start Play mode ```zsh -uloop control-play-mode --action Play +npx --yes uloop-cli@2.2.0 control-play-mode --action Play ``` **Step 2**: Wait for scene initialization, then find and click a button @@ -283,17 +283,17 @@ return $"Clicked {startBtn.gameObject.name}"; **Step 3**: Capture screenshot as evidence ```zsh -uloop screenshot --window-name Game +npx --yes uloop-cli@2.2.0 screenshot --window-name Game ``` **Step 4**: Check logs for errors ```zsh -uloop get-logs --log-type Error +npx --yes uloop-cli@2.2.0 get-logs --log-type Error ``` **Step 5**: Stop Play mode ```zsh -uloop control-play-mode --action Stop +npx --yes uloop-cli@2.2.0 control-play-mode --action Stop ``` diff --git a/.claude/skills/uloop-find-game-objects/SKILL.md b/.claude/skills/uloop-find-game-objects/SKILL.md index 9c809e658c..3d80fce320 100644 --- a/.claude/skills/uloop-find-game-objects/SKILL.md +++ b/.claude/skills/uloop-find-game-objects/SKILL.md @@ -1,9 +1,9 @@ --- name: uloop-find-game-objects -description: "Use first when the user asks about the currently selected GameObject in the Unity Hierarchy. Inspect selected object details with `--search-mode Selected` before using `execute-dynamic-code`. Use when you need to: (1) Get details and component properties for selected GameObject(s), (2) Search for objects by name, regex, or path, (3) Find objects with specific components, tags, or layers. Use get-hierarchy when the child tree under the selection is needed. Returns hierarchy paths, active state, tags, layers, and components (or writes to a file when multiple GameObjects are selected)." +description: "Find or inspect Unity GameObjects, especially objects the user currently selected in the Hierarchy. Use for details, components, tags, layers, or name/path searches." --- -# uloop find-game-objects +# npx --yes uloop-cli@2.2.0 find-game-objects Find GameObjects with search criteria or get details for currently selected Hierarchy objects. @@ -12,7 +12,7 @@ Use this before `execute-dynamic-code` when identifying or inspecting selected G ## Usage ```bash -uloop find-game-objects [options] +npx --yes uloop-cli@2.2.0 find-game-objects [options] ``` ## Parameters @@ -48,22 +48,22 @@ uloop find-game-objects [options] ```bash # Find by name -uloop find-game-objects --name-pattern "Player" +npx --yes uloop-cli@2.2.0 find-game-objects --name-pattern "Player" # Find with component -uloop find-game-objects --required-components Rigidbody +npx --yes uloop-cli@2.2.0 find-game-objects --required-components Rigidbody # Find by tag -uloop find-game-objects --tag "Enemy" +npx --yes uloop-cli@2.2.0 find-game-objects --tag "Enemy" # Regex search -uloop find-game-objects --name-pattern "UI_.*" --search-mode Regex +npx --yes uloop-cli@2.2.0 find-game-objects --name-pattern "UI_.*" --search-mode Regex # Get selected GameObjects -uloop find-game-objects --search-mode Selected +npx --yes uloop-cli@2.2.0 find-game-objects --search-mode Selected # Get selected including inactive -uloop find-game-objects --search-mode Selected --include-inactive +npx --yes uloop-cli@2.2.0 find-game-objects --search-mode Selected --include-inactive ``` ## Output diff --git a/.claude/skills/uloop-focus-window/SKILL.md b/.claude/skills/uloop-focus-window/SKILL.md index 87dbc35876..5552b76e86 100644 --- a/.claude/skills/uloop-focus-window/SKILL.md +++ b/.claude/skills/uloop-focus-window/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-focus-window -description: "Bring Unity Editor window to front via uloop CLI. Use when you need to: (1) Focus Unity Editor before capturing screenshots, (2) Ensure Unity window is visible for visual checks, (3) Bring Unity to foreground for user interaction." +description: "Bring the Unity Editor window to front. Use when Unity must be visible for visual checks or user-facing interaction." --- -# uloop focus-window +# npx --yes uloop-cli@2.2.0 focus-window Bring Unity Editor window to front using OS-level commands. ## Usage ```bash -uloop focus-window +npx --yes uloop-cli@2.2.0 focus-window ``` ## Parameters @@ -27,7 +27,7 @@ None. ```bash # Focus Unity Editor -uloop focus-window +npx --yes uloop-cli@2.2.0 focus-window ``` ## Output @@ -42,5 +42,5 @@ These are the only two fields. There is no PID, window-handle, or platform field - **Works even when Unity is busy** (compiling, domain reload, etc.) - Uses OS-level commands (osascript on macOS, PowerShell on Windows) -- Useful before `uloop capture-unity-window` to ensure the target window is visible +- Useful before `npx --yes uloop-cli@2.2.0 capture-unity-window` to ensure the target window is visible - Brings the main Unity Editor window to the foreground diff --git a/.claude/skills/uloop-get-hierarchy/SKILL.md b/.claude/skills/uloop-get-hierarchy/SKILL.md index 976e0de5a6..a460089e65 100644 --- a/.claude/skills/uloop-get-hierarchy/SKILL.md +++ b/.claude/skills/uloop-get-hierarchy/SKILL.md @@ -1,9 +1,9 @@ --- name: uloop-get-hierarchy -description: "Get Unity scene hierarchy as a structured tree from all roots, a root path, or the current Hierarchy selection. Use this when you need the child tree, parent-child structure, or descendants under selected GameObject(s) with `--use-selection`. Use find-game-objects for selected object details and component properties. Hierarchy data is written to a JSON file on disk and the response returns the file path (not the tree inline) — open the file to read the structure." +description: "Get the Unity scene hierarchy as a structured tree. Use for parent-child structure, descendants, roots, or subtrees under objects the user currently selected." --- -# uloop get-hierarchy +# npx --yes uloop-cli@2.2.0 get-hierarchy Get Unity Hierarchy structure from the whole scene, a root path, or selected Hierarchy objects. @@ -12,7 +12,7 @@ Use this for hierarchy structure, especially descendants under the current selec ## Usage ```bash -uloop get-hierarchy [options] +npx --yes uloop-cli@2.2.0 get-hierarchy [options] ``` ## Parameters @@ -37,19 +37,19 @@ uloop get-hierarchy [options] ```bash # Get entire hierarchy -uloop get-hierarchy +npx --yes uloop-cli@2.2.0 get-hierarchy # Get hierarchy from specific root -uloop get-hierarchy --root-path "Canvas/UI" +npx --yes uloop-cli@2.2.0 get-hierarchy --root-path "Canvas/UI" # Limit depth -uloop get-hierarchy --max-depth 2 +npx --yes uloop-cli@2.2.0 get-hierarchy --max-depth 2 # Without components -uloop get-hierarchy --include-components false +npx --yes uloop-cli@2.2.0 get-hierarchy --include-components false # Get hierarchy from currently selected GameObjects -uloop get-hierarchy --use-selection +npx --yes uloop-cli@2.2.0 get-hierarchy --use-selection ``` ## Output diff --git a/.claude/skills/uloop-get-logs/SKILL.md b/.claude/skills/uloop-get-logs/SKILL.md index 1ca03fc116..ab8ddeda45 100644 --- a/.claude/skills/uloop-get-logs/SKILL.md +++ b/.claude/skills/uloop-get-logs/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-get-logs -description: "Retrieve current Unity Console entries via uloop CLI. Use when you need to: (1) inspect errors, warnings, or logs after compile, tests, PlayMode, or dynamic code execution, (2) search current Console messages or stack traces, (3) confirm whether a recent Unity operation emitted logs. Prefer this over reading Editor.log or Unity log files for normal Console contents; use log files only for startup, crash, freeze, or uloop connection failures." +description: "Read current Unity Console entries from a running Editor. Use during bug investigation after compile, tests, PlayMode, or dynamic code to inspect logs, warnings, errors, and stack traces." --- -# uloop get-logs +# npx --yes uloop-cli@2.2.0 get-logs Retrieve logs from Unity Console. ## Usage ```bash -uloop get-logs [options] +npx --yes uloop-cli@2.2.0 get-logs [options] ``` ## Parameters @@ -34,16 +34,16 @@ uloop get-logs [options] ```bash # Get all logs -uloop get-logs +npx --yes uloop-cli@2.2.0 get-logs # Get only errors -uloop get-logs --log-type Error +npx --yes uloop-cli@2.2.0 get-logs --log-type Error # Search for specific text -uloop get-logs --search-text "NullReference" +npx --yes uloop-cli@2.2.0 get-logs --search-text "NullReference" # Regex search -uloop get-logs --search-text "Missing.*Component" --use-regex +npx --yes uloop-cli@2.2.0 get-logs --search-text "Missing.*Component" --use-regex ``` ## Output diff --git a/.claude/skills/uloop-hello-world/SKILL.md b/.claude/skills/uloop-hello-world/SKILL.md index c1b7a61717..041630f302 100644 --- a/.claude/skills/uloop-hello-world/SKILL.md +++ b/.claude/skills/uloop-hello-world/SKILL.md @@ -3,14 +3,14 @@ name: uloop-hello-world description: "Sample hello world tool via uloop CLI. Use when you need to test the MCP tool system or see an example of custom tool implementation." --- -# uloop hello-world +# npx --yes uloop-cli@2.2.0 hello-world Personalized hello world tool with multi-language support. ## Usage ```bash -uloop hello-world [options] +npx --yes uloop-cli@2.2.0 hello-world [options] ``` ## Parameters @@ -25,16 +25,16 @@ uloop hello-world [options] ```bash # Default greeting -uloop hello-world +npx --yes uloop-cli@2.2.0 hello-world # Greet with custom name -uloop hello-world --name "Alice" +npx --yes uloop-cli@2.2.0 hello-world --name "Alice" # Japanese greeting -uloop hello-world --name "太郎" --language japanese +npx --yes uloop-cli@2.2.0 hello-world --name "太郎" --language japanese # Spanish greeting without timestamp -uloop hello-world --name "Carlos" --language spanish --include-timestamp false +npx --yes uloop-cli@2.2.0 hello-world --name "Carlos" --language spanish --include-timestamp false ``` ## Output diff --git a/.claude/skills/uloop-launch/SKILL.md b/.claude/skills/uloop-launch/SKILL.md index 411f481ba0..7bbcd969c3 100644 --- a/.claude/skills/uloop-launch/SKILL.md +++ b/.claude/skills/uloop-launch/SKILL.md @@ -1,19 +1,19 @@ --- name: uloop-launch -description: "Launch Unity project with matching Editor version via uloop CLI. Use when you need to: (1) Open a Unity project with the correct Editor version, (2) Restart Unity to apply changes, (3) Switch build target when launching." +description: "Use when Unity Editor is not running or needs a clean restart." --- -# uloop launch +# npx --yes uloop-cli@2.2.0 launch Launch Unity Editor with the correct version for a project. -`uloop launch` is not fire-and-forget. When Unity needs to start or restart, the command waits +`npx --yes uloop-cli@2.2.0 launch` is not fire-and-forget. When Unity needs to start or restart, the command waits until Unity is actually ready for CLI operations before it exits. ## Usage ```bash -uloop launch [project-path] [options] +npx --yes uloop-cli@2.2.0 launch [project-path] [options] ``` ## Parameters @@ -31,19 +31,19 @@ uloop launch [project-path] [options] ```bash # Search for Unity project in current directory and launch -uloop launch +npx --yes uloop-cli@2.2.0 launch # Launch specific project -uloop launch /path/to/project +npx --yes uloop-cli@2.2.0 launch /path/to/project # Restart Unity (kill existing and relaunch) -uloop launch -r +npx --yes uloop-cli@2.2.0 launch -r # Launch with build target -uloop launch -p Android +npx --yes uloop-cli@2.2.0 launch -p Android # Add project to Unity Hub without launching -uloop launch -a +npx --yes uloop-cli@2.2.0 launch -a ``` ## Output diff --git a/.claude/skills/uloop-raycast/SKILL.md b/.claude/skills/uloop-raycast/SKILL.md new file mode 100644 index 0000000000..6098734304 --- /dev/null +++ b/.claude/skills/uloop-raycast/SKILL.md @@ -0,0 +1,61 @@ +--- +name: uloop-raycast +description: "Raycast from Camera.main through a Game View coordinate. Use when you need to check what a screenshot coordinate would hit in 3D physics before clicking or long-pressing with simulate-mouse-input." +--- + +# npx --yes uloop-cli@2.2.0 raycast + +Check what a top-left Game View coordinate hits in 3D physics. + +## Usage + +```bash +npx --yes uloop-cli@2.2.0 raycast --x --y [--layer-mask ] [--max-distance ] +``` + +## Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Use `AnnotatedElements[].SimX`, `RaycastGridPoints[].InputX`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Use `AnnotatedElements[].SimY`, `RaycastGridPoints[].InputY`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--layer-mask` | number | Unity default raycast layers | Physics layer mask used by the raycast. | +| `--max-distance` | number | `1000` | Maximum raycast distance in world units. | + +## Coordinate System + +- `--x` / `--y` use the same top-left Game View input coordinates as `simulate-mouse-input`. +- Raw image pixels from `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. +- Do not flip Y in the caller. The tool converts internally: + +```text +unity_x = input_x +unity_y = gameViewHeight - input_y +``` + +## Examples + +```bash +# Check what is under a screenshot coordinate +npx --yes uloop-cli@2.2.0 raycast --x 960 --y 540 + +# Check only specific layers +npx --yes uloop-cli@2.2.0 raycast --x 960 --y 540 --layer-mask 1 +``` + +## Output + +Returns JSON with: +- `Success`: Whether the command completed +- `Message`: Status message +- `Hit`: Whether physics hit anything +- `HitGameObjectName` / `HitGameObjectPath`: Hit object identity when `Hit` is true +- `HitLayer` / `HitLayerName`: Hit object layer when `Hit` is true +- `Distance`, `HitPointX/Y/Z`, `HitNormalX/Y/Z`: Hit details when `Hit` is true +- `InputCoordinateSystem`, `UnityCoordinateSystem`, `GameViewWidth/Height`, `InputPositionX/Y`, `InjectedUnityPositionX/Y`, `CoordinateConversionFormula`: Coordinate conversion details + +## Notes + +- Requires an active `Camera.main`. +- Uses Unity Physics raycasts, not UI EventSystem raycasts. diff --git a/.claude/skills/uloop-record-input/SKILL.md b/.claude/skills/uloop-record-input/SKILL.md index 9e00ebcfcb..4833ac060f 100644 --- a/.claude/skills/uloop-record-input/SKILL.md +++ b/.claude/skills/uloop-record-input/SKILL.md @@ -1,9 +1,9 @@ --- name: uloop-record-input -description: "Record keyboard and mouse input during PlayMode into a JSON file. Use when you need to: (1) Capture human gameplay input for later replay, (2) Record input sequences for E2E testing, (3) Save input for bug reproduction. Captures Input System device-state diffs frame-by-frame in PlayMode and serializes them to JSON when stopped. Requires PlayMode and the New Input System." +description: "Record PlayMode keyboard and mouse input to JSON. Use to capture gameplay, bug repro, or E2E input sequences for replay." --- -# uloop record-input +# npx --yes uloop-cli@2.2.0 record-input Record keyboard and mouse input during PlayMode frame-by-frame into a JSON file. Captures key presses, mouse movement, clicks, and scroll events via Input System device state diffing. @@ -11,16 +11,16 @@ Record keyboard and mouse input during PlayMode frame-by-frame into a JSON file. ```bash # Start recording -uloop record-input --action Start +npx --yes uloop-cli@2.2.0 record-input --action Start # Start recording with key filter -uloop record-input --action Start --keys "W,A,S,D,Space" +npx --yes uloop-cli@2.2.0 record-input --action Start --keys "W,A,S,D,Space" # Stop recording and save -uloop record-input --action Stop +npx --yes uloop-cli@2.2.0 record-input --action Stop # Stop and save to specific path -uloop record-input --action Stop --output-path scripts/my-play.json +npx --yes uloop-cli@2.2.0 record-input --action Stop --output-path scripts/my-play.json ``` ## Parameters @@ -39,7 +39,7 @@ Replay injects input frame-by-frame, so the game must also be deterministic to p - Unity must be in **PlayMode** - **Input System package** must be installed (`com.unity.inputsystem`) -- Active Input Handling must be set to `Input System Package (New)` or `Both` in Player Settings +- Use this only when the project already uses the New Input System. ## Output diff --git a/.claude/skills/uloop-replay-input/SKILL.md b/.claude/skills/uloop-replay-input/SKILL.md index 1c9cb00081..41734194e1 100644 --- a/.claude/skills/uloop-replay-input/SKILL.md +++ b/.claude/skills/uloop-replay-input/SKILL.md @@ -1,9 +1,9 @@ --- name: uloop-replay-input -description: "Replay recorded input during PlayMode with frame-precise injection. Use when you need to: (1) Reproduce recorded gameplay exactly, (2) Run E2E tests from recorded input, (3) Generate demo videos with consistent input. Deserializes the JSON recording and pushes captured device states back into Mouse.current / Keyboard.current frame-by-frame in PlayMode. Requires PlayMode and the New Input System." +description: "Replay recorded PlayMode keyboard and mouse input. Use for exact gameplay reproduction, E2E runs, or consistent demos from JSON recordings." --- -# uloop replay-input +# npx --yes uloop-cli@2.2.0 replay-input Replay recorded keyboard and mouse input during PlayMode. Loads a JSON recording and injects input frame-by-frame via Input System with zero CLI overhead. Supports looping and progress monitoring. @@ -11,19 +11,19 @@ Replay recorded keyboard and mouse input during PlayMode. Loads a JSON recording ```bash # Start replay (auto-detect latest recording) -uloop replay-input --action Start +npx --yes uloop-cli@2.2.0 replay-input --action Start # Start replay with specific file -uloop replay-input --action Start --input-path scripts/my-play.json +npx --yes uloop-cli@2.2.0 replay-input --action Start --input-path scripts/my-play.json # Start replay with looping -uloop replay-input --action Start --loop true +npx --yes uloop-cli@2.2.0 replay-input --action Start --loop true # Check replay progress -uloop replay-input --action Status +npx --yes uloop-cli@2.2.0 replay-input --action Status # Stop replay -uloop replay-input --action Stop +npx --yes uloop-cli@2.2.0 replay-input --action Stop ``` ## Parameters @@ -39,6 +39,12 @@ uloop replay-input --action Stop Replay injects the exact same input frame-by-frame, but the game must also be deterministic to produce identical results. If replay output must be compared across runs, read [references/deterministic-replay.md](references/deterministic-replay.md) before interpreting failures. +## Prerequisites + +- Unity must be in **PlayMode** +- **Input System package** must be installed (`com.unity.inputsystem`) +- Use this only when the project already uses the New Input System. + ## Output Returns JSON with: diff --git a/.claude/skills/uloop-run-tests/SKILL.md b/.claude/skills/uloop-run-tests/SKILL.md index 9f4547659e..b4160d0e30 100644 --- a/.claude/skills/uloop-run-tests/SKILL.md +++ b/.claude/skills/uloop-run-tests/SKILL.md @@ -1,18 +1,20 @@ --- name: uloop-run-tests -description: "Execute Unity Test Runner and get detailed results. Use when you need to: (1) Run EditMode or PlayMode unit tests, (2) Verify code changes pass all tests, (3) Diagnose test failures with error messages and stack traces. Single-flight only — never run multiple `uloop run-tests` in parallel." +description: "Run Unity Test Runner and report detailed results. Use for EditMode/PlayMode tests, change verification, or failure diagnosis." --- -# uloop run-tests +# npx --yes uloop-cli@2.2.0 run-tests -Execute Unity Test Runner. When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. +Execute Unity Test Runner. This command requires the Unity Test Framework package (`com.unity.test-framework`). If that package is not installed, the command returns `Success: false` with an unsupported message and does not affect the other Unity CLI Loop tools. -Before executing tests, `uloop run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. +When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. + +Before executing tests, `npx --yes uloop-cli@2.2.0 run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. ## Usage ```bash -uloop run-tests [options] +npx --yes uloop-cli@2.2.0 run-tests [options] ``` ## Parameters @@ -34,19 +36,19 @@ uloop run-tests [options] ```bash # Run all EditMode tests -uloop run-tests +npx --yes uloop-cli@2.2.0 run-tests # Run PlayMode tests -uloop run-tests --test-mode PlayMode +npx --yes uloop-cli@2.2.0 run-tests --test-mode PlayMode # Save explicitly approved editor changes before running tests -uloop run-tests --save-before-run true +npx --yes uloop-cli@2.2.0 run-tests --save-before-run true # Run specific test -uloop run-tests --filter-type exact --filter-value "MyTest.TestMethod" +npx --yes uloop-cli@2.2.0 run-tests --filter-type exact --filter-value "MyTest.TestMethod" # Run tests matching pattern -uloop run-tests --filter-type regex --filter-value ".*Integration.*" +npx --yes uloop-cli@2.2.0 run-tests --filter-type regex --filter-value ".*Integration.*" ``` ## Output @@ -59,7 +61,7 @@ Returns JSON with: - `PassedCount` (number): Passed tests - `FailedCount` (number): Failed tests - `SkippedCount` (number): Skipped tests -- `XmlPath` (string): Path to NUnit XML result file. Empty string when no XML was saved (typically on `Success: true`); populated only when tests failed and the XML file exists on disk. +- `XmlPath` (string | null): Path to NUnit XML result file. `null` when no XML was saved; populated only when tests failed and the XML file exists on disk. ### XML Result File diff --git a/.claude/skills/uloop-screenshot/SKILL.md b/.claude/skills/uloop-screenshot/SKILL.md index 724ecedaa3..7295d521cc 100644 --- a/.claude/skills/uloop-screenshot/SKILL.md +++ b/.claude/skills/uloop-screenshot/SKILL.md @@ -1,16 +1,16 @@ --- name: uloop-screenshot -description: "Capture screenshots of Unity Editor windows as PNG files. Use when you need to: (1) Screenshot Game View, Scene View, Console, Inspector, or other windows, (2) Capture current visual state for debugging or documentation, (3) Save editor window appearance as PNG files with optional UI element annotations. Writes PNG files via uloop CLI." +description: "Capture Unity Editor windows or Game View rendering as PNG. Use for visual checks, debugging, documentation, or annotated UI element coordinates." --- -# uloop screenshot +# npx --yes uloop-cli@2.2.0 screenshot Take a screenshot of any Unity EditorWindow by name and save as PNG. ## Usage ```bash -uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements] [--elements-only] [--output-directory ] +npx --yes uloop-cli@2.2.0 screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] ``` ## Parameters @@ -22,8 +22,10 @@ uloop screenshot [--window-name ] [--resolution-scale ] [--match-mo | `--match-mode` | enum | `exact` | Window name matching mode: `exact`, `prefix`, or `contains`. Ignored when `--capture-mode rendering`. | | `--capture-mode` | enum | `window` | `window`=capture EditorWindow including toolbar, `rendering`=capture game rendering only (PlayMode required, coordinates match simulate-mouse) | | `--output-directory` | string | `""` | Output directory path for saving screenshots. When empty, uses default path (.uloop/outputs/Screenshots/). Accepts absolute paths. | -| `--annotate-elements` | boolean | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Only works with `--capture-mode rendering` in PlayMode. | -| `--elements-only` | boolean | `false` | Return only annotated element JSON without capturing a screenshot image. Requires `--annotate-elements` and `--capture-mode rendering` in PlayMode. | +| `--annotate-elements` | boolean value | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering` in PlayMode. | +| `--annotate-raycast-grid` | boolean value | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | +| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by collider and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | +| `--elements-only` | boolean value | `false` | Return only annotation JSON without capturing a screenshot image. Pass `true` or `false`; bare flags are not accepted. Requires `--annotate-elements true` or `--annotate-raycast-grid true`, and `--capture-mode rendering` in PlayMode. | ## Match Modes @@ -47,28 +49,41 @@ The window name is the text displayed in the window's title bar (tab). Common na ```bash # Take a screenshot of Game View (default) -uloop screenshot +npx --yes uloop-cli@2.2.0 screenshot -# Capture game rendering (coordinates match simulate-mouse, PlayMode required) -uloop screenshot --capture-mode rendering +# Capture game rendering (default scale coordinates match simulate-mouse, PlayMode required) +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering # Annotate interactive UI elements with index labels (for simulate-mouse workflow) -uloop screenshot --capture-mode rendering --annotate-elements +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true + +# Annotate 3D physics raycast candidate points +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true + +# Inspect hit layers, then rerun with the layer used by game input +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true + +# Annotate clustered 3D collider candidates on selected layers +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable # Get UI element coordinates without capturing an image (fastest) -uloop screenshot --capture-mode rendering --annotate-elements --elements-only +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true --elements-only true + +# Get clustered 3D collider coordinates without capturing an image +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true # Take a screenshot of Scene View -uloop screenshot --window-name Scene +npx --yes uloop-cli@2.2.0 screenshot --window-name Scene # Capture all windows starting with "Project" (prefix match) -uloop screenshot --window-name Project --match-mode prefix +npx --yes uloop-cli@2.2.0 screenshot --window-name Project --match-mode prefix # Save screenshot to a specific directory -uloop screenshot --output-directory /tmp/screenshots +npx --yes uloop-cli@2.2.0 screenshot --output-directory /tmp/screenshots # Combine options -uloop screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots +npx --yes uloop-cli@2.2.0 screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots ``` ## Output @@ -80,17 +95,41 @@ Returns JSON with: - `FileSizeBytes`: Size of the saved file in bytes - `Width`: Captured image width in pixels - `Height`: Captured image height in pixels - - `CoordinateSystem`: `"gameView"` or `"window"` + - `ImageCoordinateSystem`: `"top-left-game-view"` for `--capture-mode rendering`, or `"top-left-window"` for window captures - `ResolutionScale`: Resolution scale used for capture - - `YOffset`: Y offset used for gameView coordinate conversion - - `AnnotatedElements`: Array of annotated UI element metadata. Empty unless `--annotate-elements` is used. + - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates + - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools + - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates + - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` + - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid true --raycast-layer-mask ` is used. + - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. + - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. Empty when a mask is provided. -For `AnnotatedElements` fields and gameView coordinate conversion, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. +For `AnnotatedElements` fields and Game View coordinates, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. When multiple windows match (e.g., multiple Inspector windows or when using `contains` mode), all matching windows are captured with numbered filenames (e.g., `Inspector_1_*.png`, `Inspector_2_*.png`). ## Notes -- Use `uloop focus-window` first if needed +- Use `npx --yes uloop-cli@2.2.0 focus-window` first if needed - Target window must be open in Unity Editor - Window name matching is always case-insensitive +- Boolean options use value syntax. Write `--annotate-elements true`, `--annotate-raycast-grid true`, and `--elements-only true`; do not use bare boolean flags. +- Use `--capture-mode rendering` for coordinates that should be passed to `simulate-mouse-input`, `simulate-mouse-ui`, or `raycast`. +- Use `ScreenshotToInputFormula` before passing raw image pixels to mouse tools. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates. +- To discover a useful physics layer, first run `--annotate-raycast-grid true` without `--raycast-layer-mask`, inspect `RaycastLayerSummaries`, then rerun with `--raycast-layer-mask `. +- Use `--raycast-layer-mask` with layer names from `find-game-objects` or project code when the game input code raycasts only specific layers. Layers hidden by Camera.main.cullingMask are not reported because they are not visible to the screenshot camera. +- Clustered `PhysicsCollider` entries avoid points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element, including world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. The screenshot overlay draws the reachable sampled-cell outline, while `BoundsMinX/Y` and `BoundsMaxX/Y` remain the axis-aligned sampled-cell bbox for JSON consumers. Use `SimX/SimY` for the actual click point. If every sampled hit in the cluster is covered, that collider is omitted. + +### Raycast Annotation Demo Scene + +Use `Assets/Scenes/RaycastAnnotationDemoScene.unity` as the maintained visual fixture for raycast annotation checks. Enter PlayMode, then run: + +```bash +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 +``` + +The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile outlines should shrink around the blocker, outlines should follow reachable sampled cells, and `SimX/SimY` should stay inside `BoundsMinX/Y` and `BoundsMaxX/Y`. + +Use `Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity` as the perspective visual fixture. It contains rotated multi-collider placement areas, separated cell groups, a visual foreground blocker, and a center `GraphicRaycaster` UI blocker. Enter PlayMode, then run the same command above. Verify the outline follows the visible angled placement areas without reverting to a large axis-aligned rectangle. +- Do not use `window` captures as mouse-input coordinates because they include Unity Editor chrome. diff --git a/.claude/skills/uloop-screenshot/references/annotated-elements.md b/.claude/skills/uloop-screenshot/references/annotated-elements.md index 283109ccc2..f6aba22c84 100644 --- a/.claude/skills/uloop-screenshot/references/annotated-elements.md +++ b/.claude/skills/uloop-screenshot/references/annotated-elements.md @@ -1,37 +1,62 @@ # Annotated Elements and Coordinates -Read this when using `uloop screenshot --capture-mode rendering --annotate-elements` to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. +Read this when using `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. ## AnnotatedElements Fields -`AnnotatedElements` is empty unless `--annotate-elements` is used. Entries are sorted by z-order, frontmost first. Each item contains: +`AnnotatedElements` is empty unless `--annotate-elements true` is used, or unless `--annotate-raycast-grid true --raycast-layer-mask ` adds clustered 3D collider candidates. UI entries are sorted by z-order, frontmost first. Each item contains: - `Label`: Index label in JSON (`A` = frontmost, `B` = next, ...). Screenshot labels also include the interaction hint, such as `A / CLICK` or `B / DRAG`. - `Name`: Element name - `Path`: Hierarchy path from the scene root, for example `Canvas/Panel/Button`. Use this as `simulate-mouse-ui --target-path` when bypassing raycast blockers. -- `Type`: Element type (`Button`, `Toggle`, `Slider`, `Dropdown`, `InputField`, `Scrollbar`, `Draggable`, `DropTarget`, `Selectable`) -- `Interaction`: Derived interaction category (`Click`, `Drag`, `Drop`, `Text`). Use this to choose between `simulate-mouse-ui --action Click` and drag actions. -- `SimX`, `SimY`: Center position in simulate-mouse coordinates. Use these directly with `--x` and `--y`. -- `BoundsMinX`, `BoundsMinY`, `BoundsMaxX`, `BoundsMaxY`: Bounding box in simulate-mouse coordinates +- `Type`: Element type (`Button`, `Toggle`, `Slider`, `Dropdown`, `InputField`, `Scrollbar`, `Draggable`, `DropTarget`, `Selectable`, `PhysicsCollider`) +- `Interaction`: Derived interaction category (`Click`, `Drag`, `Drop`, `Text`) or `Raycast` for clustered physics collider entries. Use this to choose between `simulate-mouse-ui --action Click`, drag actions, or `simulate-mouse-input`/`raycast`. +- `Layer`: Physics layer name for `PhysicsCollider` entries. Empty for UI entries. +- `Components`: Collider and MonoBehaviour component type names from the hit GameObject for `PhysicsCollider` entries. Empty for UI entries. +- `SimX`, `SimY`: Target click position in top-left Game View coordinates. For UI entries this is the element center; for `PhysicsCollider` entries this is a representative sampled hit. Use these directly with `simulate-mouse-ui --x/--y`, `simulate-mouse-input --x/--y`, or `raycast --x/--y`. +- `BoundsMinX`, `BoundsMinY`, `BoundsMaxX`, `BoundsMaxY`: Bounding box in simulate-mouse coordinates. For `PhysicsCollider` entries, this is the axis-aligned sampled-cell coverage box from reachable raycast hits, not a guarantee that every interior point is clickable. - `SortingOrder`: Canvas sorting order. Higher values are in front. - `SiblingIndex`: Transform sibling index under the element's direct parent. Do not use it as a reliable z-order signal across nested UI hierarchies. +## RaycastLayerSummaries Fields + +`RaycastLayerSummaries` is populated when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. It is built from dense raycast samples, while `RaycastGridPoints` remains the coarse 5x5 annotated grid. + +- `Layer`: Physics layer name to pass to `--raycast-layer-mask` +- `LayerIndex`: Unity physics layer index +- `HitCount`: Dense raycast hit count for the layer +- `RepresentativeObjectPath`: Hierarchy path for the object with the most hits on that layer. Ties are resolved alphabetically by path. + +Entries are sorted by `HitCount` descending, then `LayerIndex` ascending. + ## Coordinate Conversion -When `CoordinateSystem` is `"gameView"`, convert image pixel coordinates to simulate-mouse coordinates: +When `ImageCoordinateSystem` is `"top-left-game-view"`, convert raw image pixel coordinates from `screenshot --capture-mode rendering` with the formula returned in `ScreenshotToInputFormula`: ```text -sim_x = image_x / ResolutionScale -sim_y = image_y / ResolutionScale + YOffset +input_x = image_x / resolutionScale +input_y = image_y / resolutionScale + imageToInputOffsetY ``` -When `ResolutionScale` is `1.0`, this simplifies to: +When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0`, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are always returned as mouse-input coordinates, so pass those values directly. + +For `PhysicsCollider` entries, `SimX/SimY` is a real sampled raycast hit nearest to the reachable cluster centroid. This avoids synthetic center points that may fall into empty space for L-shaped or ring-shaped collider coverage. Always use `SimX/SimY` for clicking; use `BoundsMinX/Y` and `BoundsMaxX/Y` only as a sampled coverage guide. + +`--raycast-layer-mask` filters by the requested physics layers and Camera.main.cullingMask. A layer that is requested but hidden from the active camera is treated as not visible and will not produce `PhysicsCollider` entries. + +For clustered `PhysicsCollider` entries, points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element are treated as covered by UI. This includes world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. Bounds, screenshot outlines, and `SimX/SimY` are derived from the remaining reachable samples; if every sampled hit in that collider cluster is covered, the collider is omitted from `AnnotatedElements`. + +`PhysicsCollider` bounds expand each reachable sample by half the dense raycast sampling step in X and Y, then clamp the result to the captured Game View area. The screenshot overlay draws only the outer edges of those reachable sample cells, so angled, L-shaped, separated, and partially UI-covered hit regions do not become one large rectangle. `BoundsMinX/Y` and `BoundsMaxX/Y` are still an axis-aligned bbox for JSON consumers, may extend up to half a sample step past the visible collider edge, and do not guarantee that every interior point is clickable. + +The mouse input tools convert internally to Unity Input System coordinates: ```text -sim_x = image_x -sim_y = image_y + YOffset +unity_x = input_x +unity_y = gameViewHeight - input_y ``` +Do not flip Y in the caller. + ## Annotation Readability Annotated screenshots compensate border thickness for `ResolutionScale`, so the saved PNG keeps the intended outline width after downscaling. The neutral contrast borders are 2 output pixels each, and the colored middle border is 4 output pixels. Label outlines are also compensated and are separated from element borders by a 4 output pixel gap. diff --git a/.claude/skills/uloop-simulate-keyboard/SKILL.md b/.claude/skills/uloop-simulate-keyboard/SKILL.md index 574794c166..8a6dddc613 100644 --- a/.claude/skills/uloop-simulate-keyboard/SKILL.md +++ b/.claude/skills/uloop-simulate-keyboard/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-keyboard -description: "Simulate keyboard key input in PlayMode via Input System. Use when you need to: (1) Press game control keys like WASD, Space, or Shift during PlayMode, (2) Hold keys down for continuous movement or actions, (3) Combine multiple held keys for complex input like Shift+W for sprint. Injects into Unity Input System (`Keyboard.current`); requires PlayMode and the New Input System." +description: "Simulate keyboard input in PlayMode through Unity Input System. Use for key presses, holds, releases, and game controls such as WASD or Space." context: fork --- @@ -10,15 +10,15 @@ Simulate keyboard input on Unity PlayMode: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. Execute the appropriate `uloop simulate-keyboard` command -3. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` +1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) +2. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-keyboard` command +3. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` 4. Report what happened ## Tool Reference ```bash -uloop simulate-keyboard --action --key [options] +npx --yes uloop-cli@2.2.0 simulate-keyboard --action --key [options] ``` ### Parameters @@ -55,20 +55,20 @@ uloop simulate-keyboard --action --key [options] ```bash # One-shot key press (tap W once) -uloop simulate-keyboard --action Press --key W +npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key W # Jump (tap Space) -uloop simulate-keyboard --action Press --key Space +npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key Space # Hold W for 2 seconds (move forward) -uloop simulate-keyboard --action Press --key W --duration 2.0 +npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key W --duration 2.0 # Sprint forward (hold Shift + W, then release) -uloop simulate-keyboard --action KeyDown --key LeftShift -uloop simulate-keyboard --action KeyDown --key W -uloop screenshot --capture-mode rendering -uloop simulate-keyboard --action KeyUp --key W -uloop simulate-keyboard --action KeyUp --key LeftShift +npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyDown --key LeftShift +npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyDown --key W +npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering +npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyUp --key W +npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyUp --key LeftShift ``` ## Output @@ -82,6 +82,6 @@ Returns JSON with: ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** (`com.unity.inputsystem`) must be installed -- Active Input Handling must be set to **Input System Package (New)** or **Both** in Player Settings +- **Input System package** must be installed (`com.unity.inputsystem`) +- Use this only when the project already uses the New Input System. - Game code must read input via Input System API (e.g. `Keyboard.current[Key.W].isPressed`), not legacy `Input.GetKey()` diff --git a/.claude/skills/uloop-simulate-mouse-input/SKILL.md b/.claude/skills/uloop-simulate-mouse-input/SKILL.md index 8a4f9e0981..c7b8c4ae2f 100644 --- a/.claude/skills/uloop-simulate-mouse-input/SKILL.md +++ b/.claude/skills/uloop-simulate-mouse-input/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-mouse-input -description: "Simulate mouse input in PlayMode for gameplay code that reads Unity Input System Mouse.current. Use when you need to: (1) Click or right-click in games that read Mouse.current button state, (2) Inject mouse delta for FPS camera control, (3) Inject scroll wheel for hotbar switching or zoom. Requires PlayMode and the New Input System; for EventSystem UI elements, use simulate-mouse-ui instead." +description: "Simulate Mouse.current input in PlayMode through Unity Input System. Use for gameplay clicks, mouse delta, or scroll; use simulate-mouse-ui for EventSystem UI elements." context: fork --- @@ -10,16 +10,16 @@ Simulate mouse input via Input System in Unity PlayMode: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. For Click/LongPress: determine the target screen position (use `uloop screenshot` to find coordinates) -3. Execute the appropriate `uloop simulate-mouse-input` command -4. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` +1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) +2. For Click/LongPress: determine the target Game View input position from annotated `SimX`/`SimY`, raycast-grid `InputX`/`InputY`, or raw image pixels converted with `ScreenshotToInputFormula` +3. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-mouse-input` command +4. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` 5. Report what happened ## Tool Reference ```bash -uloop simulate-mouse-input --action [options] +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action [options] ``` ### Parameters @@ -27,8 +27,8 @@ uloop simulate-mouse-input --action [options] | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--action` | enum | `Click` | `Click`, `LongPress`, `MoveDelta`, `SmoothDelta`, `Scroll` | -| `--x` | number | `0` | Target X position (origin: top-left). Used by Click and LongPress. | -| `--y` | number | `0` | Target Y position (origin: top-left). Used by Click and LongPress. | +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Used by Click and LongPress. Use `AnnotatedElements[].SimX`, `RaycastGridPoints[].InputX`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Used by Click and LongPress. Use `AnnotatedElements[].SimY`, `RaycastGridPoints[].InputY`, or raw image pixels converted with `ScreenshotToInputFormula`. | | `--button` | enum | `Left` | Mouse button: `Left`, `Right`, `Middle`. Used by Click and LongPress. | | `--duration` | number | `0` | Hold duration for LongPress, or interpolation duration for SmoothDelta (seconds). For Click, 0 = one-shot tap. | | `--delta-x` | number | `0` | Delta X in pixels for MoveDelta/SmoothDelta. Positive = right. | @@ -58,7 +58,7 @@ uloop simulate-mouse-input --action [options] | Scenario | Tool | |----------|------| | Click a Unity UI Button (IPointerClickHandler) | `simulate-mouse-ui` | -| Destroy a block in Minecraft (reads `Mouse.current.leftButton`) | `simulate-mouse-input` when the project uses the New Input System; otherwise prefer `execute-dynamic-code` for a project-specific workaround | +| Destroy a block in Minecraft (reads `Mouse.current.leftButton`) | `simulate-mouse-input` when the project uses the New Input System | | Place a block with right-click | `simulate-mouse-input --button Right` when the project uses the New Input System | | Drag a UI slider | `simulate-mouse-ui --action Drag` | | Look around with mouse (FPS camera) | `simulate-mouse-input --action MoveDelta` when the project uses the New Input System | @@ -67,34 +67,48 @@ uloop simulate-mouse-input --action [options] ## Examples ```bash -# Left-click at screen center (for game logic) -uloop simulate-mouse-input --action Click --x 400 --y 300 +# Left-click at the Game View center (for game logic) +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Click --x 400 --y 300 # Right-click at screen center (e.g. place block) -uloop simulate-mouse-input --action Click --x 400 --y 300 --button Right +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Click --x 400 --y 300 --button Right # Hold left-click for 2 seconds (e.g. mine block) -uloop simulate-mouse-input --action LongPress --x 400 --y 300 --duration 2.0 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action LongPress --x 400 --y 300 --duration 2.0 # Look right (FPS camera) -uloop simulate-mouse-input --action MoveDelta --delta-x 100 --delta-y 0 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action MoveDelta --delta-x 100 --delta-y 0 # Scroll up (e.g. previous hotbar slot) -uloop simulate-mouse-input --action Scroll --scroll-y 120 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Scroll --scroll-y 120 # Scroll down (e.g. next hotbar slot) -uloop simulate-mouse-input --action Scroll --scroll-y -120 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Scroll --scroll-y -120 # Smooth camera pan right over 0.5 seconds -uloop simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 +npx --yes uloop-cli@2.2.0 simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 ``` +## Coordinate System + +- `--x` / `--y` use **top-left Game View coordinates**. +- Raw image pixels from `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. +- Do not flip Y in the caller. The tool converts internally for Unity Input System: + +```text +unity_x = input_x +unity_y = gameViewHeight - input_y +``` + +- `Mouse.current.position` uses bottom-left Unity coordinates, so the value read inside Unity may show the converted Y. + ## Prerequisites - Unity must be in **PlayMode** - **Input System package** must be installed (`com.unity.inputsystem`) - Game code must read input via Input System API (e.g. `Mouse.current.leftButton.wasPressedThisFrame`) -- If the target project cannot use the New Input System, prefer `execute-dynamic-code` for a project-specific workaround instead of changing project settings just to use this tool +- Use this only when the project already uses the New Input System. ## Output @@ -103,7 +117,12 @@ Returns JSON with: - `Message`: Status message - `Action`: Echoes which action was executed (`Click`, `LongPress`, `MoveDelta`, `SmoothDelta`, or `Scroll`) - `Button`: Which button was used (nullable string; populated for `Click` / `LongPress`, null otherwise) -- `PositionX`: Target X coordinate (nullable float; populated for `Click` / `LongPress`) -- `PositionY`: Target Y coordinate (nullable float; populated for `Click` / `LongPress`) - -These are the only six fields. There is no `DeltaX`, `DeltaY`, `ScrollX`, `ScrollY`, `Duration`, or hit-element field in the response — only the issued action, button, and target position are echoed back. Verify visual outcome with a follow-up screenshot. +- `PositionX` / `PositionY`: Target top-left Game View coordinates (nullable float; populated for `Click` / `LongPress`) +- `InputCoordinateSystem`: `"top-left-game-view"` for click/long-press coordinates +- `UnityCoordinateSystem`: `"bottom-left-game-view"` for the injected `Mouse.current.position` +- `GameViewWidth` / `GameViewHeight`: Game View size used for conversion +- `InputPositionX` / `InputPositionY`: Coordinates received from the caller +- `InjectedUnityPositionX` / `InjectedUnityPositionY`: Coordinates injected into `Mouse.current.position` +- `CoordinateConversionFormula`: Conversion formula used by the tool + +Verify visual outcome with a follow-up screenshot. diff --git a/.claude/skills/uloop-simulate-mouse-ui/SKILL.md b/.claude/skills/uloop-simulate-mouse-ui/SKILL.md index ffd6160091..89a174cf13 100644 --- a/.claude/skills/uloop-simulate-mouse-ui/SKILL.md +++ b/.claude/skills/uloop-simulate-mouse-ui/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-mouse-ui -description: "Simulate mouse click, long-press, and drag on PlayMode UI elements via EventSystem screen coordinates from annotated screenshots. Use when you need to: (1) Click buttons or interactive UI elements during PlayMode testing, (2) Drag UI elements between annotated screen positions, (3) Long-press or hold a drag for sustained pointer interactions. First get target coordinates with `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` and use `AnnotatedElements[].SimX` / `SimY`; for gameplay code that reads Mouse.current, use simulate-mouse-input instead." +description: "Simulate PlayMode EventSystem UI mouse actions using top-left Game View coordinates. Use for UI clicks, long-presses, or drags from annotated screenshots." context: fork --- @@ -10,17 +10,17 @@ Simulate mouse interaction on Unity PlayMode UI: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` +1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) +2. Get UI element info: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true --elements-only true` 3. Use the `AnnotatedElements` array to find the target element by `Label`, `Name`, or `Path` (A=frontmost, B=next, ...). Use `Interaction` to distinguish click targets from drag/drop/text targets, then use `SimX`/`SimY` directly as `--x`/`--y` coordinates. -4. Execute the appropriate `uloop simulate-mouse-ui` command -5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements` +4. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-mouse-ui` command +5. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true` 6. Report what happened ## Tool Reference ```bash -uloop simulate-mouse-ui --action --x --y [options] +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action --x --y [options] ``` ### Parameters @@ -28,10 +28,10 @@ uloop simulate-mouse-ui --action --x --y [options] | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--action` | enum | `Click` | `Click`, `Drag`, `DragStart`, `DragMove`, `DragEnd`, `LongPress` | -| `--x` | number | `0` | Target X position in screen pixels (origin: top-left). For Drag action, this is the destination. | -| `--y` | number | `0` | Target Y position in screen pixels (origin: top-left). For Drag action, this is the destination. | -| `--from-x` | number | `0` | Start X position for Drag action. Drag starts here and moves to x,y. | -| `--from-y` | number | `0` | Start Y position for Drag action. Drag starts here and moves to x,y. | +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Used by Click, LongPress, DragStart, DragMove, and DragEnd; for Drag, this is the destination. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Used by Click, LongPress, DragStart, DragMove, and DragEnd; for Drag, this is the destination. | +| `--from-x` | number | `0` | Start X position in Game View pixels for Drag action. Drag starts here and moves to x,y. | +| `--from-y` | number | `0` | Start Y position in Game View pixels for Drag action. Drag starts here and moves to x,y. | | `--drag-speed` | number | `2000` | Drag speed in pixels per second (0 for instant). 2000 is fast (default), 200 is slow enough to watch. Applies to Drag, DragMove, and DragEnd actions. | | `--duration` | number | `0.5` | Hold duration in seconds for LongPress action. | | `--button` | enum | `Left` | Mouse button. `Click` and `LongPress` support `Left`, `Right`, and `Middle`. Drag actions support `Left` only; other buttons return an error. | @@ -67,7 +67,7 @@ uloop simulate-mouse-ui --action --x --y [options] ## Coordinate System - Origin is **top-left** (0, 0) -- All positions are in **screen pixels** +- All positions are in **top-left Game View pixels** - Get coordinates from `AnnotatedElements` JSON (`SimX`/`SimY`) — do NOT look up GameObject positions - Clicking or long-pressing on empty space (no UI element) still succeeds with a message indicating no element was hit - Dragging on empty space (no draggable UI element) returns `Success = false` @@ -77,35 +77,35 @@ uloop simulate-mouse-ui --action --x --y [options] ## Examples ```bash -# Click a button at screen position -uloop simulate-mouse-ui --action Click --x 400 --y 300 +# Click a button at a Game View position +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Click --x 400 --y 300 # Force-click a button behind a raycast blocker by path -uloop simulate-mouse-ui --action Click --x 400 --y 300 --bypass-raycast true --target-path "Canvas/Panel/Button" +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Click --x 400 --y 300 --bypass-raycast true --target-path "Canvas/Panel/Button" # Force-long-press a button behind a raycast blocker by path -uloop simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 --bypass-raycast true --target-path "Canvas/Panel/Button" +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 --bypass-raycast true --target-path "Canvas/Panel/Button" # Force-drag an item behind a raycast blocker by path -uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" # Force-drag and dispatch Drop to a blocked drop zone -uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" --drop-target-path "Canvas/DropZone" +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" --drop-target-path "Canvas/DropZone" # Long-press a button for 3 seconds -uloop simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 # One-shot drag (start to end in one call) -uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 # Slow drag for visual inspection -uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --drag-speed 200 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --drag-speed 200 # Split drag with hold (for inspection between steps) -uloop simulate-mouse-ui --action DragStart --x 400 --y 300 -uloop screenshot --window-name Game -uloop simulate-mouse-ui --action DragMove --x 500 --y 300 -uloop simulate-mouse-ui --action DragEnd --x 600 --y 300 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragStart --x 400 --y 300 +npx --yes uloop-cli@2.2.0 screenshot --window-name Game +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragMove --x 500 --y 300 +npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragEnd --x 600 --y 300 ``` ## Prerequisites @@ -127,6 +127,6 @@ Returns JSON with: - `EndPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `EndPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. +These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true`. Note: Click and LongPress on empty space (no UI element) still return `Success = true` with `HitGameObjectName = null`. Drag actions on empty space return `Success = false`. From 245f890661c223f4011ec526063a73c8dec9736f Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 22:04:26 +0900 Subject: [PATCH 07/12] Fix raycast outline corner caps Extend outline segment rectangles along their endpoints so perpendicular segments overlap at corners and avoid visible notches. --- .../Tests/Editor/UIElementAnnotatorTests.cs | 20 +++++++++++++++++++ .../src/Editor/Utils/UIElementAnnotator.cs | 8 ++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Assets/Tests/Editor/UIElementAnnotatorTests.cs b/Assets/Tests/Editor/UIElementAnnotatorTests.cs index 9b4072e40a..501bf37ba5 100644 --- a/Assets/Tests/Editor/UIElementAnnotatorTests.cs +++ b/Assets/Tests/Editor/UIElementAnnotatorTests.cs @@ -140,6 +140,26 @@ public void ConvertTopLeftOutlineSegmentToScreenSegment_ShouldFlipYForCanvasSpac Assert.That(screenSegment.EndY, Is.EqualTo(80f)); } + [Test] + public void CalculateOutlineSegmentRect_WhenHorizontalSegment_ShouldExtendBothEndpoints() + { + RaycastOutlineSegment segment = new RaycastOutlineSegment(10f, 20f, 30f, 20f); + + Rect rect = UIElementAnnotator.CalculateOutlineSegmentRect(segment, 4f); + + Assert.That(rect, Is.EqualTo(new Rect(8f, 18f, 24f, 4f))); + } + + [Test] + public void CalculateOutlineSegmentRect_WhenVerticalSegment_ShouldExtendBothEndpoints() + { + RaycastOutlineSegment segment = new RaycastOutlineSegment(10f, 20f, 10f, 50f); + + Rect rect = UIElementAnnotator.CalculateOutlineSegmentRect(segment, 4f); + + Assert.That(rect, Is.EqualTo(new Rect(8f, 18f, 4f, 34f))); + } + [Test] public void GetAnnotationBorderColors_WhenAnnotationColorIsProvided_ShouldPutAnnotationColorInTheMiddle() { diff --git a/Packages/src/Editor/Utils/UIElementAnnotator.cs b/Packages/src/Editor/Utils/UIElementAnnotator.cs index 3d33f5e4a2..794df6eccb 100644 --- a/Packages/src/Editor/Utils/UIElementAnnotator.cs +++ b/Packages/src/Editor/Utils/UIElementAnnotator.cs @@ -570,13 +570,13 @@ internal static Rect CalculateOutlineSegmentRect(RaycastOutlineSegment segment, if (horizontal) { - float minX = Mathf.Min(segment.StartX, segment.EndX); - float width = Mathf.Abs(segment.EndX - segment.StartX); + float minX = Mathf.Min(segment.StartX, segment.EndX) - thickness / 2f; + float width = Mathf.Abs(segment.EndX - segment.StartX) + thickness; return new Rect(minX, segment.StartY - thickness / 2f, width, thickness); } - float minY = Mathf.Min(segment.StartY, segment.EndY); - float height = Mathf.Abs(segment.EndY - segment.StartY); + float minY = Mathf.Min(segment.StartY, segment.EndY) - thickness / 2f; + float height = Mathf.Abs(segment.EndY - segment.StartY) + thickness; return new Rect(segment.StartX - thickness / 2f, minY, thickness, height); } From 0ffa713f1397f7917c151acb63f9e6e9cbbc67db Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 22:20:36 +0900 Subject: [PATCH 08/12] update skills --- .agents/skills/uloop-clear-console/SKILL.md | 8 ++--- .agents/skills/uloop-compile/SKILL.md | 20 +++++------ .../skills/uloop-control-play-mode/SKILL.md | 10 +++--- .../uloop-execute-dynamic-code/SKILL.md | 10 +++--- .../playmode-automation-powershell.md | 32 ++++++++--------- .../references/playmode-automation-zsh.md | 28 +++++++-------- .../skills/uloop-find-game-objects/SKILL.md | 16 ++++----- .agents/skills/uloop-focus-window/SKILL.md | 8 ++--- .agents/skills/uloop-get-hierarchy/SKILL.md | 14 ++++---- .agents/skills/uloop-get-logs/SKILL.md | 12 +++---- .agents/skills/uloop-hello-world/SKILL.md | 12 +++---- .agents/skills/uloop-launch/SKILL.md | 16 ++++----- .agents/skills/uloop-raycast/SKILL.md | 10 +++--- .agents/skills/uloop-record-input/SKILL.md | 10 +++--- .agents/skills/uloop-replay-input/SKILL.md | 12 +++---- .agents/skills/uloop-run-tests/SKILL.md | 16 ++++----- .agents/skills/uloop-screenshot/SKILL.md | 34 +++++++++--------- .../references/annotated-elements.md | 2 +- .../skills/uloop-simulate-keyboard/SKILL.md | 24 ++++++------- .../uloop-simulate-mouse-input/SKILL.md | 24 ++++++------- .../skills/uloop-simulate-mouse-ui/SKILL.md | 36 +++++++++---------- .claude/skills/uloop-clear-console/SKILL.md | 8 ++--- .claude/skills/uloop-compile/SKILL.md | 20 +++++------ .../skills/uloop-control-play-mode/SKILL.md | 10 +++--- .../uloop-execute-dynamic-code/SKILL.md | 10 +++--- .../playmode-automation-powershell.md | 32 ++++++++--------- .../references/playmode-automation-zsh.md | 28 +++++++-------- .../skills/uloop-find-game-objects/SKILL.md | 16 ++++----- .claude/skills/uloop-focus-window/SKILL.md | 8 ++--- .claude/skills/uloop-get-hierarchy/SKILL.md | 14 ++++---- .claude/skills/uloop-get-logs/SKILL.md | 12 +++---- .claude/skills/uloop-hello-world/SKILL.md | 12 +++---- .claude/skills/uloop-launch/SKILL.md | 16 ++++----- .claude/skills/uloop-raycast/SKILL.md | 10 +++--- .claude/skills/uloop-record-input/SKILL.md | 10 +++--- .claude/skills/uloop-replay-input/SKILL.md | 12 +++---- .claude/skills/uloop-run-tests/SKILL.md | 16 ++++----- .claude/skills/uloop-screenshot/SKILL.md | 34 +++++++++--------- .../references/annotated-elements.md | 2 +- .../skills/uloop-simulate-keyboard/SKILL.md | 24 ++++++------- .../uloop-simulate-mouse-input/SKILL.md | 24 ++++++------- .../skills/uloop-simulate-mouse-ui/SKILL.md | 36 +++++++++---------- 42 files changed, 354 insertions(+), 354 deletions(-) diff --git a/.agents/skills/uloop-clear-console/SKILL.md b/.agents/skills/uloop-clear-console/SKILL.md index 5f9c1daf66..40891bc388 100644 --- a/.agents/skills/uloop-clear-console/SKILL.md +++ b/.agents/skills/uloop-clear-console/SKILL.md @@ -3,14 +3,14 @@ name: uloop-clear-console description: "Clear Unity Console entries. Use before compile, tests, or debugging when stale logs would hide the current result." --- -# npx --yes uloop-cli@2.2.0 clear-console +# uloop clear-console Clear Unity console logs. ## Usage ```bash -npx --yes uloop-cli@2.2.0 clear-console [--add-confirmation-message] +uloop clear-console [--add-confirmation-message] ``` ## Parameters @@ -29,10 +29,10 @@ npx --yes uloop-cli@2.2.0 clear-console [--add-confirmation-message] ```bash # Clear console -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console # Clear with confirmation -npx --yes uloop-cli@2.2.0 clear-console --add-confirmation-message +uloop clear-console --add-confirmation-message ``` ## Output diff --git a/.agents/skills/uloop-compile/SKILL.md b/.agents/skills/uloop-compile/SKILL.md index f8df207d0b..6e927b236f 100644 --- a/.agents/skills/uloop-compile/SKILL.md +++ b/.agents/skills/uloop-compile/SKILL.md @@ -3,14 +3,14 @@ name: uloop-compile description: "Compile the Unity project and report errors/warnings. Use after C# edits or when a full Domain Reload compile is needed." --- -# npx --yes uloop-cli@2.2.0 compile +# uloop compile Execute Unity project compilation. ## Usage ```bash -npx --yes uloop-cli@2.2.0 compile [--force-recompile] [--wait-for-domain-reload] +uloop compile [--force-recompile] [--wait-for-domain-reload] ``` ## Parameters @@ -30,16 +30,16 @@ npx --yes uloop-cli@2.2.0 compile [--force-recompile] [--wait-for-domain-reload] ```bash # Check compilation -npx --yes uloop-cli@2.2.0 compile +uloop compile # Force full recompilation -npx --yes uloop-cli@2.2.0 compile --force-recompile +uloop compile --force-recompile # Force recompilation and wait for Domain Reload completion -npx --yes uloop-cli@2.2.0 compile --force-recompile true --wait-for-domain-reload true +uloop compile --force-recompile true --wait-for-domain-reload true # Wait for Domain Reload completion even without force recompilation -npx --yes uloop-cli@2.2.0 compile --force-recompile false --wait-for-domain-reload true +uloop compile --force-recompile false --wait-for-domain-reload true ``` ## Output @@ -56,15 +56,15 @@ Diagnose the failure mode before retrying. **Stale lock files** (CLI hangs or shows "Unity is busy" while Unity Editor *is* running): ```bash -npx --yes uloop-cli@2.2.0 fix +uloop fix ``` -This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `npx --yes uloop-cli@2.2.0 compile`. +This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `uloop compile`. **Unity Editor not running** (CLI returns a connection failure and no Unity process is alive): ```bash -npx --yes uloop-cli@2.2.0 launch +uloop launch ``` -`npx --yes uloop-cli@2.2.0 launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `npx --yes uloop-cli@2.2.0 compile`. +`uloop launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `uloop compile`. diff --git a/.agents/skills/uloop-control-play-mode/SKILL.md b/.agents/skills/uloop-control-play-mode/SKILL.md index d95f5a6baa..5f830696ec 100644 --- a/.agents/skills/uloop-control-play-mode/SKILL.md +++ b/.agents/skills/uloop-control-play-mode/SKILL.md @@ -3,14 +3,14 @@ name: uloop-control-play-mode description: "Control Unity Editor Play Mode. Use to start, stop, or pause Play Mode for runtime behavior checks and frame inspection." --- -# npx --yes uloop-cli@2.2.0 control-play-mode +# uloop control-play-mode Control Unity Editor play mode (play/stop/pause). ## Usage ```bash -npx --yes uloop-cli@2.2.0 control-play-mode [options] +uloop control-play-mode [options] ``` ## Parameters @@ -29,13 +29,13 @@ npx --yes uloop-cli@2.2.0 control-play-mode [options] ```bash # Start play mode -npx --yes uloop-cli@2.2.0 control-play-mode --action Play +uloop control-play-mode --action Play # Stop play mode -npx --yes uloop-cli@2.2.0 control-play-mode --action Stop +uloop control-play-mode --action Stop # Pause play mode -npx --yes uloop-cli@2.2.0 control-play-mode --action Pause +uloop control-play-mode --action Pause ``` ## Output diff --git a/.agents/skills/uloop-execute-dynamic-code/SKILL.md b/.agents/skills/uloop-execute-dynamic-code/SKILL.md index 7d1c49e5d7..189ed4d1c2 100644 --- a/.agents/skills/uloop-execute-dynamic-code/SKILL.md +++ b/.agents/skills/uloop-execute-dynamic-code/SKILL.md @@ -6,7 +6,7 @@ context: fork # Task -Execute the following request using `npx --yes uloop-cli@2.2.0 execute-dynamic-code`: $ARGUMENTS +Execute the following request using `uloop execute-dynamic-code`: $ARGUMENTS For basic selected GameObject discovery or property inspection, use `find-game-objects --search-mode Selected` before this tool. Use this tool after the built-in inspection tools are not enough or when you need to modify Unity state. @@ -14,8 +14,8 @@ For basic selected GameObject discovery or property inspection, use `find-game-o 1. Read the relevant reference file(s) from the Code Examples section below 2. Construct C# code based on the reference examples -3. For multiline snippets, write the C# statements to a temporary `.csx` file and execute `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code-file ` -4. Use `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code ''` only for short one-line snippets +3. For multiline snippets, write the C# statements to a temporary `.csx` file and execute `uloop execute-dynamic-code --code-file ` +4. Use `uloop execute-dynamic-code --code ''` only for short one-line snippets 5. If execution fails, adjust code and retry 6. Report the execution result @@ -23,7 +23,7 @@ For basic selected GameObject discovery or property inspection, use `find-game-o - `--code ''`: Inline C# statements to execute. Use this for one-line snippets only. - `--code-file `: Read C# statements from a UTF-8 file. Prefer this for multiline snippets, especially on PowerShell, because Windows `.cmd` shims can lose lines from multiline inline arguments before `uloop` receives them. -- **Shell quoting**: bash/zsh uses single quotes, for example `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'using UnityEngine; return Mathf.PI;'`. PowerShell single-quoted strings can contain normal double quotes, for example `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'Debug.Log("Hello!");'`. +- **Shell quoting**: bash/zsh uses single quotes, for example `uloop execute-dynamic-code --code 'using UnityEngine; return Mathf.PI;'`. PowerShell single-quoted strings can contain normal double quotes, for example `uloop execute-dynamic-code --code 'Debug.Log("Hello!");'`. - `--parameters {}` (advanced, optional): Pass an object when reusing a snippet with varying data or when keeping values outside the code. Values are exposed as `parameters["param0"]`, `parameters["param1"]`, and so on. Omit this flag for most snippets, and pass an object instead of a JSON string. - `--compile-only true` (optional): Compile the snippet without executing it. Use this when you want Roslyn diagnostics before running new code. @@ -53,7 +53,7 @@ Returns JSON: - `DiagnosticsSummary`: string|null — compact summary when diagnostics are available - `Diagnostics`: object[] — structured diagnostics; same shape as `CompilationErrors`, usually populated together with it -Use `npx --yes uloop-cli@2.2.0 get-logs` to retrieve `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` messages emitted by the snippet. On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, security violation, cancellation, or an "execution in progress" rejection, all of which return empty `CompilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. +Use `uloop get-logs` to retrieve `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` messages emitted by the snippet. On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, security violation, cancellation, or an "execution in progress" rejection, all of which return empty `CompilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. ## Code Examples by Category diff --git a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index 6a764d5c2d..dd6fb01735 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -31,7 +31,7 @@ Use these patterns when you need shell-safe inline code: Single-quote the whole snippet and keep C# string literals unchanged. ```powershell -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from PowerShell";' +uloop execute-dynamic-code --code 'return "Hello from PowerShell";' ``` ### Single quotes inside inline C# code @@ -39,7 +39,7 @@ npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from PowerS If the C# snippet itself contains a single quote, double it inside the PowerShell single-quoted string. ```powershell -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' +uloop execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' ``` ### JSON-like values passed via `--parameters` @@ -47,7 +47,7 @@ npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = ''A''; ret Wrap the whole expression in single quotes so PowerShell passes the inner double quotes through unchanged. ```powershell -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' +uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' ``` ### Multi-line C# snippets @@ -64,7 +64,7 @@ if (obj == null) return "Player not found"; return obj.name; '@ -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code $code +uloop execute-dynamic-code --code $code ``` You can combine multi-line code with multi-line parameters the same way. @@ -78,7 +78,7 @@ $parameters = @' {"param0":"Hello from PowerShell"} '@ -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code $code --parameters $parameters +uloop execute-dynamic-code --code $code --parameters $parameters ``` ## Click UI Button by Path @@ -168,7 +168,7 @@ return $"Moved {player.name} to {targetPos}"; # Tool Combination Workflows -Examples of combining `execute-dynamic-code` with other npx --yes uloop-cli@2.2.0 tools for multi-step PlayMode automation. +Examples of combining `execute-dynamic-code` with other uloop tools for multi-step PlayMode automation. ## find-game-objects → Click Button @@ -177,7 +177,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```powershell -npx --yes uloop-cli@2.2.0 find-game-objects --required-components UnityEngine.UI.Button +uloop find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -npx --yes uloop-cli@2.2.0 get-hierarchy --root-path 'Canvas' --max-depth 3 +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button @@ -243,7 +243,7 @@ return "Clicked PlayButton"; **Step 2**: Capture Game View to verify the result ```powershell -npx --yes uloop-cli@2.2.0 screenshot --window-name Game +uloop screenshot --window-name Game ``` ## Execute Action → Check Logs for Side Effects @@ -253,7 +253,7 @@ Run an action then inspect Unity Console logs to verify expected behavior. **Step 1**: Clear console before the action ```powershell -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console ``` **Step 2**: Perform the action @@ -277,7 +277,7 @@ return "Invoked TakeDamage(50)"; **Step 3**: Check logs for expected output ```powershell -npx --yes uloop-cli@2.2.0 get-logs --log-type Log --search-text 'damage' +uloop get-logs --log-type Log --search-text 'damage' ``` ## Full Automation: Play → Act → Capture → Stop @@ -287,13 +287,13 @@ End-to-end test flow: start Play mode, perform actions, capture evidence, stop. **Step 0**: Clear console to isolate this run ```powershell -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console ``` **Step 1**: Start Play mode ```powershell -npx --yes uloop-cli@2.2.0 control-play-mode --action Play +uloop control-play-mode --action Play ``` **Step 2**: Wait for scene initialization, then find and click a button @@ -314,17 +314,17 @@ return $"Clicked {startBtn.gameObject.name}"; **Step 3**: Capture screenshot as evidence ```powershell -npx --yes uloop-cli@2.2.0 screenshot --window-name Game +uloop screenshot --window-name Game ``` **Step 4**: Check logs for errors ```powershell -npx --yes uloop-cli@2.2.0 get-logs --log-type Error +uloop get-logs --log-type Error ``` **Step 5**: Stop Play mode ```powershell -npx --yes uloop-cli@2.2.0 control-play-mode --action Stop +uloop control-play-mode --action Stop ``` diff --git a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md index c44ea92235..b1d94797db 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md @@ -31,7 +31,7 @@ Use these patterns when you need shell-safe inline code: Single-quote the whole snippet and keep C# string literals unchanged. ```zsh -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from zsh";' +uloop execute-dynamic-code --code 'return "Hello from zsh";' ``` ### Single quotes inside inline C# code @@ -39,7 +39,7 @@ npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from zsh";' If the C# snippet itself contains a single quote, close and reopen the shell string with `'\''`. ```zsh -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToString();' +uloop execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToString();' ``` ### JSON-like values passed via `--parameters` @@ -47,7 +47,7 @@ npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = '\''A'\''; Wrap the whole expression in single quotes so the shell does not interpret double quotes. ```zsh -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from zsh"}' +uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from zsh"}' ``` ## Click UI Button by Path @@ -137,7 +137,7 @@ return $"Moved {player.name} to {targetPos}"; # Tool Combination Workflows -Examples of combining `execute-dynamic-code` with other npx --yes uloop-cli@2.2.0 tools for multi-step PlayMode automation. +Examples of combining `execute-dynamic-code` with other uloop tools for multi-step PlayMode automation. ## find-game-objects → Click Button @@ -146,7 +146,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```zsh -npx --yes uloop-cli@2.2.0 find-game-objects --required-components UnityEngine.UI.Button +uloop find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -172,7 +172,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```zsh -npx --yes uloop-cli@2.2.0 get-hierarchy --root-path "Canvas" --max-depth 3 +uloop get-hierarchy --root-path "Canvas" --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button @@ -212,7 +212,7 @@ return "Clicked PlayButton"; **Step 2**: Capture Game View to verify the result ```zsh -npx --yes uloop-cli@2.2.0 screenshot --window-name Game +uloop screenshot --window-name Game ``` ## Execute Action → Check Logs for Side Effects @@ -222,7 +222,7 @@ Run an action then inspect Unity Console logs to verify expected behavior. **Step 1**: Clear console before the action ```zsh -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console ``` **Step 2**: Perform the action @@ -246,7 +246,7 @@ return "Invoked TakeDamage(50)"; **Step 3**: Check logs for expected output ```zsh -npx --yes uloop-cli@2.2.0 get-logs --log-type Log --search-text "damage" +uloop get-logs --log-type Log --search-text "damage" ``` ## Full Automation: Play → Act → Capture → Stop @@ -256,13 +256,13 @@ End-to-end test flow: start Play mode, perform actions, capture evidence, stop. **Step 0**: Clear console to isolate this run ```zsh -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console ``` **Step 1**: Start Play mode ```zsh -npx --yes uloop-cli@2.2.0 control-play-mode --action Play +uloop control-play-mode --action Play ``` **Step 2**: Wait for scene initialization, then find and click a button @@ -283,17 +283,17 @@ return $"Clicked {startBtn.gameObject.name}"; **Step 3**: Capture screenshot as evidence ```zsh -npx --yes uloop-cli@2.2.0 screenshot --window-name Game +uloop screenshot --window-name Game ``` **Step 4**: Check logs for errors ```zsh -npx --yes uloop-cli@2.2.0 get-logs --log-type Error +uloop get-logs --log-type Error ``` **Step 5**: Stop Play mode ```zsh -npx --yes uloop-cli@2.2.0 control-play-mode --action Stop +uloop control-play-mode --action Stop ``` diff --git a/.agents/skills/uloop-find-game-objects/SKILL.md b/.agents/skills/uloop-find-game-objects/SKILL.md index 3d80fce320..d7084b4a91 100644 --- a/.agents/skills/uloop-find-game-objects/SKILL.md +++ b/.agents/skills/uloop-find-game-objects/SKILL.md @@ -3,7 +3,7 @@ name: uloop-find-game-objects description: "Find or inspect Unity GameObjects, especially objects the user currently selected in the Hierarchy. Use for details, components, tags, layers, or name/path searches." --- -# npx --yes uloop-cli@2.2.0 find-game-objects +# uloop find-game-objects Find GameObjects with search criteria or get details for currently selected Hierarchy objects. @@ -12,7 +12,7 @@ Use this before `execute-dynamic-code` when identifying or inspecting selected G ## Usage ```bash -npx --yes uloop-cli@2.2.0 find-game-objects [options] +uloop find-game-objects [options] ``` ## Parameters @@ -48,22 +48,22 @@ npx --yes uloop-cli@2.2.0 find-game-objects [options] ```bash # Find by name -npx --yes uloop-cli@2.2.0 find-game-objects --name-pattern "Player" +uloop find-game-objects --name-pattern "Player" # Find with component -npx --yes uloop-cli@2.2.0 find-game-objects --required-components Rigidbody +uloop find-game-objects --required-components Rigidbody # Find by tag -npx --yes uloop-cli@2.2.0 find-game-objects --tag "Enemy" +uloop find-game-objects --tag "Enemy" # Regex search -npx --yes uloop-cli@2.2.0 find-game-objects --name-pattern "UI_.*" --search-mode Regex +uloop find-game-objects --name-pattern "UI_.*" --search-mode Regex # Get selected GameObjects -npx --yes uloop-cli@2.2.0 find-game-objects --search-mode Selected +uloop find-game-objects --search-mode Selected # Get selected including inactive -npx --yes uloop-cli@2.2.0 find-game-objects --search-mode Selected --include-inactive +uloop find-game-objects --search-mode Selected --include-inactive ``` ## Output diff --git a/.agents/skills/uloop-focus-window/SKILL.md b/.agents/skills/uloop-focus-window/SKILL.md index 5552b76e86..9379ef080b 100644 --- a/.agents/skills/uloop-focus-window/SKILL.md +++ b/.agents/skills/uloop-focus-window/SKILL.md @@ -3,14 +3,14 @@ name: uloop-focus-window description: "Bring the Unity Editor window to front. Use when Unity must be visible for visual checks or user-facing interaction." --- -# npx --yes uloop-cli@2.2.0 focus-window +# uloop focus-window Bring Unity Editor window to front using OS-level commands. ## Usage ```bash -npx --yes uloop-cli@2.2.0 focus-window +uloop focus-window ``` ## Parameters @@ -27,7 +27,7 @@ None. ```bash # Focus Unity Editor -npx --yes uloop-cli@2.2.0 focus-window +uloop focus-window ``` ## Output @@ -42,5 +42,5 @@ These are the only two fields. There is no PID, window-handle, or platform field - **Works even when Unity is busy** (compiling, domain reload, etc.) - Uses OS-level commands (osascript on macOS, PowerShell on Windows) -- Useful before `npx --yes uloop-cli@2.2.0 capture-unity-window` to ensure the target window is visible +- Useful before `uloop capture-unity-window` to ensure the target window is visible - Brings the main Unity Editor window to the foreground diff --git a/.agents/skills/uloop-get-hierarchy/SKILL.md b/.agents/skills/uloop-get-hierarchy/SKILL.md index a460089e65..8a5a932952 100644 --- a/.agents/skills/uloop-get-hierarchy/SKILL.md +++ b/.agents/skills/uloop-get-hierarchy/SKILL.md @@ -3,7 +3,7 @@ name: uloop-get-hierarchy description: "Get the Unity scene hierarchy as a structured tree. Use for parent-child structure, descendants, roots, or subtrees under objects the user currently selected." --- -# npx --yes uloop-cli@2.2.0 get-hierarchy +# uloop get-hierarchy Get Unity Hierarchy structure from the whole scene, a root path, or selected Hierarchy objects. @@ -12,7 +12,7 @@ Use this for hierarchy structure, especially descendants under the current selec ## Usage ```bash -npx --yes uloop-cli@2.2.0 get-hierarchy [options] +uloop get-hierarchy [options] ``` ## Parameters @@ -37,19 +37,19 @@ npx --yes uloop-cli@2.2.0 get-hierarchy [options] ```bash # Get entire hierarchy -npx --yes uloop-cli@2.2.0 get-hierarchy +uloop get-hierarchy # Get hierarchy from specific root -npx --yes uloop-cli@2.2.0 get-hierarchy --root-path "Canvas/UI" +uloop get-hierarchy --root-path "Canvas/UI" # Limit depth -npx --yes uloop-cli@2.2.0 get-hierarchy --max-depth 2 +uloop get-hierarchy --max-depth 2 # Without components -npx --yes uloop-cli@2.2.0 get-hierarchy --include-components false +uloop get-hierarchy --include-components false # Get hierarchy from currently selected GameObjects -npx --yes uloop-cli@2.2.0 get-hierarchy --use-selection +uloop get-hierarchy --use-selection ``` ## Output diff --git a/.agents/skills/uloop-get-logs/SKILL.md b/.agents/skills/uloop-get-logs/SKILL.md index ab8ddeda45..7cb5f5f311 100644 --- a/.agents/skills/uloop-get-logs/SKILL.md +++ b/.agents/skills/uloop-get-logs/SKILL.md @@ -3,14 +3,14 @@ name: uloop-get-logs description: "Read current Unity Console entries from a running Editor. Use during bug investigation after compile, tests, PlayMode, or dynamic code to inspect logs, warnings, errors, and stack traces." --- -# npx --yes uloop-cli@2.2.0 get-logs +# uloop get-logs Retrieve logs from Unity Console. ## Usage ```bash -npx --yes uloop-cli@2.2.0 get-logs [options] +uloop get-logs [options] ``` ## Parameters @@ -34,16 +34,16 @@ npx --yes uloop-cli@2.2.0 get-logs [options] ```bash # Get all logs -npx --yes uloop-cli@2.2.0 get-logs +uloop get-logs # Get only errors -npx --yes uloop-cli@2.2.0 get-logs --log-type Error +uloop get-logs --log-type Error # Search for specific text -npx --yes uloop-cli@2.2.0 get-logs --search-text "NullReference" +uloop get-logs --search-text "NullReference" # Regex search -npx --yes uloop-cli@2.2.0 get-logs --search-text "Missing.*Component" --use-regex +uloop get-logs --search-text "Missing.*Component" --use-regex ``` ## Output diff --git a/.agents/skills/uloop-hello-world/SKILL.md b/.agents/skills/uloop-hello-world/SKILL.md index 041630f302..c1b7a61717 100644 --- a/.agents/skills/uloop-hello-world/SKILL.md +++ b/.agents/skills/uloop-hello-world/SKILL.md @@ -3,14 +3,14 @@ name: uloop-hello-world description: "Sample hello world tool via uloop CLI. Use when you need to test the MCP tool system or see an example of custom tool implementation." --- -# npx --yes uloop-cli@2.2.0 hello-world +# uloop hello-world Personalized hello world tool with multi-language support. ## Usage ```bash -npx --yes uloop-cli@2.2.0 hello-world [options] +uloop hello-world [options] ``` ## Parameters @@ -25,16 +25,16 @@ npx --yes uloop-cli@2.2.0 hello-world [options] ```bash # Default greeting -npx --yes uloop-cli@2.2.0 hello-world +uloop hello-world # Greet with custom name -npx --yes uloop-cli@2.2.0 hello-world --name "Alice" +uloop hello-world --name "Alice" # Japanese greeting -npx --yes uloop-cli@2.2.0 hello-world --name "太郎" --language japanese +uloop hello-world --name "太郎" --language japanese # Spanish greeting without timestamp -npx --yes uloop-cli@2.2.0 hello-world --name "Carlos" --language spanish --include-timestamp false +uloop hello-world --name "Carlos" --language spanish --include-timestamp false ``` ## Output diff --git a/.agents/skills/uloop-launch/SKILL.md b/.agents/skills/uloop-launch/SKILL.md index 7bbcd969c3..bedf8464ed 100644 --- a/.agents/skills/uloop-launch/SKILL.md +++ b/.agents/skills/uloop-launch/SKILL.md @@ -3,17 +3,17 @@ name: uloop-launch description: "Use when Unity Editor is not running or needs a clean restart." --- -# npx --yes uloop-cli@2.2.0 launch +# uloop launch Launch Unity Editor with the correct version for a project. -`npx --yes uloop-cli@2.2.0 launch` is not fire-and-forget. When Unity needs to start or restart, the command waits +`uloop launch` is not fire-and-forget. When Unity needs to start or restart, the command waits until Unity is actually ready for CLI operations before it exits. ## Usage ```bash -npx --yes uloop-cli@2.2.0 launch [project-path] [options] +uloop launch [project-path] [options] ``` ## Parameters @@ -31,19 +31,19 @@ npx --yes uloop-cli@2.2.0 launch [project-path] [options] ```bash # Search for Unity project in current directory and launch -npx --yes uloop-cli@2.2.0 launch +uloop launch # Launch specific project -npx --yes uloop-cli@2.2.0 launch /path/to/project +uloop launch /path/to/project # Restart Unity (kill existing and relaunch) -npx --yes uloop-cli@2.2.0 launch -r +uloop launch -r # Launch with build target -npx --yes uloop-cli@2.2.0 launch -p Android +uloop launch -p Android # Add project to Unity Hub without launching -npx --yes uloop-cli@2.2.0 launch -a +uloop launch -a ``` ## Output diff --git a/.agents/skills/uloop-raycast/SKILL.md b/.agents/skills/uloop-raycast/SKILL.md index 6098734304..b464ae09c3 100644 --- a/.agents/skills/uloop-raycast/SKILL.md +++ b/.agents/skills/uloop-raycast/SKILL.md @@ -3,14 +3,14 @@ name: uloop-raycast description: "Raycast from Camera.main through a Game View coordinate. Use when you need to check what a screenshot coordinate would hit in 3D physics before clicking or long-pressing with simulate-mouse-input." --- -# npx --yes uloop-cli@2.2.0 raycast +# uloop raycast Check what a top-left Game View coordinate hits in 3D physics. ## Usage ```bash -npx --yes uloop-cli@2.2.0 raycast --x --y [--layer-mask ] [--max-distance ] +uloop raycast --x --y [--layer-mask ] [--max-distance ] ``` ## Parameters @@ -25,7 +25,7 @@ npx --yes uloop-cli@2.2.0 raycast --x --y [--layer-mask ] [--max-d ## Coordinate System - `--x` / `--y` use the same top-left Game View input coordinates as `simulate-mouse-input`. -- Raw image pixels from `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- Raw image pixels from `uloop screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. - `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. - Do not flip Y in the caller. The tool converts internally: @@ -38,10 +38,10 @@ unity_y = gameViewHeight - input_y ```bash # Check what is under a screenshot coordinate -npx --yes uloop-cli@2.2.0 raycast --x 960 --y 540 +uloop raycast --x 960 --y 540 # Check only specific layers -npx --yes uloop-cli@2.2.0 raycast --x 960 --y 540 --layer-mask 1 +uloop raycast --x 960 --y 540 --layer-mask 1 ``` ## Output diff --git a/.agents/skills/uloop-record-input/SKILL.md b/.agents/skills/uloop-record-input/SKILL.md index 4833ac060f..bfb60f7721 100644 --- a/.agents/skills/uloop-record-input/SKILL.md +++ b/.agents/skills/uloop-record-input/SKILL.md @@ -3,7 +3,7 @@ name: uloop-record-input description: "Record PlayMode keyboard and mouse input to JSON. Use to capture gameplay, bug repro, or E2E input sequences for replay." --- -# npx --yes uloop-cli@2.2.0 record-input +# uloop record-input Record keyboard and mouse input during PlayMode frame-by-frame into a JSON file. Captures key presses, mouse movement, clicks, and scroll events via Input System device state diffing. @@ -11,16 +11,16 @@ Record keyboard and mouse input during PlayMode frame-by-frame into a JSON file. ```bash # Start recording -npx --yes uloop-cli@2.2.0 record-input --action Start +uloop record-input --action Start # Start recording with key filter -npx --yes uloop-cli@2.2.0 record-input --action Start --keys "W,A,S,D,Space" +uloop record-input --action Start --keys "W,A,S,D,Space" # Stop recording and save -npx --yes uloop-cli@2.2.0 record-input --action Stop +uloop record-input --action Stop # Stop and save to specific path -npx --yes uloop-cli@2.2.0 record-input --action Stop --output-path scripts/my-play.json +uloop record-input --action Stop --output-path scripts/my-play.json ``` ## Parameters diff --git a/.agents/skills/uloop-replay-input/SKILL.md b/.agents/skills/uloop-replay-input/SKILL.md index 41734194e1..e4a103fc20 100644 --- a/.agents/skills/uloop-replay-input/SKILL.md +++ b/.agents/skills/uloop-replay-input/SKILL.md @@ -3,7 +3,7 @@ name: uloop-replay-input description: "Replay recorded PlayMode keyboard and mouse input. Use for exact gameplay reproduction, E2E runs, or consistent demos from JSON recordings." --- -# npx --yes uloop-cli@2.2.0 replay-input +# uloop replay-input Replay recorded keyboard and mouse input during PlayMode. Loads a JSON recording and injects input frame-by-frame via Input System with zero CLI overhead. Supports looping and progress monitoring. @@ -11,19 +11,19 @@ Replay recorded keyboard and mouse input during PlayMode. Loads a JSON recording ```bash # Start replay (auto-detect latest recording) -npx --yes uloop-cli@2.2.0 replay-input --action Start +uloop replay-input --action Start # Start replay with specific file -npx --yes uloop-cli@2.2.0 replay-input --action Start --input-path scripts/my-play.json +uloop replay-input --action Start --input-path scripts/my-play.json # Start replay with looping -npx --yes uloop-cli@2.2.0 replay-input --action Start --loop true +uloop replay-input --action Start --loop true # Check replay progress -npx --yes uloop-cli@2.2.0 replay-input --action Status +uloop replay-input --action Status # Stop replay -npx --yes uloop-cli@2.2.0 replay-input --action Stop +uloop replay-input --action Stop ``` ## Parameters diff --git a/.agents/skills/uloop-run-tests/SKILL.md b/.agents/skills/uloop-run-tests/SKILL.md index b4160d0e30..78b7d2796e 100644 --- a/.agents/skills/uloop-run-tests/SKILL.md +++ b/.agents/skills/uloop-run-tests/SKILL.md @@ -3,18 +3,18 @@ name: uloop-run-tests description: "Run Unity Test Runner and report detailed results. Use for EditMode/PlayMode tests, change verification, or failure diagnosis." --- -# npx --yes uloop-cli@2.2.0 run-tests +# uloop run-tests Execute Unity Test Runner. This command requires the Unity Test Framework package (`com.unity.test-framework`). If that package is not installed, the command returns `Success: false` with an unsupported message and does not affect the other Unity CLI Loop tools. When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. -Before executing tests, `npx --yes uloop-cli@2.2.0 run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. +Before executing tests, `uloop run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. ## Usage ```bash -npx --yes uloop-cli@2.2.0 run-tests [options] +uloop run-tests [options] ``` ## Parameters @@ -36,19 +36,19 @@ npx --yes uloop-cli@2.2.0 run-tests [options] ```bash # Run all EditMode tests -npx --yes uloop-cli@2.2.0 run-tests +uloop run-tests # Run PlayMode tests -npx --yes uloop-cli@2.2.0 run-tests --test-mode PlayMode +uloop run-tests --test-mode PlayMode # Save explicitly approved editor changes before running tests -npx --yes uloop-cli@2.2.0 run-tests --save-before-run true +uloop run-tests --save-before-run true # Run specific test -npx --yes uloop-cli@2.2.0 run-tests --filter-type exact --filter-value "MyTest.TestMethod" +uloop run-tests --filter-type exact --filter-value "MyTest.TestMethod" # Run tests matching pattern -npx --yes uloop-cli@2.2.0 run-tests --filter-type regex --filter-value ".*Integration.*" +uloop run-tests --filter-type regex --filter-value ".*Integration.*" ``` ## Output diff --git a/.agents/skills/uloop-screenshot/SKILL.md b/.agents/skills/uloop-screenshot/SKILL.md index 7295d521cc..92bede01d8 100644 --- a/.agents/skills/uloop-screenshot/SKILL.md +++ b/.agents/skills/uloop-screenshot/SKILL.md @@ -3,14 +3,14 @@ name: uloop-screenshot description: "Capture Unity Editor windows or Game View rendering as PNG. Use for visual checks, debugging, documentation, or annotated UI element coordinates." --- -# npx --yes uloop-cli@2.2.0 screenshot +# uloop screenshot Take a screenshot of any Unity EditorWindow by name and save as PNG. ## Usage ```bash -npx --yes uloop-cli@2.2.0 screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] +uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] ``` ## Parameters @@ -49,41 +49,41 @@ The window name is the text displayed in the window's title bar (tab). Common na ```bash # Take a screenshot of Game View (default) -npx --yes uloop-cli@2.2.0 screenshot +uloop screenshot # Capture game rendering (default scale coordinates match simulate-mouse, PlayMode required) -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering +uloop screenshot --capture-mode rendering # Annotate interactive UI elements with index labels (for simulate-mouse workflow) -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true +uloop screenshot --capture-mode rendering --annotate-elements true # Annotate 3D physics raycast candidate points -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true # Inspect hit layers, then rerun with the layer used by game input -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true # Annotate clustered 3D collider candidates on selected layers -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable # Get UI element coordinates without capturing an image (fastest) -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true --elements-only true +uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true # Get clustered 3D collider coordinates without capturing an image -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true # Take a screenshot of Scene View -npx --yes uloop-cli@2.2.0 screenshot --window-name Scene +uloop screenshot --window-name Scene # Capture all windows starting with "Project" (prefix match) -npx --yes uloop-cli@2.2.0 screenshot --window-name Project --match-mode prefix +uloop screenshot --window-name Project --match-mode prefix # Save screenshot to a specific directory -npx --yes uloop-cli@2.2.0 screenshot --output-directory /tmp/screenshots +uloop screenshot --output-directory /tmp/screenshots # Combine options -npx --yes uloop-cli@2.2.0 screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots +uloop screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots ``` ## Output @@ -111,7 +111,7 @@ When multiple windows match (e.g., multiple Inspector windows or when using `con ## Notes -- Use `npx --yes uloop-cli@2.2.0 focus-window` first if needed +- Use `uloop focus-window` first if needed - Target window must be open in Unity Editor - Window name matching is always case-insensitive - Boolean options use value syntax. Write `--annotate-elements true`, `--annotate-raycast-grid true`, and `--elements-only true`; do not use bare boolean flags. @@ -126,7 +126,7 @@ When multiple windows match (e.g., multiple Inspector windows or when using `con Use `Assets/Scenes/RaycastAnnotationDemoScene.unity` as the maintained visual fixture for raycast annotation checks. Enter PlayMode, then run: ```bash -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 ``` The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile outlines should shrink around the blocker, outlines should follow reachable sampled cells, and `SimX/SimY` should stay inside `BoundsMinX/Y` and `BoundsMaxX/Y`. diff --git a/.agents/skills/uloop-screenshot/references/annotated-elements.md b/.agents/skills/uloop-screenshot/references/annotated-elements.md index f6aba22c84..58616dc26d 100644 --- a/.agents/skills/uloop-screenshot/references/annotated-elements.md +++ b/.agents/skills/uloop-screenshot/references/annotated-elements.md @@ -1,6 +1,6 @@ # Annotated Elements and Coordinates -Read this when using `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. +Read this when using `uloop screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. ## AnnotatedElements Fields diff --git a/.agents/skills/uloop-simulate-keyboard/SKILL.md b/.agents/skills/uloop-simulate-keyboard/SKILL.md index 8a6dddc613..b14b4d8e0c 100644 --- a/.agents/skills/uloop-simulate-keyboard/SKILL.md +++ b/.agents/skills/uloop-simulate-keyboard/SKILL.md @@ -10,15 +10,15 @@ Simulate keyboard input on Unity PlayMode: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) -2. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-keyboard` command -3. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` +1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) +2. Execute the appropriate `uloop simulate-keyboard` command +3. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` 4. Report what happened ## Tool Reference ```bash -npx --yes uloop-cli@2.2.0 simulate-keyboard --action --key [options] +uloop simulate-keyboard --action --key [options] ``` ### Parameters @@ -55,20 +55,20 @@ npx --yes uloop-cli@2.2.0 simulate-keyboard --action --key [optio ```bash # One-shot key press (tap W once) -npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key W +uloop simulate-keyboard --action Press --key W # Jump (tap Space) -npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key Space +uloop simulate-keyboard --action Press --key Space # Hold W for 2 seconds (move forward) -npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key W --duration 2.0 +uloop simulate-keyboard --action Press --key W --duration 2.0 # Sprint forward (hold Shift + W, then release) -npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyDown --key LeftShift -npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyDown --key W -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering -npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyUp --key W -npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyUp --key LeftShift +uloop simulate-keyboard --action KeyDown --key LeftShift +uloop simulate-keyboard --action KeyDown --key W +uloop screenshot --capture-mode rendering +uloop simulate-keyboard --action KeyUp --key W +uloop simulate-keyboard --action KeyUp --key LeftShift ``` ## Output diff --git a/.agents/skills/uloop-simulate-mouse-input/SKILL.md b/.agents/skills/uloop-simulate-mouse-input/SKILL.md index c7b8c4ae2f..d0bca06e34 100644 --- a/.agents/skills/uloop-simulate-mouse-input/SKILL.md +++ b/.agents/skills/uloop-simulate-mouse-input/SKILL.md @@ -10,16 +10,16 @@ Simulate mouse input via Input System in Unity PlayMode: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) +1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) 2. For Click/LongPress: determine the target Game View input position from annotated `SimX`/`SimY`, raycast-grid `InputX`/`InputY`, or raw image pixels converted with `ScreenshotToInputFormula` -3. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-mouse-input` command -4. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` +3. Execute the appropriate `uloop simulate-mouse-input` command +4. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` 5. Report what happened ## Tool Reference ```bash -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action [options] +uloop simulate-mouse-input --action [options] ``` ### Parameters @@ -68,31 +68,31 @@ npx --yes uloop-cli@2.2.0 simulate-mouse-input --action [options] ```bash # Left-click at the Game View center (for game logic) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Click --x 400 --y 300 +uloop simulate-mouse-input --action Click --x 400 --y 300 # Right-click at screen center (e.g. place block) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Click --x 400 --y 300 --button Right +uloop simulate-mouse-input --action Click --x 400 --y 300 --button Right # Hold left-click for 2 seconds (e.g. mine block) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action LongPress --x 400 --y 300 --duration 2.0 +uloop simulate-mouse-input --action LongPress --x 400 --y 300 --duration 2.0 # Look right (FPS camera) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action MoveDelta --delta-x 100 --delta-y 0 +uloop simulate-mouse-input --action MoveDelta --delta-x 100 --delta-y 0 # Scroll up (e.g. previous hotbar slot) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Scroll --scroll-y 120 +uloop simulate-mouse-input --action Scroll --scroll-y 120 # Scroll down (e.g. next hotbar slot) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Scroll --scroll-y -120 +uloop simulate-mouse-input --action Scroll --scroll-y -120 # Smooth camera pan right over 0.5 seconds -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 +uloop simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 ``` ## Coordinate System - `--x` / `--y` use **top-left Game View coordinates**. -- Raw image pixels from `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- Raw image pixels from `uloop screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. - `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. - Do not flip Y in the caller. The tool converts internally for Unity Input System: diff --git a/.agents/skills/uloop-simulate-mouse-ui/SKILL.md b/.agents/skills/uloop-simulate-mouse-ui/SKILL.md index 89a174cf13..95f6185a18 100644 --- a/.agents/skills/uloop-simulate-mouse-ui/SKILL.md +++ b/.agents/skills/uloop-simulate-mouse-ui/SKILL.md @@ -10,17 +10,17 @@ Simulate mouse interaction on Unity PlayMode UI: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) -2. Get UI element info: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true --elements-only true` +1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) +2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true` 3. Use the `AnnotatedElements` array to find the target element by `Label`, `Name`, or `Path` (A=frontmost, B=next, ...). Use `Interaction` to distinguish click targets from drag/drop/text targets, then use `SimX`/`SimY` directly as `--x`/`--y` coordinates. -4. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-mouse-ui` command -5. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true` +4. Execute the appropriate `uloop simulate-mouse-ui` command +5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements true` 6. Report what happened ## Tool Reference ```bash -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action --x --y [options] +uloop simulate-mouse-ui --action --x --y [options] ``` ### Parameters @@ -78,34 +78,34 @@ npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action --x --y [o ```bash # Click a button at a Game View position -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Click --x 400 --y 300 +uloop simulate-mouse-ui --action Click --x 400 --y 300 # Force-click a button behind a raycast blocker by path -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Click --x 400 --y 300 --bypass-raycast true --target-path "Canvas/Panel/Button" +uloop simulate-mouse-ui --action Click --x 400 --y 300 --bypass-raycast true --target-path "Canvas/Panel/Button" # Force-long-press a button behind a raycast blocker by path -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 --bypass-raycast true --target-path "Canvas/Panel/Button" +uloop simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 --bypass-raycast true --target-path "Canvas/Panel/Button" # Force-drag an item behind a raycast blocker by path -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" +uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" # Force-drag and dispatch Drop to a blocked drop zone -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" --drop-target-path "Canvas/DropZone" +uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" --drop-target-path "Canvas/DropZone" # Long-press a button for 3 seconds -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 +uloop simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 # One-shot drag (start to end in one call) -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 +uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 # Slow drag for visual inspection -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --drag-speed 200 +uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --drag-speed 200 # Split drag with hold (for inspection between steps) -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragStart --x 400 --y 300 -npx --yes uloop-cli@2.2.0 screenshot --window-name Game -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragMove --x 500 --y 300 -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragEnd --x 600 --y 300 +uloop simulate-mouse-ui --action DragStart --x 400 --y 300 +uloop screenshot --window-name Game +uloop simulate-mouse-ui --action DragMove --x 500 --y 300 +uloop simulate-mouse-ui --action DragEnd --x 600 --y 300 ``` ## Prerequisites @@ -127,6 +127,6 @@ Returns JSON with: - `EndPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `EndPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true`. +These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements true`. Note: Click and LongPress on empty space (no UI element) still return `Success = true` with `HitGameObjectName = null`. Drag actions on empty space return `Success = false`. diff --git a/.claude/skills/uloop-clear-console/SKILL.md b/.claude/skills/uloop-clear-console/SKILL.md index 5f9c1daf66..40891bc388 100644 --- a/.claude/skills/uloop-clear-console/SKILL.md +++ b/.claude/skills/uloop-clear-console/SKILL.md @@ -3,14 +3,14 @@ name: uloop-clear-console description: "Clear Unity Console entries. Use before compile, tests, or debugging when stale logs would hide the current result." --- -# npx --yes uloop-cli@2.2.0 clear-console +# uloop clear-console Clear Unity console logs. ## Usage ```bash -npx --yes uloop-cli@2.2.0 clear-console [--add-confirmation-message] +uloop clear-console [--add-confirmation-message] ``` ## Parameters @@ -29,10 +29,10 @@ npx --yes uloop-cli@2.2.0 clear-console [--add-confirmation-message] ```bash # Clear console -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console # Clear with confirmation -npx --yes uloop-cli@2.2.0 clear-console --add-confirmation-message +uloop clear-console --add-confirmation-message ``` ## Output diff --git a/.claude/skills/uloop-compile/SKILL.md b/.claude/skills/uloop-compile/SKILL.md index f8df207d0b..6e927b236f 100644 --- a/.claude/skills/uloop-compile/SKILL.md +++ b/.claude/skills/uloop-compile/SKILL.md @@ -3,14 +3,14 @@ name: uloop-compile description: "Compile the Unity project and report errors/warnings. Use after C# edits or when a full Domain Reload compile is needed." --- -# npx --yes uloop-cli@2.2.0 compile +# uloop compile Execute Unity project compilation. ## Usage ```bash -npx --yes uloop-cli@2.2.0 compile [--force-recompile] [--wait-for-domain-reload] +uloop compile [--force-recompile] [--wait-for-domain-reload] ``` ## Parameters @@ -30,16 +30,16 @@ npx --yes uloop-cli@2.2.0 compile [--force-recompile] [--wait-for-domain-reload] ```bash # Check compilation -npx --yes uloop-cli@2.2.0 compile +uloop compile # Force full recompilation -npx --yes uloop-cli@2.2.0 compile --force-recompile +uloop compile --force-recompile # Force recompilation and wait for Domain Reload completion -npx --yes uloop-cli@2.2.0 compile --force-recompile true --wait-for-domain-reload true +uloop compile --force-recompile true --wait-for-domain-reload true # Wait for Domain Reload completion even without force recompilation -npx --yes uloop-cli@2.2.0 compile --force-recompile false --wait-for-domain-reload true +uloop compile --force-recompile false --wait-for-domain-reload true ``` ## Output @@ -56,15 +56,15 @@ Diagnose the failure mode before retrying. **Stale lock files** (CLI hangs or shows "Unity is busy" while Unity Editor *is* running): ```bash -npx --yes uloop-cli@2.2.0 fix +uloop fix ``` -This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `npx --yes uloop-cli@2.2.0 compile`. +This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory. Then retry `uloop compile`. **Unity Editor not running** (CLI returns a connection failure and no Unity process is alive): ```bash -npx --yes uloop-cli@2.2.0 launch +uloop launch ``` -`npx --yes uloop-cli@2.2.0 launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `npx --yes uloop-cli@2.2.0 compile`. +`uloop launch` auto-detects the project at the current working directory and opens it in the matching Unity Editor version. After Unity finishes launching, retry `uloop compile`. diff --git a/.claude/skills/uloop-control-play-mode/SKILL.md b/.claude/skills/uloop-control-play-mode/SKILL.md index d95f5a6baa..5f830696ec 100644 --- a/.claude/skills/uloop-control-play-mode/SKILL.md +++ b/.claude/skills/uloop-control-play-mode/SKILL.md @@ -3,14 +3,14 @@ name: uloop-control-play-mode description: "Control Unity Editor Play Mode. Use to start, stop, or pause Play Mode for runtime behavior checks and frame inspection." --- -# npx --yes uloop-cli@2.2.0 control-play-mode +# uloop control-play-mode Control Unity Editor play mode (play/stop/pause). ## Usage ```bash -npx --yes uloop-cli@2.2.0 control-play-mode [options] +uloop control-play-mode [options] ``` ## Parameters @@ -29,13 +29,13 @@ npx --yes uloop-cli@2.2.0 control-play-mode [options] ```bash # Start play mode -npx --yes uloop-cli@2.2.0 control-play-mode --action Play +uloop control-play-mode --action Play # Stop play mode -npx --yes uloop-cli@2.2.0 control-play-mode --action Stop +uloop control-play-mode --action Stop # Pause play mode -npx --yes uloop-cli@2.2.0 control-play-mode --action Pause +uloop control-play-mode --action Pause ``` ## Output diff --git a/.claude/skills/uloop-execute-dynamic-code/SKILL.md b/.claude/skills/uloop-execute-dynamic-code/SKILL.md index 7d1c49e5d7..189ed4d1c2 100644 --- a/.claude/skills/uloop-execute-dynamic-code/SKILL.md +++ b/.claude/skills/uloop-execute-dynamic-code/SKILL.md @@ -6,7 +6,7 @@ context: fork # Task -Execute the following request using `npx --yes uloop-cli@2.2.0 execute-dynamic-code`: $ARGUMENTS +Execute the following request using `uloop execute-dynamic-code`: $ARGUMENTS For basic selected GameObject discovery or property inspection, use `find-game-objects --search-mode Selected` before this tool. Use this tool after the built-in inspection tools are not enough or when you need to modify Unity state. @@ -14,8 +14,8 @@ For basic selected GameObject discovery or property inspection, use `find-game-o 1. Read the relevant reference file(s) from the Code Examples section below 2. Construct C# code based on the reference examples -3. For multiline snippets, write the C# statements to a temporary `.csx` file and execute `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code-file ` -4. Use `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code ''` only for short one-line snippets +3. For multiline snippets, write the C# statements to a temporary `.csx` file and execute `uloop execute-dynamic-code --code-file ` +4. Use `uloop execute-dynamic-code --code ''` only for short one-line snippets 5. If execution fails, adjust code and retry 6. Report the execution result @@ -23,7 +23,7 @@ For basic selected GameObject discovery or property inspection, use `find-game-o - `--code ''`: Inline C# statements to execute. Use this for one-line snippets only. - `--code-file `: Read C# statements from a UTF-8 file. Prefer this for multiline snippets, especially on PowerShell, because Windows `.cmd` shims can lose lines from multiline inline arguments before `uloop` receives them. -- **Shell quoting**: bash/zsh uses single quotes, for example `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'using UnityEngine; return Mathf.PI;'`. PowerShell single-quoted strings can contain normal double quotes, for example `npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'Debug.Log("Hello!");'`. +- **Shell quoting**: bash/zsh uses single quotes, for example `uloop execute-dynamic-code --code 'using UnityEngine; return Mathf.PI;'`. PowerShell single-quoted strings can contain normal double quotes, for example `uloop execute-dynamic-code --code 'Debug.Log("Hello!");'`. - `--parameters {}` (advanced, optional): Pass an object when reusing a snippet with varying data or when keeping values outside the code. Values are exposed as `parameters["param0"]`, `parameters["param1"]`, and so on. Omit this flag for most snippets, and pass an object instead of a JSON string. - `--compile-only true` (optional): Compile the snippet without executing it. Use this when you want Roslyn diagnostics before running new code. @@ -53,7 +53,7 @@ Returns JSON: - `DiagnosticsSummary`: string|null — compact summary when diagnostics are available - `Diagnostics`: object[] — structured diagnostics; same shape as `CompilationErrors`, usually populated together with it -Use `npx --yes uloop-cli@2.2.0 get-logs` to retrieve `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` messages emitted by the snippet. On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, security violation, cancellation, or an "execution in progress" rejection, all of which return empty `CompilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. +Use `uloop get-logs` to retrieve `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` messages emitted by the snippet. On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, security violation, cancellation, or an "execution in progress" rejection, all of which return empty `CompilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. ## Code Examples by Category diff --git a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index 6a764d5c2d..dd6fb01735 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -31,7 +31,7 @@ Use these patterns when you need shell-safe inline code: Single-quote the whole snippet and keep C# string literals unchanged. ```powershell -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from PowerShell";' +uloop execute-dynamic-code --code 'return "Hello from PowerShell";' ``` ### Single quotes inside inline C# code @@ -39,7 +39,7 @@ npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from PowerS If the C# snippet itself contains a single quote, double it inside the PowerShell single-quoted string. ```powershell -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' +uloop execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' ``` ### JSON-like values passed via `--parameters` @@ -47,7 +47,7 @@ npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = ''A''; ret Wrap the whole expression in single quotes so PowerShell passes the inner double quotes through unchanged. ```powershell -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' +uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' ``` ### Multi-line C# snippets @@ -64,7 +64,7 @@ if (obj == null) return "Player not found"; return obj.name; '@ -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code $code +uloop execute-dynamic-code --code $code ``` You can combine multi-line code with multi-line parameters the same way. @@ -78,7 +78,7 @@ $parameters = @' {"param0":"Hello from PowerShell"} '@ -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code $code --parameters $parameters +uloop execute-dynamic-code --code $code --parameters $parameters ``` ## Click UI Button by Path @@ -168,7 +168,7 @@ return $"Moved {player.name} to {targetPos}"; # Tool Combination Workflows -Examples of combining `execute-dynamic-code` with other npx --yes uloop-cli@2.2.0 tools for multi-step PlayMode automation. +Examples of combining `execute-dynamic-code` with other uloop tools for multi-step PlayMode automation. ## find-game-objects → Click Button @@ -177,7 +177,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```powershell -npx --yes uloop-cli@2.2.0 find-game-objects --required-components UnityEngine.UI.Button +uloop find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -npx --yes uloop-cli@2.2.0 get-hierarchy --root-path 'Canvas' --max-depth 3 +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button @@ -243,7 +243,7 @@ return "Clicked PlayButton"; **Step 2**: Capture Game View to verify the result ```powershell -npx --yes uloop-cli@2.2.0 screenshot --window-name Game +uloop screenshot --window-name Game ``` ## Execute Action → Check Logs for Side Effects @@ -253,7 +253,7 @@ Run an action then inspect Unity Console logs to verify expected behavior. **Step 1**: Clear console before the action ```powershell -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console ``` **Step 2**: Perform the action @@ -277,7 +277,7 @@ return "Invoked TakeDamage(50)"; **Step 3**: Check logs for expected output ```powershell -npx --yes uloop-cli@2.2.0 get-logs --log-type Log --search-text 'damage' +uloop get-logs --log-type Log --search-text 'damage' ``` ## Full Automation: Play → Act → Capture → Stop @@ -287,13 +287,13 @@ End-to-end test flow: start Play mode, perform actions, capture evidence, stop. **Step 0**: Clear console to isolate this run ```powershell -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console ``` **Step 1**: Start Play mode ```powershell -npx --yes uloop-cli@2.2.0 control-play-mode --action Play +uloop control-play-mode --action Play ``` **Step 2**: Wait for scene initialization, then find and click a button @@ -314,17 +314,17 @@ return $"Clicked {startBtn.gameObject.name}"; **Step 3**: Capture screenshot as evidence ```powershell -npx --yes uloop-cli@2.2.0 screenshot --window-name Game +uloop screenshot --window-name Game ``` **Step 4**: Check logs for errors ```powershell -npx --yes uloop-cli@2.2.0 get-logs --log-type Error +uloop get-logs --log-type Error ``` **Step 5**: Stop Play mode ```powershell -npx --yes uloop-cli@2.2.0 control-play-mode --action Stop +uloop control-play-mode --action Stop ``` diff --git a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md index c44ea92235..b1d94797db 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md @@ -31,7 +31,7 @@ Use these patterns when you need shell-safe inline code: Single-quote the whole snippet and keep C# string literals unchanged. ```zsh -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from zsh";' +uloop execute-dynamic-code --code 'return "Hello from zsh";' ``` ### Single quotes inside inline C# code @@ -39,7 +39,7 @@ npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return "Hello from zsh";' If the C# snippet itself contains a single quote, close and reopen the shell string with `'\''`. ```zsh -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToString();' +uloop execute-dynamic-code --code 'char initial = '\''A'\''; return initial.ToString();' ``` ### JSON-like values passed via `--parameters` @@ -47,7 +47,7 @@ npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'char initial = '\''A'\''; Wrap the whole expression in single quotes so the shell does not interpret double quotes. ```zsh -npx --yes uloop-cli@2.2.0 execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from zsh"}' +uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from zsh"}' ``` ## Click UI Button by Path @@ -137,7 +137,7 @@ return $"Moved {player.name} to {targetPos}"; # Tool Combination Workflows -Examples of combining `execute-dynamic-code` with other npx --yes uloop-cli@2.2.0 tools for multi-step PlayMode automation. +Examples of combining `execute-dynamic-code` with other uloop tools for multi-step PlayMode automation. ## find-game-objects → Click Button @@ -146,7 +146,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```zsh -npx --yes uloop-cli@2.2.0 find-game-objects --required-components UnityEngine.UI.Button +uloop find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -172,7 +172,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```zsh -npx --yes uloop-cli@2.2.0 get-hierarchy --root-path "Canvas" --max-depth 3 +uloop get-hierarchy --root-path "Canvas" --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button @@ -212,7 +212,7 @@ return "Clicked PlayButton"; **Step 2**: Capture Game View to verify the result ```zsh -npx --yes uloop-cli@2.2.0 screenshot --window-name Game +uloop screenshot --window-name Game ``` ## Execute Action → Check Logs for Side Effects @@ -222,7 +222,7 @@ Run an action then inspect Unity Console logs to verify expected behavior. **Step 1**: Clear console before the action ```zsh -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console ``` **Step 2**: Perform the action @@ -246,7 +246,7 @@ return "Invoked TakeDamage(50)"; **Step 3**: Check logs for expected output ```zsh -npx --yes uloop-cli@2.2.0 get-logs --log-type Log --search-text "damage" +uloop get-logs --log-type Log --search-text "damage" ``` ## Full Automation: Play → Act → Capture → Stop @@ -256,13 +256,13 @@ End-to-end test flow: start Play mode, perform actions, capture evidence, stop. **Step 0**: Clear console to isolate this run ```zsh -npx --yes uloop-cli@2.2.0 clear-console +uloop clear-console ``` **Step 1**: Start Play mode ```zsh -npx --yes uloop-cli@2.2.0 control-play-mode --action Play +uloop control-play-mode --action Play ``` **Step 2**: Wait for scene initialization, then find and click a button @@ -283,17 +283,17 @@ return $"Clicked {startBtn.gameObject.name}"; **Step 3**: Capture screenshot as evidence ```zsh -npx --yes uloop-cli@2.2.0 screenshot --window-name Game +uloop screenshot --window-name Game ``` **Step 4**: Check logs for errors ```zsh -npx --yes uloop-cli@2.2.0 get-logs --log-type Error +uloop get-logs --log-type Error ``` **Step 5**: Stop Play mode ```zsh -npx --yes uloop-cli@2.2.0 control-play-mode --action Stop +uloop control-play-mode --action Stop ``` diff --git a/.claude/skills/uloop-find-game-objects/SKILL.md b/.claude/skills/uloop-find-game-objects/SKILL.md index 3d80fce320..d7084b4a91 100644 --- a/.claude/skills/uloop-find-game-objects/SKILL.md +++ b/.claude/skills/uloop-find-game-objects/SKILL.md @@ -3,7 +3,7 @@ name: uloop-find-game-objects description: "Find or inspect Unity GameObjects, especially objects the user currently selected in the Hierarchy. Use for details, components, tags, layers, or name/path searches." --- -# npx --yes uloop-cli@2.2.0 find-game-objects +# uloop find-game-objects Find GameObjects with search criteria or get details for currently selected Hierarchy objects. @@ -12,7 +12,7 @@ Use this before `execute-dynamic-code` when identifying or inspecting selected G ## Usage ```bash -npx --yes uloop-cli@2.2.0 find-game-objects [options] +uloop find-game-objects [options] ``` ## Parameters @@ -48,22 +48,22 @@ npx --yes uloop-cli@2.2.0 find-game-objects [options] ```bash # Find by name -npx --yes uloop-cli@2.2.0 find-game-objects --name-pattern "Player" +uloop find-game-objects --name-pattern "Player" # Find with component -npx --yes uloop-cli@2.2.0 find-game-objects --required-components Rigidbody +uloop find-game-objects --required-components Rigidbody # Find by tag -npx --yes uloop-cli@2.2.0 find-game-objects --tag "Enemy" +uloop find-game-objects --tag "Enemy" # Regex search -npx --yes uloop-cli@2.2.0 find-game-objects --name-pattern "UI_.*" --search-mode Regex +uloop find-game-objects --name-pattern "UI_.*" --search-mode Regex # Get selected GameObjects -npx --yes uloop-cli@2.2.0 find-game-objects --search-mode Selected +uloop find-game-objects --search-mode Selected # Get selected including inactive -npx --yes uloop-cli@2.2.0 find-game-objects --search-mode Selected --include-inactive +uloop find-game-objects --search-mode Selected --include-inactive ``` ## Output diff --git a/.claude/skills/uloop-focus-window/SKILL.md b/.claude/skills/uloop-focus-window/SKILL.md index 5552b76e86..9379ef080b 100644 --- a/.claude/skills/uloop-focus-window/SKILL.md +++ b/.claude/skills/uloop-focus-window/SKILL.md @@ -3,14 +3,14 @@ name: uloop-focus-window description: "Bring the Unity Editor window to front. Use when Unity must be visible for visual checks or user-facing interaction." --- -# npx --yes uloop-cli@2.2.0 focus-window +# uloop focus-window Bring Unity Editor window to front using OS-level commands. ## Usage ```bash -npx --yes uloop-cli@2.2.0 focus-window +uloop focus-window ``` ## Parameters @@ -27,7 +27,7 @@ None. ```bash # Focus Unity Editor -npx --yes uloop-cli@2.2.0 focus-window +uloop focus-window ``` ## Output @@ -42,5 +42,5 @@ These are the only two fields. There is no PID, window-handle, or platform field - **Works even when Unity is busy** (compiling, domain reload, etc.) - Uses OS-level commands (osascript on macOS, PowerShell on Windows) -- Useful before `npx --yes uloop-cli@2.2.0 capture-unity-window` to ensure the target window is visible +- Useful before `uloop capture-unity-window` to ensure the target window is visible - Brings the main Unity Editor window to the foreground diff --git a/.claude/skills/uloop-get-hierarchy/SKILL.md b/.claude/skills/uloop-get-hierarchy/SKILL.md index a460089e65..8a5a932952 100644 --- a/.claude/skills/uloop-get-hierarchy/SKILL.md +++ b/.claude/skills/uloop-get-hierarchy/SKILL.md @@ -3,7 +3,7 @@ name: uloop-get-hierarchy description: "Get the Unity scene hierarchy as a structured tree. Use for parent-child structure, descendants, roots, or subtrees under objects the user currently selected." --- -# npx --yes uloop-cli@2.2.0 get-hierarchy +# uloop get-hierarchy Get Unity Hierarchy structure from the whole scene, a root path, or selected Hierarchy objects. @@ -12,7 +12,7 @@ Use this for hierarchy structure, especially descendants under the current selec ## Usage ```bash -npx --yes uloop-cli@2.2.0 get-hierarchy [options] +uloop get-hierarchy [options] ``` ## Parameters @@ -37,19 +37,19 @@ npx --yes uloop-cli@2.2.0 get-hierarchy [options] ```bash # Get entire hierarchy -npx --yes uloop-cli@2.2.0 get-hierarchy +uloop get-hierarchy # Get hierarchy from specific root -npx --yes uloop-cli@2.2.0 get-hierarchy --root-path "Canvas/UI" +uloop get-hierarchy --root-path "Canvas/UI" # Limit depth -npx --yes uloop-cli@2.2.0 get-hierarchy --max-depth 2 +uloop get-hierarchy --max-depth 2 # Without components -npx --yes uloop-cli@2.2.0 get-hierarchy --include-components false +uloop get-hierarchy --include-components false # Get hierarchy from currently selected GameObjects -npx --yes uloop-cli@2.2.0 get-hierarchy --use-selection +uloop get-hierarchy --use-selection ``` ## Output diff --git a/.claude/skills/uloop-get-logs/SKILL.md b/.claude/skills/uloop-get-logs/SKILL.md index ab8ddeda45..7cb5f5f311 100644 --- a/.claude/skills/uloop-get-logs/SKILL.md +++ b/.claude/skills/uloop-get-logs/SKILL.md @@ -3,14 +3,14 @@ name: uloop-get-logs description: "Read current Unity Console entries from a running Editor. Use during bug investigation after compile, tests, PlayMode, or dynamic code to inspect logs, warnings, errors, and stack traces." --- -# npx --yes uloop-cli@2.2.0 get-logs +# uloop get-logs Retrieve logs from Unity Console. ## Usage ```bash -npx --yes uloop-cli@2.2.0 get-logs [options] +uloop get-logs [options] ``` ## Parameters @@ -34,16 +34,16 @@ npx --yes uloop-cli@2.2.0 get-logs [options] ```bash # Get all logs -npx --yes uloop-cli@2.2.0 get-logs +uloop get-logs # Get only errors -npx --yes uloop-cli@2.2.0 get-logs --log-type Error +uloop get-logs --log-type Error # Search for specific text -npx --yes uloop-cli@2.2.0 get-logs --search-text "NullReference" +uloop get-logs --search-text "NullReference" # Regex search -npx --yes uloop-cli@2.2.0 get-logs --search-text "Missing.*Component" --use-regex +uloop get-logs --search-text "Missing.*Component" --use-regex ``` ## Output diff --git a/.claude/skills/uloop-hello-world/SKILL.md b/.claude/skills/uloop-hello-world/SKILL.md index 041630f302..c1b7a61717 100644 --- a/.claude/skills/uloop-hello-world/SKILL.md +++ b/.claude/skills/uloop-hello-world/SKILL.md @@ -3,14 +3,14 @@ name: uloop-hello-world description: "Sample hello world tool via uloop CLI. Use when you need to test the MCP tool system or see an example of custom tool implementation." --- -# npx --yes uloop-cli@2.2.0 hello-world +# uloop hello-world Personalized hello world tool with multi-language support. ## Usage ```bash -npx --yes uloop-cli@2.2.0 hello-world [options] +uloop hello-world [options] ``` ## Parameters @@ -25,16 +25,16 @@ npx --yes uloop-cli@2.2.0 hello-world [options] ```bash # Default greeting -npx --yes uloop-cli@2.2.0 hello-world +uloop hello-world # Greet with custom name -npx --yes uloop-cli@2.2.0 hello-world --name "Alice" +uloop hello-world --name "Alice" # Japanese greeting -npx --yes uloop-cli@2.2.0 hello-world --name "太郎" --language japanese +uloop hello-world --name "太郎" --language japanese # Spanish greeting without timestamp -npx --yes uloop-cli@2.2.0 hello-world --name "Carlos" --language spanish --include-timestamp false +uloop hello-world --name "Carlos" --language spanish --include-timestamp false ``` ## Output diff --git a/.claude/skills/uloop-launch/SKILL.md b/.claude/skills/uloop-launch/SKILL.md index 7bbcd969c3..bedf8464ed 100644 --- a/.claude/skills/uloop-launch/SKILL.md +++ b/.claude/skills/uloop-launch/SKILL.md @@ -3,17 +3,17 @@ name: uloop-launch description: "Use when Unity Editor is not running or needs a clean restart." --- -# npx --yes uloop-cli@2.2.0 launch +# uloop launch Launch Unity Editor with the correct version for a project. -`npx --yes uloop-cli@2.2.0 launch` is not fire-and-forget. When Unity needs to start or restart, the command waits +`uloop launch` is not fire-and-forget. When Unity needs to start or restart, the command waits until Unity is actually ready for CLI operations before it exits. ## Usage ```bash -npx --yes uloop-cli@2.2.0 launch [project-path] [options] +uloop launch [project-path] [options] ``` ## Parameters @@ -31,19 +31,19 @@ npx --yes uloop-cli@2.2.0 launch [project-path] [options] ```bash # Search for Unity project in current directory and launch -npx --yes uloop-cli@2.2.0 launch +uloop launch # Launch specific project -npx --yes uloop-cli@2.2.0 launch /path/to/project +uloop launch /path/to/project # Restart Unity (kill existing and relaunch) -npx --yes uloop-cli@2.2.0 launch -r +uloop launch -r # Launch with build target -npx --yes uloop-cli@2.2.0 launch -p Android +uloop launch -p Android # Add project to Unity Hub without launching -npx --yes uloop-cli@2.2.0 launch -a +uloop launch -a ``` ## Output diff --git a/.claude/skills/uloop-raycast/SKILL.md b/.claude/skills/uloop-raycast/SKILL.md index 6098734304..b464ae09c3 100644 --- a/.claude/skills/uloop-raycast/SKILL.md +++ b/.claude/skills/uloop-raycast/SKILL.md @@ -3,14 +3,14 @@ name: uloop-raycast description: "Raycast from Camera.main through a Game View coordinate. Use when you need to check what a screenshot coordinate would hit in 3D physics before clicking or long-pressing with simulate-mouse-input." --- -# npx --yes uloop-cli@2.2.0 raycast +# uloop raycast Check what a top-left Game View coordinate hits in 3D physics. ## Usage ```bash -npx --yes uloop-cli@2.2.0 raycast --x --y [--layer-mask ] [--max-distance ] +uloop raycast --x --y [--layer-mask ] [--max-distance ] ``` ## Parameters @@ -25,7 +25,7 @@ npx --yes uloop-cli@2.2.0 raycast --x --y [--layer-mask ] [--max-d ## Coordinate System - `--x` / `--y` use the same top-left Game View input coordinates as `simulate-mouse-input`. -- Raw image pixels from `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- Raw image pixels from `uloop screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. - `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. - Do not flip Y in the caller. The tool converts internally: @@ -38,10 +38,10 @@ unity_y = gameViewHeight - input_y ```bash # Check what is under a screenshot coordinate -npx --yes uloop-cli@2.2.0 raycast --x 960 --y 540 +uloop raycast --x 960 --y 540 # Check only specific layers -npx --yes uloop-cli@2.2.0 raycast --x 960 --y 540 --layer-mask 1 +uloop raycast --x 960 --y 540 --layer-mask 1 ``` ## Output diff --git a/.claude/skills/uloop-record-input/SKILL.md b/.claude/skills/uloop-record-input/SKILL.md index 4833ac060f..bfb60f7721 100644 --- a/.claude/skills/uloop-record-input/SKILL.md +++ b/.claude/skills/uloop-record-input/SKILL.md @@ -3,7 +3,7 @@ name: uloop-record-input description: "Record PlayMode keyboard and mouse input to JSON. Use to capture gameplay, bug repro, or E2E input sequences for replay." --- -# npx --yes uloop-cli@2.2.0 record-input +# uloop record-input Record keyboard and mouse input during PlayMode frame-by-frame into a JSON file. Captures key presses, mouse movement, clicks, and scroll events via Input System device state diffing. @@ -11,16 +11,16 @@ Record keyboard and mouse input during PlayMode frame-by-frame into a JSON file. ```bash # Start recording -npx --yes uloop-cli@2.2.0 record-input --action Start +uloop record-input --action Start # Start recording with key filter -npx --yes uloop-cli@2.2.0 record-input --action Start --keys "W,A,S,D,Space" +uloop record-input --action Start --keys "W,A,S,D,Space" # Stop recording and save -npx --yes uloop-cli@2.2.0 record-input --action Stop +uloop record-input --action Stop # Stop and save to specific path -npx --yes uloop-cli@2.2.0 record-input --action Stop --output-path scripts/my-play.json +uloop record-input --action Stop --output-path scripts/my-play.json ``` ## Parameters diff --git a/.claude/skills/uloop-replay-input/SKILL.md b/.claude/skills/uloop-replay-input/SKILL.md index 41734194e1..e4a103fc20 100644 --- a/.claude/skills/uloop-replay-input/SKILL.md +++ b/.claude/skills/uloop-replay-input/SKILL.md @@ -3,7 +3,7 @@ name: uloop-replay-input description: "Replay recorded PlayMode keyboard and mouse input. Use for exact gameplay reproduction, E2E runs, or consistent demos from JSON recordings." --- -# npx --yes uloop-cli@2.2.0 replay-input +# uloop replay-input Replay recorded keyboard and mouse input during PlayMode. Loads a JSON recording and injects input frame-by-frame via Input System with zero CLI overhead. Supports looping and progress monitoring. @@ -11,19 +11,19 @@ Replay recorded keyboard and mouse input during PlayMode. Loads a JSON recording ```bash # Start replay (auto-detect latest recording) -npx --yes uloop-cli@2.2.0 replay-input --action Start +uloop replay-input --action Start # Start replay with specific file -npx --yes uloop-cli@2.2.0 replay-input --action Start --input-path scripts/my-play.json +uloop replay-input --action Start --input-path scripts/my-play.json # Start replay with looping -npx --yes uloop-cli@2.2.0 replay-input --action Start --loop true +uloop replay-input --action Start --loop true # Check replay progress -npx --yes uloop-cli@2.2.0 replay-input --action Status +uloop replay-input --action Status # Stop replay -npx --yes uloop-cli@2.2.0 replay-input --action Stop +uloop replay-input --action Stop ``` ## Parameters diff --git a/.claude/skills/uloop-run-tests/SKILL.md b/.claude/skills/uloop-run-tests/SKILL.md index b4160d0e30..78b7d2796e 100644 --- a/.claude/skills/uloop-run-tests/SKILL.md +++ b/.claude/skills/uloop-run-tests/SKILL.md @@ -3,18 +3,18 @@ name: uloop-run-tests description: "Run Unity Test Runner and report detailed results. Use for EditMode/PlayMode tests, change verification, or failure diagnosis." --- -# npx --yes uloop-cli@2.2.0 run-tests +# uloop run-tests Execute Unity Test Runner. This command requires the Unity Test Framework package (`com.unity.test-framework`). If that package is not installed, the command returns `Success: false` with an unsupported message and does not affect the other Unity CLI Loop tools. When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. -Before executing tests, `npx --yes uloop-cli@2.2.0 run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. +Before executing tests, `uloop run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. ## Usage ```bash -npx --yes uloop-cli@2.2.0 run-tests [options] +uloop run-tests [options] ``` ## Parameters @@ -36,19 +36,19 @@ npx --yes uloop-cli@2.2.0 run-tests [options] ```bash # Run all EditMode tests -npx --yes uloop-cli@2.2.0 run-tests +uloop run-tests # Run PlayMode tests -npx --yes uloop-cli@2.2.0 run-tests --test-mode PlayMode +uloop run-tests --test-mode PlayMode # Save explicitly approved editor changes before running tests -npx --yes uloop-cli@2.2.0 run-tests --save-before-run true +uloop run-tests --save-before-run true # Run specific test -npx --yes uloop-cli@2.2.0 run-tests --filter-type exact --filter-value "MyTest.TestMethod" +uloop run-tests --filter-type exact --filter-value "MyTest.TestMethod" # Run tests matching pattern -npx --yes uloop-cli@2.2.0 run-tests --filter-type regex --filter-value ".*Integration.*" +uloop run-tests --filter-type regex --filter-value ".*Integration.*" ``` ## Output diff --git a/.claude/skills/uloop-screenshot/SKILL.md b/.claude/skills/uloop-screenshot/SKILL.md index 7295d521cc..92bede01d8 100644 --- a/.claude/skills/uloop-screenshot/SKILL.md +++ b/.claude/skills/uloop-screenshot/SKILL.md @@ -3,14 +3,14 @@ name: uloop-screenshot description: "Capture Unity Editor windows or Game View rendering as PNG. Use for visual checks, debugging, documentation, or annotated UI element coordinates." --- -# npx --yes uloop-cli@2.2.0 screenshot +# uloop screenshot Take a screenshot of any Unity EditorWindow by name and save as PNG. ## Usage ```bash -npx --yes uloop-cli@2.2.0 screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] +uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] ``` ## Parameters @@ -49,41 +49,41 @@ The window name is the text displayed in the window's title bar (tab). Common na ```bash # Take a screenshot of Game View (default) -npx --yes uloop-cli@2.2.0 screenshot +uloop screenshot # Capture game rendering (default scale coordinates match simulate-mouse, PlayMode required) -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering +uloop screenshot --capture-mode rendering # Annotate interactive UI elements with index labels (for simulate-mouse workflow) -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true +uloop screenshot --capture-mode rendering --annotate-elements true # Annotate 3D physics raycast candidate points -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true # Inspect hit layers, then rerun with the layer used by game input -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true # Annotate clustered 3D collider candidates on selected layers -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable # Get UI element coordinates without capturing an image (fastest) -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true --elements-only true +uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true # Get clustered 3D collider coordinates without capturing an image -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true # Take a screenshot of Scene View -npx --yes uloop-cli@2.2.0 screenshot --window-name Scene +uloop screenshot --window-name Scene # Capture all windows starting with "Project" (prefix match) -npx --yes uloop-cli@2.2.0 screenshot --window-name Project --match-mode prefix +uloop screenshot --window-name Project --match-mode prefix # Save screenshot to a specific directory -npx --yes uloop-cli@2.2.0 screenshot --output-directory /tmp/screenshots +uloop screenshot --output-directory /tmp/screenshots # Combine options -npx --yes uloop-cli@2.2.0 screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots +uloop screenshot --window-name Scene --resolution-scale 0.5 --output-directory /tmp/screenshots ``` ## Output @@ -111,7 +111,7 @@ When multiple windows match (e.g., multiple Inspector windows or when using `con ## Notes -- Use `npx --yes uloop-cli@2.2.0 focus-window` first if needed +- Use `uloop focus-window` first if needed - Target window must be open in Unity Editor - Window name matching is always case-insensitive - Boolean options use value syntax. Write `--annotate-elements true`, `--annotate-raycast-grid true`, and `--elements-only true`; do not use bare boolean flags. @@ -126,7 +126,7 @@ When multiple windows match (e.g., multiple Inspector windows or when using `con Use `Assets/Scenes/RaycastAnnotationDemoScene.unity` as the maintained visual fixture for raycast annotation checks. Enter PlayMode, then run: ```bash -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 ``` The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile outlines should shrink around the blocker, outlines should follow reachable sampled cells, and `SimX/SimY` should stay inside `BoundsMinX/Y` and `BoundsMaxX/Y`. diff --git a/.claude/skills/uloop-screenshot/references/annotated-elements.md b/.claude/skills/uloop-screenshot/references/annotated-elements.md index f6aba22c84..58616dc26d 100644 --- a/.claude/skills/uloop-screenshot/references/annotated-elements.md +++ b/.claude/skills/uloop-screenshot/references/annotated-elements.md @@ -1,6 +1,6 @@ # Annotated Elements and Coordinates -Read this when using `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. +Read this when using `uloop screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. ## AnnotatedElements Fields diff --git a/.claude/skills/uloop-simulate-keyboard/SKILL.md b/.claude/skills/uloop-simulate-keyboard/SKILL.md index 8a6dddc613..b14b4d8e0c 100644 --- a/.claude/skills/uloop-simulate-keyboard/SKILL.md +++ b/.claude/skills/uloop-simulate-keyboard/SKILL.md @@ -10,15 +10,15 @@ Simulate keyboard input on Unity PlayMode: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) -2. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-keyboard` command -3. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` +1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) +2. Execute the appropriate `uloop simulate-keyboard` command +3. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` 4. Report what happened ## Tool Reference ```bash -npx --yes uloop-cli@2.2.0 simulate-keyboard --action --key [options] +uloop simulate-keyboard --action --key [options] ``` ### Parameters @@ -55,20 +55,20 @@ npx --yes uloop-cli@2.2.0 simulate-keyboard --action --key [optio ```bash # One-shot key press (tap W once) -npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key W +uloop simulate-keyboard --action Press --key W # Jump (tap Space) -npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key Space +uloop simulate-keyboard --action Press --key Space # Hold W for 2 seconds (move forward) -npx --yes uloop-cli@2.2.0 simulate-keyboard --action Press --key W --duration 2.0 +uloop simulate-keyboard --action Press --key W --duration 2.0 # Sprint forward (hold Shift + W, then release) -npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyDown --key LeftShift -npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyDown --key W -npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering -npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyUp --key W -npx --yes uloop-cli@2.2.0 simulate-keyboard --action KeyUp --key LeftShift +uloop simulate-keyboard --action KeyDown --key LeftShift +uloop simulate-keyboard --action KeyDown --key W +uloop screenshot --capture-mode rendering +uloop simulate-keyboard --action KeyUp --key W +uloop simulate-keyboard --action KeyUp --key LeftShift ``` ## Output diff --git a/.claude/skills/uloop-simulate-mouse-input/SKILL.md b/.claude/skills/uloop-simulate-mouse-input/SKILL.md index c7b8c4ae2f..d0bca06e34 100644 --- a/.claude/skills/uloop-simulate-mouse-input/SKILL.md +++ b/.claude/skills/uloop-simulate-mouse-input/SKILL.md @@ -10,16 +10,16 @@ Simulate mouse input via Input System in Unity PlayMode: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) +1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) 2. For Click/LongPress: determine the target Game View input position from annotated `SimX`/`SimY`, raycast-grid `InputX`/`InputY`, or raw image pixels converted with `ScreenshotToInputFormula` -3. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-mouse-input` command -4. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` +3. Execute the appropriate `uloop simulate-mouse-input` command +4. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` 5. Report what happened ## Tool Reference ```bash -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action [options] +uloop simulate-mouse-input --action [options] ``` ### Parameters @@ -68,31 +68,31 @@ npx --yes uloop-cli@2.2.0 simulate-mouse-input --action [options] ```bash # Left-click at the Game View center (for game logic) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Click --x 400 --y 300 +uloop simulate-mouse-input --action Click --x 400 --y 300 # Right-click at screen center (e.g. place block) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Click --x 400 --y 300 --button Right +uloop simulate-mouse-input --action Click --x 400 --y 300 --button Right # Hold left-click for 2 seconds (e.g. mine block) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action LongPress --x 400 --y 300 --duration 2.0 +uloop simulate-mouse-input --action LongPress --x 400 --y 300 --duration 2.0 # Look right (FPS camera) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action MoveDelta --delta-x 100 --delta-y 0 +uloop simulate-mouse-input --action MoveDelta --delta-x 100 --delta-y 0 # Scroll up (e.g. previous hotbar slot) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Scroll --scroll-y 120 +uloop simulate-mouse-input --action Scroll --scroll-y 120 # Scroll down (e.g. next hotbar slot) -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action Scroll --scroll-y -120 +uloop simulate-mouse-input --action Scroll --scroll-y -120 # Smooth camera pan right over 0.5 seconds -npx --yes uloop-cli@2.2.0 simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 +uloop simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 ``` ## Coordinate System - `--x` / `--y` use **top-left Game View coordinates**. -- Raw image pixels from `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- Raw image pixels from `uloop screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. - `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. - Do not flip Y in the caller. The tool converts internally for Unity Input System: diff --git a/.claude/skills/uloop-simulate-mouse-ui/SKILL.md b/.claude/skills/uloop-simulate-mouse-ui/SKILL.md index 89a174cf13..95f6185a18 100644 --- a/.claude/skills/uloop-simulate-mouse-ui/SKILL.md +++ b/.claude/skills/uloop-simulate-mouse-ui/SKILL.md @@ -10,17 +10,17 @@ Simulate mouse interaction on Unity PlayMode UI: $ARGUMENTS ## Workflow -1. Ensure Unity is in PlayMode (use `npx --yes uloop-cli@2.2.0 control-play-mode --action Play` if not) -2. Get UI element info: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true --elements-only true` +1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) +2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true` 3. Use the `AnnotatedElements` array to find the target element by `Label`, `Name`, or `Path` (A=frontmost, B=next, ...). Use `Interaction` to distinguish click targets from drag/drop/text targets, then use `SimX`/`SimY` directly as `--x`/`--y` coordinates. -4. Execute the appropriate `npx --yes uloop-cli@2.2.0 simulate-mouse-ui` command -5. Take a screenshot to verify the result: `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true` +4. Execute the appropriate `uloop simulate-mouse-ui` command +5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements true` 6. Report what happened ## Tool Reference ```bash -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action --x --y [options] +uloop simulate-mouse-ui --action --x --y [options] ``` ### Parameters @@ -78,34 +78,34 @@ npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action --x --y [o ```bash # Click a button at a Game View position -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Click --x 400 --y 300 +uloop simulate-mouse-ui --action Click --x 400 --y 300 # Force-click a button behind a raycast blocker by path -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Click --x 400 --y 300 --bypass-raycast true --target-path "Canvas/Panel/Button" +uloop simulate-mouse-ui --action Click --x 400 --y 300 --bypass-raycast true --target-path "Canvas/Panel/Button" # Force-long-press a button behind a raycast blocker by path -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 --bypass-raycast true --target-path "Canvas/Panel/Button" +uloop simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 --bypass-raycast true --target-path "Canvas/Panel/Button" # Force-drag an item behind a raycast blocker by path -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" +uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" # Force-drag and dispatch Drop to a blocked drop zone -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" --drop-target-path "Canvas/DropZone" +uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --bypass-raycast true --target-path "Canvas/Item" --drop-target-path "Canvas/DropZone" # Long-press a button for 3 seconds -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 +uloop simulate-mouse-ui --action LongPress --x 400 --y 300 --duration 3.0 # One-shot drag (start to end in one call) -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 +uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 # Slow drag for visual inspection -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --drag-speed 200 +uloop simulate-mouse-ui --action Drag --from-x 400 --from-y 300 --x 600 --y 300 --drag-speed 200 # Split drag with hold (for inspection between steps) -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragStart --x 400 --y 300 -npx --yes uloop-cli@2.2.0 screenshot --window-name Game -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragMove --x 500 --y 300 -npx --yes uloop-cli@2.2.0 simulate-mouse-ui --action DragEnd --x 600 --y 300 +uloop simulate-mouse-ui --action DragStart --x 400 --y 300 +uloop screenshot --window-name Game +uloop simulate-mouse-ui --action DragMove --x 500 --y 300 +uloop simulate-mouse-ui --action DragEnd --x 600 --y 300 ``` ## Prerequisites @@ -127,6 +127,6 @@ Returns JSON with: - `EndPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `EndPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `npx --yes uloop-cli@2.2.0 screenshot --capture-mode rendering --annotate-elements true`. +These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements true`. Note: Click and LongPress on empty space (no UI element) still return `Success = true` with `HitGameObjectName = null`. Drag actions on empty space return `Success = false`. From 19d010a0e9a2faa2e6c057f7144dee0244bb2277 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 23:26:28 +0900 Subject: [PATCH 09/12] Clarify generated skill boolean examples Align compile skill usage with the CLI value-based boolean parser and remove the duplicated raycast screenshot example from generated skill docs. --- .agents/skills/uloop-compile/SKILL.md | 8 ++++---- .agents/skills/uloop-screenshot/SKILL.md | 3 --- .claude/skills/uloop-compile/SKILL.md | 8 ++++---- .claude/skills/uloop-screenshot/SKILL.md | 3 --- Packages/src/Editor/Api/McpTools/Compile/Skill/SKILL.md | 8 ++++---- .../src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md | 3 --- 6 files changed, 12 insertions(+), 21 deletions(-) diff --git a/.agents/skills/uloop-compile/SKILL.md b/.agents/skills/uloop-compile/SKILL.md index 6e927b236f..0f6846d80d 100644 --- a/.agents/skills/uloop-compile/SKILL.md +++ b/.agents/skills/uloop-compile/SKILL.md @@ -10,15 +10,15 @@ Execute Unity project compilation. ## Usage ```bash -uloop compile [--force-recompile] [--wait-for-domain-reload] +uloop compile [--force-recompile ] [--wait-for-domain-reload ] ``` ## Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `--force-recompile` | boolean | `false` | Force full recompilation (triggers Domain Reload) | -| `--wait-for-domain-reload` | boolean | `false` | Wait until Domain Reload completes before returning | +| `--force-recompile` | boolean value | `false` | Force full recompilation (triggers Domain Reload). Pass `true` or `false`; bare flags are not accepted. | +| `--wait-for-domain-reload` | boolean value | `false` | Wait until Domain Reload completes before returning. Pass `true` or `false`; bare flags are not accepted. | ## Global Options @@ -33,7 +33,7 @@ uloop compile [--force-recompile] [--wait-for-domain-reload] uloop compile # Force full recompilation -uloop compile --force-recompile +uloop compile --force-recompile true # Force recompilation and wait for Domain Reload completion uloop compile --force-recompile true --wait-for-domain-reload true diff --git a/.agents/skills/uloop-screenshot/SKILL.md b/.agents/skills/uloop-screenshot/SKILL.md index 92bede01d8..9f8a785a3d 100644 --- a/.agents/skills/uloop-screenshot/SKILL.md +++ b/.agents/skills/uloop-screenshot/SKILL.md @@ -70,9 +70,6 @@ uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast # Get UI element coordinates without capturing an image (fastest) uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true -# Get clustered 3D collider coordinates without capturing an image -uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true - # Take a screenshot of Scene View uloop screenshot --window-name Scene diff --git a/.claude/skills/uloop-compile/SKILL.md b/.claude/skills/uloop-compile/SKILL.md index 6e927b236f..0f6846d80d 100644 --- a/.claude/skills/uloop-compile/SKILL.md +++ b/.claude/skills/uloop-compile/SKILL.md @@ -10,15 +10,15 @@ Execute Unity project compilation. ## Usage ```bash -uloop compile [--force-recompile] [--wait-for-domain-reload] +uloop compile [--force-recompile ] [--wait-for-domain-reload ] ``` ## Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `--force-recompile` | boolean | `false` | Force full recompilation (triggers Domain Reload) | -| `--wait-for-domain-reload` | boolean | `false` | Wait until Domain Reload completes before returning | +| `--force-recompile` | boolean value | `false` | Force full recompilation (triggers Domain Reload). Pass `true` or `false`; bare flags are not accepted. | +| `--wait-for-domain-reload` | boolean value | `false` | Wait until Domain Reload completes before returning. Pass `true` or `false`; bare flags are not accepted. | ## Global Options @@ -33,7 +33,7 @@ uloop compile [--force-recompile] [--wait-for-domain-reload] uloop compile # Force full recompilation -uloop compile --force-recompile +uloop compile --force-recompile true # Force recompilation and wait for Domain Reload completion uloop compile --force-recompile true --wait-for-domain-reload true diff --git a/.claude/skills/uloop-screenshot/SKILL.md b/.claude/skills/uloop-screenshot/SKILL.md index 92bede01d8..9f8a785a3d 100644 --- a/.claude/skills/uloop-screenshot/SKILL.md +++ b/.claude/skills/uloop-screenshot/SKILL.md @@ -70,9 +70,6 @@ uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast # Get UI element coordinates without capturing an image (fastest) uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true -# Get clustered 3D collider coordinates without capturing an image -uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true - # Take a screenshot of Scene View uloop screenshot --window-name Scene diff --git a/Packages/src/Editor/Api/McpTools/Compile/Skill/SKILL.md b/Packages/src/Editor/Api/McpTools/Compile/Skill/SKILL.md index 6e927b236f..0f6846d80d 100644 --- a/Packages/src/Editor/Api/McpTools/Compile/Skill/SKILL.md +++ b/Packages/src/Editor/Api/McpTools/Compile/Skill/SKILL.md @@ -10,15 +10,15 @@ Execute Unity project compilation. ## Usage ```bash -uloop compile [--force-recompile] [--wait-for-domain-reload] +uloop compile [--force-recompile ] [--wait-for-domain-reload ] ``` ## Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `--force-recompile` | boolean | `false` | Force full recompilation (triggers Domain Reload) | -| `--wait-for-domain-reload` | boolean | `false` | Wait until Domain Reload completes before returning | +| `--force-recompile` | boolean value | `false` | Force full recompilation (triggers Domain Reload). Pass `true` or `false`; bare flags are not accepted. | +| `--wait-for-domain-reload` | boolean value | `false` | Wait until Domain Reload completes before returning. Pass `true` or `false`; bare flags are not accepted. | ## Global Options @@ -33,7 +33,7 @@ uloop compile [--force-recompile] [--wait-for-domain-reload] uloop compile # Force full recompilation -uloop compile --force-recompile +uloop compile --force-recompile true # Force recompilation and wait for Domain Reload completion uloop compile --force-recompile true --wait-for-domain-reload true diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md index 92bede01d8..9f8a785a3d 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md +++ b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md @@ -70,9 +70,6 @@ uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast # Get UI element coordinates without capturing an image (fastest) uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true -# Get clustered 3D collider coordinates without capturing an image -uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true - # Take a screenshot of Scene View uloop screenshot --window-name Scene From 08735c56f7dc14ee4164757b0ab74b956a9f6036 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 23:39:02 +0900 Subject: [PATCH 10/12] Verify compile boolean arguments require values --- .../src/Cli~/src/__tests__/cli-e2e.test.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Packages/src/Cli~/src/__tests__/cli-e2e.test.ts b/Packages/src/Cli~/src/__tests__/cli-e2e.test.ts index 0344c3f9de..eea350f698 100644 --- a/Packages/src/Cli~/src/__tests__/cli-e2e.test.ts +++ b/Packages/src/Cli~/src/__tests__/cli-e2e.test.ts @@ -889,22 +889,24 @@ describe('CLI E2E Tests (requires running Unity)', () => { // Domain Reload tests must run last to avoid affecting other tests describe('compile --force-recompile (Domain Reload)', () => { - it('should support --force-recompile option', () => { - const { exitCode } = runCli('compile --force-recompile'); + it('should reject --force-recompile without an explicit value', () => { + const result: { stdout: string; stderr: string; exitCode: number } = runCli( + 'compile --force-recompile', + ); - // Domain Reload causes connection to be lost, so we just verify the command runs - // The exit code may be non-zero due to connection being dropped during reload - expect(typeof exitCode).toBe('number'); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("option '--force-recompile ' argument missing"); }); }); describe('compile --wait-for-domain-reload', () => { - it('should support --wait-for-domain-reload option', () => { - const { exitCode } = runCli('compile --wait-for-domain-reload'); + it('should reject --wait-for-domain-reload without an explicit value', () => { + const result: { stdout: string; stderr: string; exitCode: number } = runCli( + 'compile --wait-for-domain-reload', + ); - // This option is intended to survive domain reload and return once the - // compile result is available again. - expect(typeof exitCode).toBe('number'); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("option '--wait-for-domain-reload ' argument missing"); }); }); }); From fbab1d73b25a86451f9f80df4727397633d8fd7c Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 23:45:58 +0900 Subject: [PATCH 11/12] Clarify screenshot skill coordinate scope --- .../references/playmode-automation-powershell.md | 2 +- .agents/skills/uloop-screenshot/SKILL.md | 10 +++++----- .../uloop-screenshot/references/annotated-elements.md | 2 +- .../references/playmode-automation-powershell.md | 2 +- .claude/skills/uloop-screenshot/SKILL.md | 10 +++++----- .../uloop-screenshot/references/annotated-elements.md | 2 +- .../Skill/references/playmode-automation-powershell.md | 2 +- .../src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md | 10 +++++----- .../Screenshot/Skill/references/annotated-elements.md | 2 +- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index dd6fb01735..5104257ba1 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive false ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/.agents/skills/uloop-screenshot/SKILL.md b/.agents/skills/uloop-screenshot/SKILL.md index 9f8a785a3d..9125b7b471 100644 --- a/.agents/skills/uloop-screenshot/SKILL.md +++ b/.agents/skills/uloop-screenshot/SKILL.md @@ -24,7 +24,7 @@ uloop screenshot [--window-name ] [--resolution-scale ] [--match-mo | `--output-directory` | string | `""` | Output directory path for saving screenshots. When empty, uses default path (.uloop/outputs/Screenshots/). Accepts absolute paths. | | `--annotate-elements` | boolean value | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering` in PlayMode. | | `--annotate-raycast-grid` | boolean value | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | -| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by collider and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | +| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by clickable GameObject and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; multiple colliders on the same GameObject share one entry. Samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | | `--elements-only` | boolean value | `false` | Return only annotation JSON without capturing a screenshot image. Pass `true` or `false`; bare flags are not accepted. Requires `--annotate-elements true` or `--annotate-raycast-grid true`, and `--capture-mode rendering` in PlayMode. | ## Match Modes @@ -94,10 +94,10 @@ Returns JSON with: - `Height`: Captured image height in pixels - `ImageCoordinateSystem`: `"top-left-game-view"` for `--capture-mode rendering`, or `"top-left-window"` for window captures - `ResolutionScale`: Resolution scale used for capture - - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates - - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools - - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates - - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` + - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates (rendering captures only) + - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools (rendering captures only) + - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates (rendering captures only; unavailable for window captures) + - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` (rendering captures only; empty for window captures) - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid true --raycast-layer-mask ` is used. - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. Empty when a mask is provided. diff --git a/.agents/skills/uloop-screenshot/references/annotated-elements.md b/.agents/skills/uloop-screenshot/references/annotated-elements.md index 58616dc26d..11117162d7 100644 --- a/.agents/skills/uloop-screenshot/references/annotated-elements.md +++ b/.agents/skills/uloop-screenshot/references/annotated-elements.md @@ -38,7 +38,7 @@ input_x = image_x / resolutionScale input_y = image_y / resolutionScale + imageToInputOffsetY ``` -When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0`, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are always returned as mouse-input coordinates, so pass those values directly. +When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0` for rendering captures, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates in that mode, so pass those values directly. For `PhysicsCollider` entries, `SimX/SimY` is a real sampled raycast hit nearest to the reachable cluster centroid. This avoids synthetic center points that may fall into empty space for L-shaped or ring-shaped collider coverage. Always use `SimX/SimY` for clicking; use `BoundsMinX/Y` and `BoundsMaxX/Y` only as a sampled coverage guide. diff --git a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index dd6fb01735..5104257ba1 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive false ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/.claude/skills/uloop-screenshot/SKILL.md b/.claude/skills/uloop-screenshot/SKILL.md index 9f8a785a3d..9125b7b471 100644 --- a/.claude/skills/uloop-screenshot/SKILL.md +++ b/.claude/skills/uloop-screenshot/SKILL.md @@ -24,7 +24,7 @@ uloop screenshot [--window-name ] [--resolution-scale ] [--match-mo | `--output-directory` | string | `""` | Output directory path for saving screenshots. When empty, uses default path (.uloop/outputs/Screenshots/). Accepts absolute paths. | | `--annotate-elements` | boolean value | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering` in PlayMode. | | `--annotate-raycast-grid` | boolean value | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | -| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by collider and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | +| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by clickable GameObject and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; multiple colliders on the same GameObject share one entry. Samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | | `--elements-only` | boolean value | `false` | Return only annotation JSON without capturing a screenshot image. Pass `true` or `false`; bare flags are not accepted. Requires `--annotate-elements true` or `--annotate-raycast-grid true`, and `--capture-mode rendering` in PlayMode. | ## Match Modes @@ -94,10 +94,10 @@ Returns JSON with: - `Height`: Captured image height in pixels - `ImageCoordinateSystem`: `"top-left-game-view"` for `--capture-mode rendering`, or `"top-left-window"` for window captures - `ResolutionScale`: Resolution scale used for capture - - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates - - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools - - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates - - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` + - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates (rendering captures only) + - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools (rendering captures only) + - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates (rendering captures only; unavailable for window captures) + - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` (rendering captures only; empty for window captures) - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid true --raycast-layer-mask ` is used. - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. Empty when a mask is provided. diff --git a/.claude/skills/uloop-screenshot/references/annotated-elements.md b/.claude/skills/uloop-screenshot/references/annotated-elements.md index 58616dc26d..11117162d7 100644 --- a/.claude/skills/uloop-screenshot/references/annotated-elements.md +++ b/.claude/skills/uloop-screenshot/references/annotated-elements.md @@ -38,7 +38,7 @@ input_x = image_x / resolutionScale input_y = image_y / resolutionScale + imageToInputOffsetY ``` -When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0`, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are always returned as mouse-input coordinates, so pass those values directly. +When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0` for rendering captures, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates in that mode, so pass those values directly. For `PhysicsCollider` entries, `SimX/SimY` is a real sampled raycast hit nearest to the reachable cluster centroid. This avoids synthetic center points that may fall into empty space for L-shaped or ring-shaped collider coverage. Always use `SimX/SimY` for clicking; use `BoundsMinX/Y` and `BoundsMaxX/Y` only as a sampled coverage guide. diff --git a/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md b/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md index dd6fb01735..5104257ba1 100644 --- a/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md +++ b/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive false ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md index 9f8a785a3d..9125b7b471 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md +++ b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md @@ -24,7 +24,7 @@ uloop screenshot [--window-name ] [--resolution-scale ] [--match-mo | `--output-directory` | string | `""` | Output directory path for saving screenshots. When empty, uses default path (.uloop/outputs/Screenshots/). Accepts absolute paths. | | `--annotate-elements` | boolean value | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering` in PlayMode. | | `--annotate-raycast-grid` | boolean value | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | -| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by collider and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | +| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by clickable GameObject and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; multiple colliders on the same GameObject share one entry. Samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | | `--elements-only` | boolean value | `false` | Return only annotation JSON without capturing a screenshot image. Pass `true` or `false`; bare flags are not accepted. Requires `--annotate-elements true` or `--annotate-raycast-grid true`, and `--capture-mode rendering` in PlayMode. | ## Match Modes @@ -94,10 +94,10 @@ Returns JSON with: - `Height`: Captured image height in pixels - `ImageCoordinateSystem`: `"top-left-game-view"` for `--capture-mode rendering`, or `"top-left-window"` for window captures - `ResolutionScale`: Resolution scale used for capture - - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates - - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools - - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates - - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` + - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates (rendering captures only) + - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools (rendering captures only) + - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates (rendering captures only; unavailable for window captures) + - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` (rendering captures only; empty for window captures) - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid true --raycast-layer-mask ` is used. - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. Empty when a mask is provided. diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md index 58616dc26d..11117162d7 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md +++ b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md @@ -38,7 +38,7 @@ input_x = image_x / resolutionScale input_y = image_y / resolutionScale + imageToInputOffsetY ``` -When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0`, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are always returned as mouse-input coordinates, so pass those values directly. +When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0` for rendering captures, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates in that mode, so pass those values directly. For `PhysicsCollider` entries, `SimX/SimY` is a real sampled raycast hit nearest to the reachable cluster centroid. This avoids synthetic center points that may fall into empty space for L-shaped or ring-shaped collider coverage. Always use `SimX/SimY` for clicking; use `BoundsMinX/Y` and `BoundsMaxX/Y` only as a sampled coverage guide. From 706e98882ab564bac30d10975ef72eac616889ea Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 4 Jul 2026 23:50:28 +0900 Subject: [PATCH 12/12] Clarify hierarchy inactive exploration example --- .../references/playmode-automation-powershell.md | 2 +- .../references/playmode-automation-powershell.md | 2 +- .../Skill/references/playmode-automation-powershell.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index 5104257ba1..15c34ac072 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive false +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive true ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index 5104257ba1..15c34ac072 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive false +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive true ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md b/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md index 5104257ba1..15c34ac072 100644 --- a/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md +++ b/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive false +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive true ``` **Step 2**: Based on the hierarchy JSON, click the desired button