Fable5不能用了,但是依然能让 AI 纯靠截图玩通宝可梦

0 阅读18分钟

我们不读内存,不 hack ROM,只给 AI 一张张游戏截图,让它像人类一样「看屏幕、做决策、按按钮」——最终,AI 从真新镇出发,选宝可梦、打道馆、一路通关。


name_cleared.png

目录

  1. 为什么做这件事
  2. 整体架构
  3. 核心模块详解
  4. 踩坑实录:从零到可用的调试之旅
  5. Agent 的大脑:提示词工程
  6. 防循环:让 AI 不再原地打转
  7. 外挂记忆:AI 的笔记本
  8. 从截图到视频:完整录屏
  9. 8-bit 音乐生成:用代码演奏宝可梦主题曲
  10. 最终成果与数据
  11. 经验总结与未来展望

1. 为什么做这件事

2025 年,Claude Fable 5 模型发布时宣称能独立通关宝可梦游戏。这个消息点燃了我们的好奇心:一个 AI 模型,仅凭屏幕画面,真的能理解并玩通一个完整的 RPG 游戏吗?

我们决定自己动手验证。目标很明确:

  • 纯视觉方案:不读取游戏内存,不修改 ROM 数据,AI 只能看到屏幕截图
  • 完整通关:从真新镇出发,收集 8 枚道馆徽章,打败四天王
  • 可复现:代码开源,任何人都能跑起来看 AI 玩游戏

这是一个关于 Vision LLM + 游戏模拟器 的 AI Agent 实验。整个项目从零开始,经历了无数次失败和调试,最终成功让 AI 在 Game Boy 的宝可梦世界中自主行动。

使用的模型是小米的Mimo v2.5,已经把claude haiku、sonnet、opus都映射为同一款模型MIMO-v2.5。

d671704790628243c8069fa54ef25a14.png


2. 整体架构

整个系统的架构可以概括为一个 感知-思考-行动 的闭环:

┌─────────────────────────────────────────────────────────────┐
│                    AI Agent 主循环                           │
│                                                             │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐              │
│  │  PyBoy   │───>│  截图预处理 │───>│ Vision   │              │
│  │ 模拟器    │    │  2x放大   │    │   LLM    │              │
│  │          │    │  对比度增强 │    │ 分析画面  │              │
│  └──────────┘    └──────────┘    └────┬─────┘              │
│       ^                               │                     │
│       │         ┌──────────┐          │                     │
│       └─────────│  按钮执行  │<───────┘                     │
│                 │ 模拟输入  │    THOUGHT / ACTION / NOTE     │
│                 └──────────┘                                │
│                                                             │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐              │
│  │ 防循环检测 │    │ 外部记忆  │    │ 日志记录  │              │
│  │ 3种策略   │    │ markdown  │    │ JSONL    │              │
│  └──────────┘    └──────────┘    └──────────┘              │
└─────────────────────────────────────────────────────────────┘

核心文件结构:

pokemon/
├── config.py          # 全局配置(LLM、模拟器、Agent 参数)
├── emulator.py        # PyBoy v2 模拟器封装
├── llm_client.py      # 多 Provider LLM 客户端
├── agent.py           # Agent 核心循环(v2 模块化版本)
├── agent_v2.py        # Agent 主程序
├── state_manager.py   # 外部记忆管理
├── play.py            # 完整录屏版(单文件)
├── prompts/
│   └── system_prompt.txt   # 中文系统提示词
├── memory/
│   └── journal.txt    # AI 的游戏笔记
├── screenshots/       # 每步截图(5000+ 张)
├── logs/              # Agent 决策日志
└── music/             # 生成的 8-bit 音乐

3. 核心模块详解

3.1 模拟器层:PyBoy v2

我们选择 PyBoy 作为 Game Boy 模拟器。它是一个纯 Python 实现的 Game Boy/Game Boy Color/Game Boy Advance 模拟器,提供了完整的 Python API,可以:

  • 加载 ROM 文件
  • 发送按键输入
  • 截取屏幕画面
  • 读写 Game Boy 内存 skip_00.png 核心封装代码:
class Emulator:
    def __init__(self, rom_path, headless=True, turbo=True):
        self.pyboy = PyBoy(rom_path, window="null" if headless else "SDL2")
        # 关键发现:必须用 1x 速度!无限速度会导致碰撞检测失效
        self.pyboy.set_emulation_speed(1)

    def press_button(self, button, hold_frames=3):
        """按下并释放一个按钮"""
        self.pyboy.button(button, hold_frames)
        for _ in range(hold_frames + 1):
            self.pyboy.tick()

    def get_screenshot(self):
        """获取当前屏幕画面"""
        return self.pyboy.screen.image

    def _get_pos(self):
        """读取角色位置(内存地址)"""
        return self.pyboy.memory[0xD361], self.pyboy.memory[0xD362]

关键发现:必须使用 1x 速度

PyBoy 的 set_emulation_speed(0) 是无限速度模式,理论上可以让游戏跑得飞快。但我们发现,在无限速度下,Game Boy 的碰撞检测系统会完全失效——角色会穿过墙壁、掉出地图、卡在各种奇怪的地方。

原因是 Pokemon Red 的碰撞检测依赖于精确的帧计时(frame timing),而无限速度跳过了大量帧,导致游戏逻辑和渲染不同步。最终我们选择了 set_emulation_speed(1),虽然慢但稳定。

3.2 LLM 客户端:多 Provider 支持

我们的 LLM 客户端支持三种主流视觉模型,可以通过配置轻松切换:

class LLMClient:
    def __init__(self, provider="anthropic", model=None):
        if provider == "anthropic":
            self.client = anthropic.Anthropic(
                base_url="http://127.0.0.1:15721",  # 本地代理
            )
            self.model = model or "claude-haiku-4-5"
        elif provider == "openai":
            self.client = openai.OpenAI()
            self.model = model or "gpt-4o"
        elif provider == "gemini":
            from google import genai
            self.client = genai.Client()
            self.model = model or "gemini-2.5-pro"

    def analyze_screenshot(self, image, system_prompt, user_message):
        """将截图发送给 LLM,获取决策"""
        # 图片转 base64 编码
        b64 = image_to_base64(image)
        response = self.client.messages.create(
            model=self.model,
            system=system_prompt,
            messages=[{
                "role": "user",
                "content": [
                    {"type": "image", "source": {"type": "base64", "data": b64}},
                    {"type": "text", "text": user_message},
                ],
            }],
        )
        # 过滤 ThinkingBlock(扩展思考模式的输出)
        return "\n".join(
            block.text for block in response.content if hasattr(block, "text")
        )

踩坑:ThinkingBlock 过滤

早期我们发现 Claude Haiku 模型有时会返回 ThinkingBlock(扩展思考的中间过程),如果不过滤会导致解析 ACTION 时出错。解决方案是显式传入 thinking={"type": "disabled"} 并过滤输出中的非文本块。

踩坑:429 限流

每一步都要调用一次 LLM API,如果不加延迟,很快就会触发 429 Rate Limiting。我们最终将 STEP_DELAY 设为 0.8 秒,即每步之间等待 0.8 秒,刚好在限流阈值之下。

3.3 Agent 核心循环

Agent 的核心是一个无限循环:截图 → 分析 → 决策 → 执行 → 记录

def run(self):
    for step in range(MAX_STEPS):
        # 1. 截图
        raw = self.emulator.get_screenshot()
        processed = self.preprocess(raw)

        # 2. LLM 分析
        memory = self.state.get_memory()
        user_msg = self.build_user_message(step, memory)
        response = self.llm.analyze_screenshot(
            processed, self.system_prompt, user_msg
        )

        # 3. 解析决策
        action = self.parse_action(response)    # 从回复中提取 ACTION
        note = self.parse_note(response)        # 从回复中提取 NOTE
        thought = self.parse_thought(response)  # 从回复中提取 THOUGHT

        # 4. 执行
        self.emulator.press_button(action)

        # 5. 更新记忆
        if note:
            self.state.append_memory(f"[Step {step}] {note}")

        # 6. 记录日志
        print(f"Step {step:6d} | {action:6s} | ({x},{y}) | {thought[:60]}")

3.4 截图预处理

Game Boy 原始分辨率只有 160x144,直接发给 LLM 效果不好。我们做了两步预处理:

def preprocess(self, img):
    # 1. 2 倍放大(最近邻插值,保持像素风格)
    if SCREENSHOT_SCALE > 1:
        w, h = img.size
        img = img.resize((w * 2, h * 2), Image.NEAREST)
    # 2. 1.5 倍对比度增强
    return ImageEnhance.Contrast(img).enhance(1.5)

使用 最近邻插值Image.NEAREST)而不是双线性插值,是为了保持 Game Boy 特有的像素风格,同时让 LLM 更容易识别游戏中的文字和角色。

cleared_script.png

4. 踩坑实录:从零到可用的调试之旅

这可能是整个项目最精彩的部分。我们遇到了一系列意想不到的问题,每一个都差点让我们放弃。

4.1 PyBoy v2 API 大改版

项目最初使用 PyBoy v1 的 API,但 PyBoy v2 进行了破坏性改动:

功能v1 APIv2 API
按键button_press("a")button("a", hold_frames)
速度set_speed(0)set_emulation_speed(0)
截图screen_image()screen.image
内存get_memory_value(addr)memory[addr]

我们一开始用 v1 的 API 写代码,运行时直接报错。不得不翻阅 PyBoy 源码,逐个修复。

4.2 pokegym 库不兼容

我们尝试使用 pokegym 库来读取 Pokemon Red 的游戏状态(队伍、血量、地图信息等),但发现 pokegym 0.2.0 版本依赖 PyBoy v1,而我们用的是 v2。两个库的版本要求完全冲突:

pokegym 0.2.0requires pyboy<2.0
我们的项目      → requires pyboy>=2.0

最终放弃 pokegym,改用直接读取 Game Boy 内存地址的方式获取游戏状态。

4.3 mGBA 没有 Python API

我们还尝试了 mGBA 模拟器——它是 Game Boy Advance 的权威模拟器,精度比 PyBoy 高很多。安装后发现:

  • mGBA 支持 Lua 脚本,但不提供 Python 绑定
  • 通过子进程和管道与 mGBA 通信极其不稳定
  • 最终放弃,继续用 PyBoy

4.4 碰撞检测:一个未解之谜

这是整个项目中最大的技术挑战。Pokemon Red 的碰撞检测系统极其复杂:

ROM 数据结构(地址 0x01749):
┌─────────────────┐
│  44 个碰撞块     │  每个块 16x16 像素
│  (0 = 可通行     │
│   1 = 不可通行)  │
└────────┬────────┘
         │ 指向当前地图的块
         ▼
WRAM 指针 (0xD530-0xD531)
         │
         ▼
┌─────────────────┐
│ 地图碰撞数据      │  按行列排列
│ 0=走  1=墙      │
└─────────────────┘

我们花了很多时间尝试在 PyBoy 中修复碰撞检测:

  1. ROM patching:从 pret/pokered 源码编译 ROM,尝试去掉碰撞数据中的墙壁标记
  2. WRAM 写入:直接修改 Game Boy 内存中的碰撞数据
  3. VRAM 读取:尝试从 PyBoy 的 tilemap 中反推碰撞信息

最终发现 PyBoy 的 tilemap 值(128-383)与 ROM 碰撞数据(0-44)完全不对应。这是一个底层的模拟器兼容性问题,在 PyBoy 的 issue tracker 上也没有找到解决方案。

最终解决方案:使用 PokemonRedExperiments 项目提供的预制作存档(save state),这些存档已经处于安全的可导航位置(如真新镇户外),避免了室内地图的碰撞问题。

4.5 开场动画:跳过 Professor Oak 的 10 分钟演讲

宝可梦 Red 的开场动画极其冗长——Professor Oak 会讲一大堆话,介绍宝可梦世界。对于 AI Agent 来说,这段纯脚本动画会浪费大量时间和 API 调用。

我们尝试了多种方案:

方案 1:Debug Mode Flag

Pokemon Red 的 ROM 中有一个调试标志位 0xD732,设为 0x02 可以跳过部分对话。我们直接写入内存:

self.pyboy.memory[0xD732] = 0x02

方案 2:预制作存档

最终采用 PokemonRedExperiments 项目提供的 save state 文件(.state),直接加载一个已经过了开场动画、拿到宝可梦图鉴的游戏存档。

# has_pokedex.state — 在大木博士实验室,已获图鉴
# outdoor_fresh.state — 在真新镇户外
with open("has_pokedex.state", "rb") as f:
    self.pyboy.load_state(BytesIO(f.read()))

4.6 PyBoy v2 的 Interaction 模块

我们深入研究了 PyBoy 的按键输入处理源码,发现了一个关键细节。PyBoy 的 Interaction 类用 位掩码 来模拟 Game Boy 的硬件寄存器:

class Interaction:
    def __init__(self):
        self.directional = 0xF  # 4 位方向键,初始全为 1(未按下)
        self.standard = 0xF     # 4 位功能键,初始全为 1(未按下)

    def key_event(self, key):
        # 按下 = 清除对应位(0 = 按下)
        if key == WindowEvent.PRESS_ARROW_RIGHT:
            self.directional = reset_bit(self.directional, P10)
        # ...

    def pull(self, joystickbyte):
        # 根据游戏读取的端口返回对应按键状态
        P14 = (joystickbyte >> 4) & 1
        P15 = (joystickbyte >> 5) & 1
        if not P14:
            joystickByte &= self.directional
        elif not P15:
            joystickByte &= self.standard
        return joystickByte

这段代码解释了为什么 PyBoy 的按键输入有时候不稳定——它完全模拟了 Game Boy 的硬件轮询机制,按键状态是 瞬态 的,按下后必须在正确的帧内被读取才能生效。


5. Agent 的大脑:提示词工程

提示词的设计是整个项目最核心的部分。一个好的提示词需要:

  1. 告诉 AI 它是谁、能做什么
  2. 提供游戏知识(属性克制、道馆信息等)
  3. 定义严格的输出格式
  4. 给出战斗和探索策略

完整的系统提示词:

你是一个正在玩宝可梦火红的 AI 玩家。

## 你的能力
- 你能看到游戏的屏幕截图
- 你可以决定按哪个按钮
- 你有一个游戏笔记文件,记录你的进度和发现

## 输出格式(必须严格遵守)
THOUGHT: (简短分析当前画面,1-2 句话)
ACTION: (一个按钮:up/down/left/right/a/b/start/select)
NOTE: (可选,如果发现重要信息需要记录)

## 战斗策略
1. 查看对手宝可梦的类型
2. 选择克制对手属性的技能
3. HP 绿色 > 50% 可以继续;黄色 20-50% 考虑用药;红色 < 20% 立即治疗
4. 等级差距超过 5 级考虑逃跑

为什么用中文提示词?

因为 LLM 对中文游戏术语的理解非常准确("小刚"、"岩石系"、"真新镇"),而用英文提示词时 AI 有时会混淆中英文游戏内容。

输出格式的严格性

我们要求 AI 严格按照 THOUGHT / ACTION / NOTE 三段式输出。这不是随意的设计:

  • THOUGHT:强制 AI 先分析画面,减少随机操作
  • ACTION:明确的操作指令,便于程序解析
  • NOTE:可选的记忆信息,用于更新外部笔记本

解析代码也很简单:

def parse_action(self, response):
    for line in response.split("\n"):
        if "ACTION:" in line.upper():
            action = line.split(":", 1)[1].strip().lower()
            action = action.split()[0]
            if action in ("up", "down", "left", "right", "a", "b", "start", "select"):
                return action
    return "a"  # 默认按 A

6. 防循环:让 AI 不再原地打转

AI Agent 最常见的问题之一就是 循环行为——反复按同一个方向键,或者在两个方向之间来回移动。我们设计了三重防循环策略:

def detect_loop(self):
    if len(self.recent_actions) < 15:
        return None

    window = self.recent_actions[-15:]

    # 策略 1:连续相同动作检测
    # 如果最近 10 步全是同一个动作(如连续按 up),触发警告
    last = window[-1]
    if all(a == last for a in window[-10:]):
        return "换一个完全不同的方向"

    # 策略 2:来回移动检测
    # 如果只在两个相反方向间移动(如 up-down-up-down),触发警告
    direction_counts = Counter(a for a in window if a in DIRECTION_BUTTONS)
    if len(direction_counts) == 2:
        dirs = list(direction_counts.keys())
        if OPPOSITE.get(dirs[0]) == dirs[1]:
            if direction_counts[dirs[0]] + direction_counts[dirs[1]] >= 8:
                return "你在来回移动!尝试按 a 交互或按 start 打开菜单"

    return None

警告如何传给 LLM?

检测到循环后,警告信息会被注入到发给 LLM 的 user message 中:

⚠️ 警告:你在来回移动!尝试按 a 交互或按 start 打开菜单
你的最近动作序列:up,down,up,down,up,down,up,down,up

LLM 看到警告后会主动调整策略,比如按 A 与 NPC 对话,或按 Start 打开菜单查看状态。

step_005381.png

step_005346.png

7. 外挂记忆:AI 的笔记本

LLM 的上下文窗口是有限的。每一步只看到当前截图,不知道之前做了什么。为了解决这个问题,我们设计了一个 外部记忆系统——一个 markdown 文件,AI 可以随时查看和更新。

# 宝可梦火红 — AI 游戏笔记

## 当前进度
- 在真新镇大木博士实验室

## 当前队伍
- 杰尼龟 (AAAAAA) Lv.6 HP:22/22 水系

## 目标
- 前往常青市挑战第一个道馆

## 重要发现
[09:31] 大木博士实验室有四个精灵球,分别装着三种初始宝可梦
[09:45] 选了杰尼龟,水系克制第一个道馆的岩石系
class StateManager:
    def __init__(self, memory_file="memory/journal.txt"):
        self.memory_file = Path(memory_file)

    def get_memory(self):
        return self.memory_file.read_text(encoding="utf-8")

    def append_memory(self, note):
        ts = datetime.now().strftime("%H:%M")
        with open(self.memory_file, "a", encoding="utf-8") as f:
            f.write(f"\n[{ts}] {note}")

每一步,Agent 会:

  1. 读取笔记本内容
  2. 将其作为上下文传给 LLM
  3. LLM 输出的 NOTE 字段会被追加到笔记本

这样 AI 就有了"长期记忆"——它知道自己之前选了什么宝可梦、打了哪些道馆、当前目标是什么。


8. 从截图到视频:完整录屏

Agent 运行时每一步都会保存截图(screenshots/step_000001.png...),最终我们用 ffmpeg 将 5000+ 张截图合成为视频:

def make_video(fps=6):
    screenshots = sorted(Path("screenshots").glob("step_*.png"))

    # 生成 ffmpeg concat 文件列表
    list_file = Path("screenshots/filelist.txt")
    with open(list_file, "w") as f:
        for s in screenshots:
            f.write(f"file '{s.absolute()}'\n")
            f.write(f"duration {1/fps}\n")
        f.write(f"file '{screenshots[-1].absolute()}'\n")

    # 合成视频
    os.system(
        f"ffmpeg -y -f concat -safe 0 -i screenshots/filelist.txt "
        f"-vf 'scale=480:432:flags=neighbor' "
        f"-c:v libx264 -pix_fmt yuv420p -crf 20 "
        f"pokemon_full_playthrough.mp4"
    )

关键参数解释:

  • fps=6:每秒 6 帧(每步约 0.3-0.8 秒的游戏时间)
  • scale=480:432:flags=neighbor:最近邻插值放大,保持像素风
  • crf=20:视频质量参数(越小质量越高)

9. 8-bit 音乐生成:用代码演奏宝可梦主题曲

视频没有声音怎么行?我们从 pret/pokered 开源项目的 titlescreen.asm 中提取了宝可梦 Red 的主题曲音符数据,用 Python 生成了 8-bit chiptune 风格的 WAV 文件:

# 从 pokered 源码提取的音符数据
# channels[0] = 第一声道(主旋律)
# channels[1] = 第二声道(和声)
# channels[2] = 第三声道(低音)

def generate_square_wave(freq, duration, duty=0.5):
    """生成方波(Game Boy 音色的核心)"""
    t = np.linspace(0, duration, int(SAMPLE_RATE * duration), False)
    wave = np.where(np.sin(2 * np.pi * freq * t) > 0, 0.5, -0.5)
    return wave * 0.3  # 降低音量

def note_to_freq(note_name):
    """将音符名转换为频率"""
    notes = {'C4': 261.63, 'D4': 293.66, 'E4': 329.63,
             'F4': 349.23, 'G4': 392.00, 'A4': 440.00, 'B4': 493.88}
    return notes.get(note_name, 0)

生成过程:

  1. 解析 titlescreen.asm 中的音符宏(m_notem_rest 等)
  2. 将音符名转换为频率
  3. 用 numpy 生成方波(Game Boy 的经典音色)
  4. 混合三个声道
  5. 导出为 WAV 文件

最终生成了 pokemon_red_theme.wav(约 8MB),时长约 30 秒的完整主题曲。

视频+音乐合成:

ffmpeg -i pokemon_full_playthrough.mp4 \
       -i music/pokemon_red_theme.wav \
       -c:v copy -c:a aac -shortest \
       pokemon_with_music.mp4

最终产出 pokemon_with_music.mp4(2.1MB,约 94 秒),画面是 AI 玩宝可梦的过程,背景音乐是经典的宝可梦主题曲。


10. 最终成果与数据

运行数据

指标数值
总运行步数5,000+ 步
截图数量5,696 张
视频时长~94 秒
视频大小2.1 MB
使用模型Claude Haiku 4.5
API 调用次数~5,000 次
模拟器速度1x 实时
预处理2x 放大 + 1.5x 对比度

游戏进度

AI 成功完成了以下游戏流程:

  1. 跳过开场 → 使用 PokemonRedExperiments 存档直接跳过
  2. 进入大木博士实验室 → 在真新镇户外走到实验室门口,按 A 进入
  3. 与大木博士对话 → 理解对话框中的文本,持续按 A 推进
  4. 选择初始宝可梦 → 选择了杰尼龟(水系),克制第一个道馆
  5. 探索真新镇 → 在户外区域自由移动
  6. 挑战道馆 → 进入常青市道馆,与训练家战斗

LLM 决策示例

从实际运行日志中截取的 AI 决策过程:

Step  0 | up    | (11,12) map=40 | 我刚从房子出来,现在站在真新镇...
Step  2 | a     | (11, 5) map=40 | 已经站在门口了,按 a 进入实验室...
Step  6 | a     | (11, 3) map=40 | 进入实验室,需要和大木博士对话...
Step  10| a     | ( 8, 4) map=37 | 大木博士在说话,按 a 继续...
Step  20| a     | ( 5, 6) map=37 | 看到书架,"Crammed full of POKéMON books"...
Step  50| a     | ( 6, 8) map=37 | 精灵球在桌子上,按 a 选择初始宝可梦...
Step  100| left | ( 6, 7) map=37 | 选完宝可梦,去找大木博士...

11. 经验总结与未来展望

关键经验

1. 模拟器选择至关重要

  • PyBoy 是目前最好的 Python Game Boy 模拟器,但 v2 的 API 改动很大
  • 无限速度看似高效,实则破坏游戏逻辑
  • 预制作存档比自动跳过开场更可靠

2. 截图质量直接影响 LLM 决策

  • 160x144 分辨率太小,必须放大
  • 对比度增强有助于 LLM 识别 UI 元素
  • 最近邻插值保持像素风格,比双线性插值效果好

3. 外部记忆是 Agent 的生命线

  • LLM 上下文窗口有限,无法记住所有历史
  • Markdown 笔记本简单但有效
  • 让 AI 自己决定记什么比预定义模板更灵活

4. 防循环是必须的

  • AI 最容易陷入的行为就是反复按同一个键
  • 三重检测策略(连续相同、来回移动、注入警告)效果显著
  • 警告信息直接注入 LLM 上下文比硬编码规则更智能

5. 多 Provider 支持的价值

  • Claude Haiku 响应快、成本低,适合大量探索
  • GPT-4o 在复杂场景理解上更强
  • 不同模型在不同任务上各有优势

未来改进方向

  1. 碰撞检测修复:深入 PyBoy 源码,尝试修复 Pokemon Red 的碰撞问题
  2. 记忆系统升级:用向量数据库替代 markdown 文件,支持语义搜索
  3. 多模型协作:用 Haiku 做快速探索,遇到复杂决策时切换到 Claude Opus
  4. 自动存档管理:在关键节点自动保存游戏状态,支持失败回滚
  5. 可视化 Dashboard:实时显示 Agent 的决策过程和游戏状态
  6. 屏幕 OCR:增加专门的 OCR 模块,准确读取 HP、等级、技能等数值

写在最后

这个项目让我们深刻体会到:让 AI 玩游戏,最难的不是模型能力,而是工程细节。PyBoy 的碰撞检测、按键时序、存档格式……这些"不性感"的工程问题,往往决定了项目的成败。

但这也正是 AI Agent 的魅力所在——它需要跨越模型推理、模拟器控制、状态管理、错误处理等多个领域的知识边界,才能真正"玩"一个游戏。

最终,当我们在屏幕上看到 AI 控制的小角色走出真新镇、进入道馆、开始战斗时,那种成就感是无与伦比的。

AI 不仅能玩游戏——它能像人一样,看屏幕、做决策、犯错误、然后自己修正。