@@ -422,6 +422,105 @@ public static object AssignToGameObject(JObject @params)
422422 } ;
423423 }
424424
425+ // Reads node graph positions for every state in all layers (recurses into
426+ // sub-state-machines). Returns [{ name, x, y, layer }] so a caller can analyze
427+ // the current layout before sending back a revised one.
428+ public static object GetStatePositions ( JObject @params )
429+ {
430+ var controller = LoadController ( @params ) ;
431+ if ( controller == null )
432+ return ControllerNotFoundError ( @params ) ;
433+
434+ var nodes = new List < object > ( ) ;
435+ for ( int li = 0 ; li < controller . layers . Length ; li ++ )
436+ CollectPositions ( controller . layers [ li ] . stateMachine , li , nodes ) ;
437+
438+ return new
439+ {
440+ success = true ,
441+ message = $ "Read { nodes . Count } state position(s).",
442+ data = new { count = nodes . Count , nodes }
443+ } ;
444+ }
445+
446+ private static void CollectPositions ( AnimatorStateMachine sm , int layer , List < object > outList )
447+ {
448+ var children = sm . states ;
449+ for ( int i = 0 ; i < children . Length ; i ++ )
450+ outList . Add ( new
451+ {
452+ name = children [ i ] . state . name ,
453+ x = children [ i ] . position . x ,
454+ y = children [ i ] . position . y ,
455+ layer
456+ } ) ;
457+ foreach ( var sub in sm . stateMachines )
458+ CollectPositions ( sub . stateMachine , layer , outList ) ;
459+ }
460+
461+ // Sets node graph positions from a 'positions' array of { name, x, y }.
462+ // Matches states by name across all layers (recurses into sub-state-machines)
463+ // and reassigns stateMachine.states so the edits persist on the asset.
464+ public static object SetStatePositions ( JObject @params )
465+ {
466+ var controller = LoadController ( @params ) ;
467+ if ( controller == null )
468+ return ControllerNotFoundError ( @params ) ;
469+
470+ if ( ! ( @params [ "positions" ] is JArray positions ) || positions . Count == 0 )
471+ return new { success = false , message = "'positions' array is required: [{ name, x, y }, ...]" } ;
472+
473+ var want = new Dictionary < string , Vector2 > ( ) ;
474+ foreach ( var token in positions )
475+ {
476+ string name = token [ "name" ] ? . ToString ( ) ;
477+ if ( string . IsNullOrEmpty ( name ) )
478+ continue ;
479+ float x = token [ "x" ] ? . ToObject < float > ( ) ?? 0f ;
480+ float y = token [ "y" ] ? . ToObject < float > ( ) ?? 0f ;
481+ want [ name ] = new Vector2 ( x , y ) ;
482+ }
483+ if ( want . Count == 0 )
484+ return new { success = false , message = "No valid entries in 'positions' (each needs a 'name')." } ;
485+
486+ var matched = new HashSet < string > ( ) ;
487+ for ( int li = 0 ; li < controller . layers . Length ; li ++ )
488+ ApplyPositions ( controller . layers [ li ] . stateMachine , want , matched ) ;
489+
490+ EditorUtility . SetDirty ( controller ) ;
491+ AssetDatabase . SaveAssets ( ) ;
492+
493+ var unmatched = want . Keys . Where ( k => ! matched . Contains ( k ) ) . ToList ( ) ;
494+ return new
495+ {
496+ success = true ,
497+ message = $ "Positioned { matched . Count } state(s); { unmatched . Count } name(s) unmatched.",
498+ data = new
499+ {
500+ matched = matched . Count ,
501+ requested = want . Count ,
502+ unmatched
503+ }
504+ } ;
505+ }
506+
507+ private static void ApplyPositions ( AnimatorStateMachine sm , Dictionary < string , Vector2 > want , HashSet < string > matched )
508+ {
509+ var children = sm . states ;
510+ for ( int i = 0 ; i < children . Length ; i ++ )
511+ {
512+ if ( want . TryGetValue ( children [ i ] . state . name , out var p ) )
513+ {
514+ children [ i ] . position = new Vector3 ( p . x , p . y , 0f ) ;
515+ matched . Add ( children [ i ] . state . name ) ;
516+ }
517+ }
518+ sm . states = children ; // reassign so position edits persist
519+
520+ foreach ( var sub in sm . stateMachines )
521+ ApplyPositions ( sub . stateMachine , want , matched ) ;
522+ }
523+
425524 private static AnimatorController LoadController ( JObject @params )
426525 {
427526 string controllerPath = @params [ "controllerPath" ] ? . ToString ( ) ;
0 commit comments