205 lines
5.3 KiB
Markdown
205 lines
5.3 KiB
Markdown
# 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)
|
||
```gdscript
|
||
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)
|
||
```gdscript
|
||
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)
|
||
```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场景搭建
|