Files
val-blog/ai-agent-living/docs/TECH_SPEC.md
T

5.3 KiB
Raw Blame History

AgentLiving - 技术规格文档

1. Godot 项目结构

ai-agent-living/
├── assets/
│   ├── sprites/
│   │   ├── val/
│   │   │   ├── idle.png
│   │   │   ├── walk.png
│   │   │   └── interact.png
│   │   └── furniture/
│   │       ├── sofa.png
│   │       ├── fridge.png
│   │       └── bed.png
│   ├── tilesets/
│   │   └── apartment_floor.tres
│   └── audio/
│       └── ambient.mp3
├── scenes/
│   ├── main.tscn
│   ├── apartment.tscn
│   ├── val.tscn
│   └── furniture/
│       ├── sofa.tscn
│       └── fridge.tscn
├── scripts/
│   ├── val.gd
│   ├── apartment.gd
│   ├── bridge_client.gd
│   └── state_manager.gd
└── project.godot

2. 关键类设计

2.1 Val (CharacterBody2D)

class_name Val
extends CharacterBody2D

@export var speed: float = 100.0
@onready var sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var thought_bubble: Label = $ThoughtBubble

var current_state: String = "idle"
var target_position: Vector2 = Vector2.ZERO
var agent_id: String = "val"

func _ready():
    BridgeClient.connect("action_received", _on_action)
    _connect_to_openclaw()

func _physics_process(delta):
    match current_state:
        "walk": _handle_walk(delta)
        "interact": _handle_interact(delta)
        "think": _handle_think(delta)
        _: _handle_idle(delta)

func move_to(pos: Vector2):
    target_position = pos
    current_state = "walk"
    sprite.play("walk")

func _on_action(action: Dictionary):
    match action.cmd:
        "walk_to": move_to(Vector2(action.pos[0], action.pos[1]))
        "play_anim": sprite.play(action.anim)
        "show_thought": _show_thought(action.text)
        "update_state": _update_internal_state(action.key, action.value)

2.2 BridgeClient (WebSocket)

extends Node

signal action_received(action: Dictionary)
signal state_synced(state: Dictionary)

var socket: WebSocketPeer
var openclaw_url: String = "ws://127.0.0.1:18789"

func _ready():
    _connect()

func _connect():
    socket = WebSocketPeer.new()
    socket.connect_to_url(openclaw_url)

func _process(delta):
    socket.poll()
    var state = socket.get_ready_state()
    if state == WebSocketPeer.STATE_OPEN:
        while socket.get_available_packets() > 0:
            var packet = socket.get_packet()
            var message = JSON.parse_string(packet.get_string_from_utf8())
            _handle_message(message)

func send_command(command: Dictionary):
    if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
        socket.send_text(JSON.stringify(command))

func _handle_message(msg: Dictionary):
    match msg.type:
        "action_sequence":
            for action in msg.actions:
                action_received.emit(action)
        "state_sync":
            state_synced.emit(msg.state)

3. OpenClaw端集成

3.1 AgentLiving Skill

创建 ~/.openclaw/skills/agent-living/

agent-living/
├── SKILL.md
├── scripts/
│   └── bridge_server.py
└── config.json

3.2 Bridge Server (Python)

# bridge_server.py
import asyncio
import websockets
import json
from openclaw import session_manager

class AgentLivingBridge:
    def __init__(self):
        self.agents = {}
        self.godot_clients = {}
    
    async def handle_godot(self, websocket, path):
        """处理Godot客户端连接"""
        async for message in websocket:
            data = json.loads(message)
            response = await self._process_command(data)
            await websocket.send(json.dumps(response))
    
    async def _process_command(self, cmd: dict) -> dict:
        """将Godot指令转换为Agent动作"""
        if cmd["type"] == "user_command":
            # 调用OpenClaw Agent处理
            agent_session = session_manager.get("val")
            result = await agent_session.send_message(
                f"用户在Godot中发出指令:{cmd}"
            )
            return self._parse_agent_response(result)
    
    def _parse_agent_response(self, response: str) -> dict:
        """解析Agent回复为Godot动作序列"""
        # 提取动作序列,格式化为Godot可执行的JSON
        actions = []
        # ... 解析逻辑
        return {"type": "action_sequence", "actions": actions}

# 启动服务器
async def main():
    bridge = AgentLivingBridge()
    async with websockets.serve(
        bridge.handle_godot, 
        "localhost", 
        8765
    ):
        await asyncio.Future()  # 永久运行

if __name__ == "__main__":
    asyncio.run(main())

4. 状态同步策略

4.1 心跳机制

  • Godot每5秒发送状态查询
  • OpenClaw立即返回完整状态
  • 差异超过阈值时触发同步

4.2 事件驱动

  • Agent状态变化 → 立即推送到Godot
  • 用户交互 → 立即发送到Agent
  • 避免轮询开销

5. 性能优化

5.1 Agent端

  • 简单行为(走动、idle)用本地规则
  • 复杂决策(任务分解)才调用LLM
  • 缓存常用响应

5.2 Godot端

  • 对象池复用粒子效果
  • 远处角色降低动画帧率
  • 按需加载房间(视野外不渲染)

下一步: Week 1 开始Godot场景搭建