AeThex-Engine-Core/docs/STUDIO_WIRING_GUIDE.md

15 KiB

Studio UI Wiring Guide

Complete mapping: Godot Editor Features → StudioBridge API → Studio Components

🎯 Goal

Wire Studio UI (TypeScript/React) to have 100% feature parity with Godot editor using the StudioBridge API.


📦 Part 1: What Godot Has (C++)

Core Editor Panels

editor/
├── docks/
│   ├── scene_tree_dock.cpp      → Scene hierarchy
│   ├── inspector_dock.cpp       → Property editor
│   ├── filesystem_dock.cpp      → File browser
│   └── node_dock.cpp            → Node info
├── plugins/
│   ├── script_editor_plugin.cpp → Code editor
│   ├── canvas_item_editor_plugin.cpp → 2D viewport
│   └── node_3d_editor_plugin.cpp → 3D viewport
└── editor_node.cpp              → Main window/orchestration

🔌 Part 2: StudioBridge API (What We Already Built)

Already Implemented (Basic CRUD)

// Scene management
loadScene(path)         Load .tscn file
saveScene(path)         Save current scene
getSceneTree()          Get full hierarchy

// Node operations
createNode(type, parent, name)  Add new node
deleteNode(path)        Remove node
selectNode(path)        Set active node

// Properties
setProperty(path, property, value)  Update node property
getProperty(path, property)         Read property value

// Game control
runGame()               Launch game instance
stopGame()              Stop game

Need to Add (Advanced Features)

Scene Tree Operations

// Add to studio_bridge.cpp:
Dictionary move_node(node_path, new_parent_path, position)
Dictionary duplicate_node(node_path)
Dictionary rename_node(node_path, new_name)
Dictionary reparent_node(node_path, new_parent_path)
Dictionary copy_node(node_path)  // to clipboard
Dictionary paste_node(parent_path)  // from clipboard
Dictionary get_node_groups(node_path)
Dictionary add_to_group(node_path, group_name)

Inspector/Properties

Dictionary get_all_properties(node_path)  // Get full property list
Dictionary get_property_info(node_path, property)  // Type, hint, range
Dictionary reset_property(node_path, property)  // Reset to default
Array get_inspectable_nodes()  // Multi-select support

Resource/Asset Management

Dictionary list_directory(path)  // File system browsing
Dictionary import_asset(path, type)
Dictionary create_resource(type, path)
Dictionary load_resource(path)
Array get_recent_files()

Script Editor

Dictionary attach_script(node_path, script_path)
Dictionary detach_script(node_path)
String get_node_script(node_path)
Dictionary save_script(path, content)
Dictionary open_script(path)

3D/2D Viewport

Dictionary get_viewport_texture()  // Stream 3D view to Studio
Dictionary set_camera_position(x, y, z)
Dictionary set_camera_rotation(x, y, z)
Dictionary toggle_gizmos(enabled)
Dictionary set_grid_visible(visible)

🎨 Part 3: Studio UI Components (TypeScript/React)

File Structure

studio/src/
├── engine/
│   ├── bridge.ts              ← API client (wraps fetch calls)
│   ├── types.ts               ← Node, Property, Scene types
│   └── hooks.ts               ← React hooks (useSceneTree, etc)
├── components/
│   ├── SceneTree/
│   │   ├── SceneTreePanel.tsx    ← Left sidebar hierarchy
│   │   ├── NodeItem.tsx          ← Tree node component
│   │   └── ContextMenu.tsx       ← Right-click menu
│   ├── Inspector/
│   │   ├── InspectorPanel.tsx    ← Right sidebar properties
│   │   ├── PropertyEditor.tsx    ← Individual property input
│   │   └── ResourcePicker.tsx    ← Resource selection
│   ├── Viewport/
│   │   ├── Viewport2D.tsx        ← 2D scene view
│   │   ├── Viewport3D.tsx        ← 3D scene view (WebGL/Three.js)
│   │   └── ViewportControls.tsx  ← Camera controls
│   ├── FileSystem/
│   │   ├── FileSystemPanel.tsx   ← Bottom file browser
│   │   └── AssetPreview.tsx      ← Preview images/models
│   ├── ScriptEditor/
│   │   ├── CodeEditor.tsx        ← Monaco editor wrapper
│   │   └── ScriptTabs.tsx        ← Open script tabs
│   └── Toolbar/
│       ├── MainToolbar.tsx       ← Top toolbar (Play, Stop, etc)
│       └── NodeToolbar.tsx       ← Node-specific tools
└── layouts/
    └── EditorLayout.tsx          ← Main layout orchestration

🔧 Implementation Roadmap

Phase 1: Extend StudioBridge API (Engine Side)

File: engine/modules/studio_bridge/studio_bridge.h

Add these method declarations:

// Advanced node operations
Dictionary move_node(const Dictionary &p_params);
Dictionary duplicate_node(const Dictionary &p_params);
Dictionary rename_node(const Dictionary &p_params);

// Advanced properties
Dictionary get_all_properties(const Dictionary &p_params);
Dictionary get_property_info(const Dictionary &p_params);

// File system
Dictionary list_directory(const Dictionary &p_params);
Dictionary get_recent_files(const Dictionary &p_params);

// Scripts
Dictionary attach_script(const Dictionary &p_params);
Dictionary get_node_script(const Dictionary &p_params);

// Viewport streaming
Dictionary get_viewport_texture(const Dictionary &p_params);
Dictionary set_camera_transform(const Dictionary &p_params);

File: engine/modules/studio_bridge/studio_bridge.cpp

Implement these methods (similar to existing _handle_create_node, etc.)


Phase 2: Build Studio Bridge Client (Studio Side)

File: studio/src/engine/bridge.ts

export class EngineBridge {
  private baseUrl = 'http://localhost:6007';
  private ws: WebSocket | null = null;
  
  // Basic API calls
  async call(method: string, params: any = {}) {
    const response = await fetch(`${this.baseUrl}/rpc`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ method, params })
    });
    return response.json();
  }
  
  // Scene Tree
  async loadScene(path: string) {
    return this.call('loadScene', { path });
  }
  
  async getSceneTree() {
    return this.call('getSceneTree', {});
  }
  
  async createNode(type: string, parent: string, name: string) {
    return this.call('createNode', { type, parent, name });
  }
  
  async deleteNode(path: string) {
    return this.call('deleteNode', { path });
  }
  
  async moveNode(path: string, newParent: string, position: number) {
    return this.call('moveNode', { path, newParent, position });
  }
  
  async duplicateNode(path: string) {
    return this.call('duplicateNode', { path });
  }
  
  // Properties
  async getAllProperties(path: string) {
    return this.call('getAllProperties', { path });
  }
  
  async setProperty(path: string, property: string, value: any) {
    return this.call('setProperty', { path, property, value });
  }
  
  async getProperty(path: string, property: string) {
    return this.call('getProperty', { path, property });
  }
  
  // File System
  async listDirectory(path: string) {
    return this.call('listDirectory', { path });
  }
  
  // Scripts
  async attachScript(nodePath: string, scriptPath: string) {
    return this.call('attachScript', { nodePath, scriptPath });
  }
  
  // Game Control
  async runGame() {
    return this.call('runGame', {});
  }
  
  async stopGame() {
    return this.call('stopGame', {});
  }
  
  // WebSocket Events
  connectEvents(callbacks: {
    onSceneChanged?: () => void;
    onNodeSelected?: (node: any) => void;
    onPropertyChanged?: (path: string, property: string) => void;
    onConsoleOutput?: (message: string, type: string) => void;
  }) {
    this.ws = new WebSocket('ws://localhost:6007/events');
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      
      if (data.event === 'scene_changed' && callbacks.onSceneChanged) {
        callbacks.onSceneChanged();
      } else if (data.event === 'node_selected' && callbacks.onNodeSelected) {
        callbacks.onNodeSelected(data.node);
      } else if (data.event === 'property_changed' && callbacks.onPropertyChanged) {
        callbacks.onPropertyChanged(data.path, data.property);
      } else if (data.event === 'console_output' && callbacks.onConsoleOutput) {
        callbacks.onConsoleOutput(data.message, data.type);
      }
    };
  }
}

// Singleton instance
export const bridge = new EngineBridge();

Phase 3: Build React Components (Studio Side)

File: studio/src/components/SceneTree/SceneTreePanel.tsx

import { useState, useEffect } from 'react';
import { bridge } from '@/engine/bridge';

interface Node {
  name: string;
  type: string;
  path: string;
  children: Node[];
}

export function SceneTreePanel() {
  const [tree, setTree] = useState<Node | null>(null);
  const [selectedPath, setSelectedPath] = useState<string>('');
  
  useEffect(() => {
    // Load initial scene tree
    loadTree();
    
    // Listen for changes
    bridge.connectEvents({
      onSceneChanged: loadTree,
      onNodeSelected: (node) => setSelectedPath(node.path)
    });
  }, []);
  
  async function loadTree() {
    const result = await bridge.getSceneTree();
    if (result.success) {
      setTree(result.result);
    }
  }
  
  async function handleCreateNode(type: string, parentPath: string) {
    await bridge.createNode(type, parentPath, `New${type}`);
    loadTree();
  }
  
  async function handleDeleteNode(path: string) {
    await bridge.deleteNode(path);
    loadTree();
  }
  
  async function handleSelectNode(path: string) {
    await bridge.call('selectNode', { path });
    setSelectedPath(path);
  }
  
  return (
    <div className="scene-tree-panel">
      <div className="toolbar">
        <button onClick={() => handleCreateNode('Node2D', selectedPath)}>
          + Add Node
        </button>
        <button onClick={() => handleDeleteNode(selectedPath)}>
          Delete
        </button>
      </div>
      
      {tree && (
        <NodeTree 
          node={tree} 
          selectedPath={selectedPath}
          onSelect={handleSelectNode}
        />
      )}
    </div>
  );
}

function NodeTree({ node, selectedPath, onSelect }: {
  node: Node;
  selectedPath: string;
  onSelect: (path: string) => void;
}) {
  const [expanded, setExpanded] = useState(true);
  
  return (
    <div className="node-item">
      <div 
        className={`node-header ${node.path === selectedPath ? 'selected' : ''}`}
        onClick={() => onSelect(node.path)}
      >
        <span onClick={() => setExpanded(!expanded)}>
          {node.children.length > 0 ? (expanded ? '▼' : '▶') : '  '}
        </span>
        <span className="node-icon">{getIconForType(node.type)}</span>
        <span className="node-name">{node.name}</span>
        <span className="node-type">{node.type}</span>
      </div>
      
      {expanded && node.children.length > 0 && (
        <div className="node-children">
          {node.children.map((child) => (
            <NodeTree 
              key={child.path}
              node={child}
              selectedPath={selectedPath}
              onSelect={onSelect}
            />
          ))}
        </div>
      )}
    </div>
  );
}

function getIconForType(type: string): string {
  // Return icon based on node type
  const icons: Record<string, string> = {
    'Node2D': '🎯',
    'Sprite2D': '🖼️',
    'Camera2D': '📷',
    'CharacterBody2D': '🏃',
    'Node3D': '📦',
    'MeshInstance3D': '🎲',
    // ... add more
  };
  return icons[type] || '⚫';
}

File: studio/src/components/Inspector/InspectorPanel.tsx

import { useState, useEffect } from 'react';
import { bridge } from '@/engine/bridge';

export function InspectorPanel({ selectedNodePath }: { selectedNodePath: string }) {
  const [properties, setProperties] = useState<any[]>([]);
  
  useEffect(() => {
    if (selectedNodePath) {
      loadProperties();
    }
  }, [selectedNodePath]);
  
  async function loadProperties() {
    const result = await bridge.getAllProperties(selectedNodePath);
    if (result.success) {
      setProperties(result.result);
    }
  }
  
  async function handlePropertyChange(property: string, value: any) {
    await bridge.setProperty(selectedNodePath, property, value);
  }
  
  if (!selectedNodePath) {
    return <div className="inspector-empty">No node selected</div>;
  }
  
  return (
    <div className="inspector-panel">
      <h3>Inspector</h3>
      <div className="property-list">
        {properties.map((prop) => (
          <PropertyEditor
            key={prop.name}
            property={prop}
            onChange={(value) => handlePropertyChange(prop.name, value)}
          />
        ))}
      </div>
    </div>
  );
}

function PropertyEditor({ property, onChange }: any) {
  // Render different input types based on property type
  if (property.type === 'Vector2') {
    return (
      <div className="property-vector2">
        <label>{property.name}</label>
        <input 
          type="number" 
          value={property.value.x}
          onChange={(e) => onChange({ ...property.value, x: +e.target.value })}
        />
        <input 
          type="number" 
          value={property.value.y}
          onChange={(e) => onChange({ ...property.value, y: +e.target.value })}
        />
      </div>
    );
  }
  
  if (property.type === 'bool') {
    return (
      <div className="property-bool">
        <label>{property.name}</label>
        <input 
          type="checkbox"
          checked={property.value}
          onChange={(e) => onChange(e.target.checked)}
        />
      </div>
    );
  }
  
  // Default: text input
  return (
    <div className="property-default">
      <label>{property.name}</label>
      <input 
        type="text"
        value={property.value}
        onChange={(e) => onChange(e.target.value)}
      />
    </div>
  );
}

📊 Feature Checklist

Core (Already Built via StudioBridge)

  • Load/save scenes
  • Create/delete nodes
  • Get/set properties
  • Scene tree hierarchy

Advanced (Need to Add)

  • Move/reparent nodes
  • Duplicate nodes
  • Copy/paste nodes
  • Undo/redo system
  • Multi-node selection
  • Node groups
  • Script attachment
  • Resource management
  • Asset import
  • 2D/3D viewport rendering
  • Camera controls
  • Gizmos (transform handles)
  • Debugger integration
  • Profiler data
  • Remote scene tree (running game)

🚀 Next Steps

  1. Extend StudioBridge API - Add 20+ new RPC methods for advanced features
  2. Implement HTTP Server - Make the bridge actually accept network requests
  3. Build React Components - Create SceneTree, Inspector, Viewport panels
  4. Add WebSocket Events - Real-time updates Engine → Studio
  5. Integrate Monaco Editor - For script editing
  6. Viewport Streaming - Show 3D scene in Studio UI

Estimated Time: 4-6 weeks for full feature parity

Want me to start implementing the extended API methods?