news 2026/9/8 13:22:10

Game Boy自制游戏开发实战:GBDK工具链与ROM构建指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Game Boy自制游戏开发实战:GBDK工具链与ROM构建指南

《黑城堡 2》是一款完全在 Game Boy 平台上运行的自制游戏。如果你对“如何在只有 8 位 CPU、8KB 工作 RAM、160×144 像素分辨率的古董掌机上做出一款能玩的动作游戏”这件事感兴趣,这篇文章正好适合你。

这次我们不聊模拟器上的 ROM 修改,而是从自制游戏开发的角度拆解:一个 Game Boy 平台的《黑城堡 2》项目,通常包含哪些技术模块、用什么工具链构建、在模拟器/真机上怎么验证、常见问题怎么排查。就算你手里没有 Game Boy 实体卡带烧录器,用模拟器一样能完整体验和调试整个项目。

先看这个类型项目最值得关注的信息:

  • 开发语言:C 语言,配合 GBDK(Game Boy Development Kit)工具链,也可以用 ZGB 引擎。
  • 硬件门槛:不需要高性能 PC,普通笔记本即可编译 ROM;运行游戏用模拟器或烧录到实体卡带。
  • 画面规格:Game Boy 原生分辨率 160×144,4 级灰阶,8×8 tile 背景,8×8/8×16 sprite。
  • 输出产物:一个.gb格式的 ROM 文件,体积通常在 32KB 到 512KB 之间。
  • 运行方式:模拟器直接加载 ROM,实体机需要烧录器 + 对应卡带。
  • 测试方式:模拟器调试 + 真机运行验证,重点观察帧率、内存占用、碰撞判定和音频效果。

1. 核心能力速览

能力项说明
项目类型Game Boy 自制游戏
开发语言C(GBDK 工具链)
可选引擎ZGB 引擎、GBDK 原生开发
目标平台Game Boy / Game Boy Color / 模拟器
画面规格160×144 分辨率,4 级灰阶
音频规格4 通道 MOS 式声音(矩形波、自定义波形、噪声)
卡带规格通常 MBC1/MBC3,容量 32KB 起
输出格式.gb ROM 文件
运行方式模拟器加载或烧录至实体卡带
调试方式模拟器断点 + 内存查看 + 日志输出
适合人群复古游戏爱好者、游戏开发入门者、嵌入式开发兴趣者

需要说明的是,不同项目的具体 ROM 容量、镜像头配置、内存控制方式会有差异。下面以 GBDK 路线为主展开,这是目前 Game Boy 自制游戏最通用的开发方案。


2. 适用场景与使用边界

2.1 适用场景

Game Boy 自制游戏适合这几类人:

  • 怀旧游戏玩家:想玩到“现代人做的复古游戏”,体验 8 位机的限制性设计。
  • 独立游戏开发者:想从零理解早期掌机的 Tile、Sprite、滚动背景、内存分页机制,这些概念在 FC、GBA、甚至部分现代引擎中仍然有用。
  • C 语言学习者:GBDK 让你用 C 直接驱动一小块硬件,比在 PC 上做题更有掌控感。
  • 嵌入式爱好者:Game Boy 本质上是一个受限的嵌入式平台,IO 端口、中断、显存管理都很直观。

2.2 不适合什么场景

  • 想快速做出复杂剧情、大世界、超长流程的 RPG,不建议选 Game Boy 平台。8KB 工作 RAM、160×144 分辨率会严重影响设计。
  • 不懂 C 语言、不熟悉命令行、不想碰模拟器调试工具链的话,玩起来会困难。
  • 如果目标是商业发行到 Steam 或移动端,Game Boy 自制游戏更适合作为偏执风格项目,而不是主力产品。

2.3 使用边界与合规提醒

自制游戏项目有一个容易忽略的问题:版权边界。

  • 如果你用了现成的 ROM 资源、背景 BGM 素材、精灵图集,必须先确认授权状态。
  • 如果游戏里出现了致敬《黑城堡》系列的名称、角色、美术风格,非商业同人作品相对安全,但一旦涉及商业售卖、众筹、收费分发,必须取得版权方许可。
  • 开发过程中不要直接复制其他商业 ROM 的代码和素材。
  • 发布 ROM 时建议附带完整说明,标明使用的引擎、工具链、素材授权方式,避免后续纠纷。

整体原则:技术可以随便学,素材和版权必须谨慎。


3. 开发环境准备

开发 Game Boy 自制游戏不需要昂贵设备。最简配置如下。

3.1 硬件需求

设备最低要求说明
电脑任意能运行 Windows / macOS / Linux 的机器编译 ROM 对性能要求极低
模拟器VisualBoyAdvance-M、BGB、mGBA 任选用于日常调试
实体机(可选)Game Boy / Game Boy Color / 兼容掌机验证真实硬件运行
烧录器(可选)GBxCart RW 或同类设备把 .gb 写入实体卡带

没有烧录器的前提下,模拟器已经可以完成绝大部分开发和测试工作。

3.2 软件工具链

软件用途
GBDK-2020C 编译器 + 汇编器 + 链接器整套工具链
代码编辑器VS Code、Vim、Emacs 都行
模拟器mGBA 或 BGB 负责调试
素材工具Aseprite、libGDX 纹理打包器等做 tile 和 sprite
音频工具hUGEDriver、GBT Player 或 Mod2GBT 做音乐素材

GBDK-2020 是目前维护最活跃的 GBDK 发行版,替代了老的 GBDK 2.96。使用老版本会遇到编译器兼容性和 C 标准支持不足的问题,直接上 GBDK-2020 更省事。

3.3 GBDK 安装步骤

Windows 下直接下载 GBDK-2020 的 Windows 压缩包,解压后把bin目录加入系统 PATH。

Linux / macOS 建议直接从源码编译,或使用包管理器安装。具体命令以项目 README 为准,这里给一个通用思路:

# 下载 GBDK-2020 源码 git clone https://github.com/gbdk-2020/gbdk-2020.git cd gbdk-2020 # 编译安装 make

编译完成后,bin目录下会有lcc编译器驱动,它是整个构建过程的核心工具。


4. 项目构建与 ROM 输出

4.1 一个最小的 GBDK 项目结构

自制 Game Boy 游戏的 C 项目通常长这样:

black_castle_2/ src/ main.c player.c enemies.c map.c audio.c res/ tiles.png sprites.png map.bin music.mod Makefile build/

res目录存放图片、地图、音乐等二进制资源,src目录存放 C 源码,build目录存放编译中间产物和最终 ROM。

4.2 Makefile 核心逻辑

使用 GBDK 编译项目,实际上是通过lcc完成编译和链接。一个最小化构建流程如下:

# 替换成你本机的 GBDK 安装路径 GBDK = /path/to/gbdk-2020 CC = $(GBDK)/bin/lcc ROM = black_castle_2.gb SRCS = $(wildcard src/*.c) RES = res/tiles.s res/sprites.s res/map.s .c.s: $(CC) -c -o $@ $< all: $(CC) -o $(ROM) $(SRCS) $(RES) clean: rm -f $(ROM) build/*.o build/*.s

更工程化的写法会用 GBDK 自带的png2asset工具把 PNG 转成 C 数组或汇编数据,这样就不需要res/*.s提前生成。

4.3 从源码到 ROM 的完整流程

按顺序执行:

# 1. 用 png2asset 生成 tile 数据 /path/to/gbdk-2020/bin/png2asset res/tiles.png -s 8 8 -out res/tiles.c # 2. 用 png2asset 生成 sprite 数据 /path/to/gbdk-2020/bin/png2asset res/sprites.png -s 8 8 -out res/sprites.c # 3. 编译主程序与资源 make clean && make # 4. 验证 ROM 是否输出 ls -lh build/black_castle_2.gb

如果一切正常,会得到一个几十到几百 KB 的.gb文件。这个文件就是最终可以在模拟器或实体机上运行的 ROM。

4.4 在模拟器中启动

用 mGBA 直接加载 ROM:

mgba build/black_castle_2.gb

或者双击 ROM 文件,在打开的模拟器中运行。启动后如果有黑屏、花屏、崩溃,八成是资源转换格式不对,或者 ROM Header 的 Checksum 校验失败,需要在编译阶段排查。


5. 技术要点拆解:Game Boy 自制游戏到底在做什么

5.1 Tile、Sprite 与 160×144 画面限制

Game Boy 的屏幕是 160×144 像素,刷新时只能显示 256 个 8×8 Tile。背景、窗口共用 Tile 地址表,角色精灵也是由 8×8 或 8×16 Tile 拼出来的。

所以,你的美术资源在进工程前必须做一件事:把原画切成 8×8 的格子。Aseprite 里有专门针对像素游戏的网格切分功能,导出 PNG 时确保每个 Tile 严格对齐 8 像素。

_黑城堡 2如果采用类银河恶魔城或俯视角动作玩法,地图表现上要控制 Tile 种类数量。一张大地图如果不同区域用了过多不同 Tile,VRAM 装不下,会出现刷屏闪烁或背景错乱。

C 层加载地图的典型代码:

#include <gb/gb.h> #include "map.h" void load_map(uint8_t map_index) { set_bkg_data(0, MAP_TILE_COUNT, map_tiles); set_bkg_tiles(0, 0, MAP_W, MAP_H, map_maps[map_index]); SHOW_BKG; }

set_bkg_data把 Tile 数据上传到 VRAM,set_bkg_tiles把地图索引写入背景显存。这两步是 Game Boy 显示流程的核心。

5.2 碰撞检测与 8 位 CPU 的性能边界

Game Boy 的 CPU 是定制版 Z80,主频约 4.19MHz。不要指望它有 Cortex-M 级别的性能,更不要碰浮点运算。

碰撞检测推荐使用 AABB(轴对齐包围盒),玩家和敌人各用一个矩形区域,每帧检测相交。切忌在 Game Boy 上做像素级逐位碰撞检测,CPU 会直接爆掉。

一个极简 AABB 判断:

#include <gb/gb.h> typedef struct { int16_t x, y, w, h; } AABB; uint8_t check_collision(AABB *a, AABB *b) { if (a->x + a->w <= b->x) return 0; if (a->x >= b->x + b->w) return 0; if (a->y + a->h <= b->y) return 0; if (a->y >= b->y + b->h) return 0; return 1; }

注意这里使用了int16_t。Game Boy 的内存和性能都有上限,能不用int32_t就不用,避免编译产物过大。

游戏的主循环通常靠wait_vbl_done()来同步帧率:

void game_loop(void) { while (1) { process_input(); update_player(); update_enemies(); update_camera(); wait_vbl_done(); } }

wait_vbl_done()会等待垂直同步信号,防止撕裂,并把帧率稳定在约 59.7 FPS。

5.3 分页机制:让游戏“变大”

Game Boy 有地址空间上限:16MB ROM 空间、8KB 工作 RAM、8KB VRAM。要跑超过 32KB 的 ROM,必须用 MBC 芯片做分页切换。

MBC1 芯片是最常见的,支持最大 2MB ROM。MBC3 则额外支持时钟和更大 RAM。开发《黑城堡 2》这种流程稍长的游戏,建议直接用 MBC5 或 MBC3 卡带配置,方便后续扩容。

在 GBDK 中,分页切换可以通过库函数或在源码中写入特定寄存器完成。普通教程项目往往不直接操作分页,但如果你想做超大地图、多关卡,必须理解这个机制:

#include <gb/gb.h> #include <gb/bgb_emu.h> void switch_bank(uint8_t bank) { SWITCH_ROM_MBC1(bank); }

每隔一定关卡切换 Bank,把不同的地图、敌人逻辑、音频数据载入到可用内存。这是 Game Boy 游戏工程化的关键一步。

5.4 音频与音乐

Game Boy 自带声音芯片有 4 个通道:

通道类型常见用途
CH1矩形波 + 扫频主旋律
CH2矩形波和声、副旋律
CH3自定义波形打击乐、旋律变奏
CH4噪声鼓、打击效果

自编音频驱动非常费时间。新手推荐用GBT PlayerhUGEDriver

GBT Player 需要先把 MOD 音乐通过mod2gbt工具转换,再在 C 端播放:

#include <gb/gb.h> #include "music.h" void play_music(uint8_t song_index) { gbt_play(music, song_index); gbt_enable_channels(GBT_CHANNEL_1 | GBT_CHANNEL_2 | GBT_CHANNEL_3 | GBT_CHANNEL_4); }

在 Game Boy 上做音乐,重点不是“编曲有多复杂”,而是“素材转换后能不能在 4 通道内播放”。复杂混音在 8 位声卡上只会变成一团噪声。

5.5 输入处理与按键状态

Game Boy 只有 D-Pad 和 A / B / Start / Select 六个输入。按键状态要自己维护,判断按下/按住/松开。

一个常见的输入模块:

#include <gb/gb.h> uint8_t keys_current; uint8_t keys_pressed; void read_input(void) { keys_current = joypad(); keys_pressed = keys_current & ~keys_previous; keys_previous = keys_current; } uint8_t key_just_pressed(uint8_t key) { return keys_pressed & key; } uint8_t key_held(uint8_t key) { return keys_current & key; }

核心逻辑是:keys_pressed保存“上一次没有被按下、这一次被按下”的按键,避免按住不放时每帧都触发跳跃或攻击。


6. 功能测试与效果验证

模拟器运行的 ROM 并不等同于真实硬件一定正常。以下是一套通用验证流程,专门针对 Game Boy 自制游戏。

6.1 基础启动测试

测试项预期结果判断标准
ROM 启动画面正常显示 logo 或标题没有花屏、无信号、不崩溃
背景显示场景地图完整显示Tile 不错位,无重复背景块浪费
Sprite 显示玩家角色出现在正确坐标无幽灵残留、无闪烁
按键响应移动、跳跃、攻击正常输入延迟不明显,无粘连
音乐播放第一首 BGM 播放4 通道无爆音,节奏稳定

6.2 地图切换与碰撞测试

地图切换时会重新加载大量 Tile 数据,最容易出现内存覆盖问题。测试步骤:

  1. 从第 1 关进入第 2 关。
  2. 观察切换瞬间是否有半屏花屏。
  3. 检查敌人是否重新生成,玩家是否被错误地卡进墙里。
  4. 连续切换 20 次以上,确认没有死机。

6.3 长时间运行测试

在模拟器中挂机或自动游玩 30 分钟,重点观察:

  • 是否出现内存泄漏导致的 Tile 错乱。
  • 音乐是否出现卡顿。
  • 精灵表是否被覆盖,出现“角色变成敌人”的情况。
  • RAM 值是否不断增长。

6.4 真机烧录测试(可选)

如果没有烧录器,可以跳过这一步。有实体设备的话,先用低容量配置跑一次最小 ROM,确认烧录链路正常,再烧完整游戏。真机上最明显的差异是音频和按键延迟,模拟器的结果只能作参考。


7. 接口与批量任务说明

Game Boy 自制游戏的产物是 ROM 文件,本身没有 HTTP API 或批量任务的概念。但如果你在批量做多关卡、多版本测试,可以围绕构建流程做自动化:

  • 用脚本批量调用png2asset转换所有素材。
  • make批量生成不同版本 ROM(例如:试玩版、完整版、无音乐版)。
  • 用模拟器命令行参数批量运行测试脚本,检查崩溃日志。

示例脚本(Linux / macOS):

for entry in res/maps/*.png; do name=$(basename "$entry" .png) /path/to/gbdk-2020/bin/png2asset "$entry" -s 8 8 -out "build/$name.c" done make clean && make echo "ROM build complete"

这个脚本的价值在于:当你新增 50 个地图时,不需要手动逐个转素材,一次命令全部完成。Game Boy 自制游戏项目里,“批量任务”更多体现在素材加工和版本构建,而不是运行时服务。


8. 资源占用与性能观察

Game Boy 没有 Windows 任务管理器可以看 CPU 占用。观测方式主要依赖模拟器调试器和内存查看器。

8.1 显存 / VRAM 占用

打开 mGBA 的调试菜单,找到VRAM Viewer,可以看到当前加载的 Tile 集合。如果背景或精灵使用的 Tile 数量超过 256 个,就会出现“该显示的 Tile 显示不出来”或“花屏”。

8.2 ROM 容量与 RAM 占用

编译出的.gb文件大小直接可见。在构建时,GBDK 会在链接结束后打印内存占用汇总:

Cartridge type: MBC5 ROM: 128KB RAM: 8KB

如果 ROM 超出目标容量,需要:

  • 优化 Tile 图集,去除重复 Tile。
  • 压缩地图数据,使用 RLE 压缩。
  • 切分关卡到不同 Bank。

8.3 帧率观察

Game Boy 的 CPU 性能不足时会出现掉帧。在模拟器中开启帧率显示,测试以下场景:

  • 单角色移动时帧率是否稳定。
  • 8 个敌人同时在场时帧率是否下降。
  • 地图滚动到复杂区域时,背景同步是否正常。
  • 大量精灵闪烁时是否因为超过了每行 10 个 Sprite 的硬件限制。

Game Boy 每行最多显示 10 个 Sprite,如果设计上是“敌人 + 玩家 + 道具”同时超过 10 个,就会看到精灵消失或闪烁,必须做优先级控制或精灵合并。


9. 常见问题与排查方法

问题现象可能原因排查方式解决方案
ROM 在模拟器中显示黑屏ROM Header 校验失败或入口代码被截断检查编译日志,确认是否输出了.gb文件及大小重新编译,确认链接器没有报错
背景显示为乱码地图数据与 Tile 数据不对应在 VRAM Viewer 查看实际 Tile 索引用 png2asset 重新生成地图,确认 Map 尺寸一致
Sprite 闪烁单行 10 个 Sprite 上限被突破暂停游戏,查看 OBJ 扫描线状态降低同屏精灵数量,或使用 8×16 Sprite
按键无响应输入状态未初始化检查joypad()返回值确认调用了SHOW_BKG和系统中断初始化
音乐播放异常MOD 转换后音频数据超限检查 gbt_play 的 bank 参数把音乐数据放到指定 Bank,或减少音轨数
地图切卡花屏Bank 切换时数据加载未完成切换处加断点,观察 VRAM 地址VBK_REG锁存或加延迟等待
模拟器正常但真机黑屏卡带 bank 寄存器兼容性差查看卡带类型是否被模拟器放宽改用 MBC5,避免 MBC1 的地址复用问题
编译报错找不到gb.hGBDK 环境变量未设置检查编译器路径把 GBDK 的include目录加入头文件搜索路径
编译产物超大未使用优化或素材未压缩查看 ROM 分段大小开启 lcc 的-Wf-bo优化选项,压缩 Tile 与地图
程序跑飞或死循环未处理玩家死亡后的函数返回在产生问题前加日志输出用模拟器的调试器查看 PC 和 SP 寄存器

10. 最佳实践与使用建议

10.1 先做最小可运行版本

第一次做 Game Boy 项目,目标不是做完整《黑城堡 2》,而是一屏地图 + 一个可移动角色 + 一个敌人。先把 ROM 构建链路跑通,再逐步加功能。

最小版本跑通后,再考虑地图滚动、敌人 AI、多关卡、音乐播放。每加一个新模块,都要立即在模拟器里验证,不要攒到最后一个大版本再测。

10.2 素材管线区分目录

资源和代码严格分开:

assets/ tiles/ sprites/ maps/ music/ src/ game/ audio/ ui/ build/ docs/

批量转换脚本只处理assets/目录,避免误打包非游戏文件。

10.3 版本管理

用 Git 管理源码和素材。.gb编译产物不要入库,因为它不便于 diff。建议在.gitignore中加入:

build/ *.exe *.gb *.s *.o

10.4 备份 ROM 版本

每次完成一个可玩里程碑,保存一份带版本号的 ROM 文件,例如:

black_castle_2_demo_20250218.gb black_castle_2_alpha_20250301.gb

方便回溯哪个版本引入了特定 Bug。

10.5 避免侵犯版权的注意事项

开发《黑城堡 2》这种主题时,尤其要考虑“致敬”和“抄袭”的分界:

  • 如果标题和游戏名与原作一致,建议在 README 中注明属于爱好者同人项目。
  • 美术素材不要直接提取商业 ROM 里的精灵图。
  • 音乐如果使用 MOD 素材,必须确认文件授权协议。
  • 发布 ROM 不要附带收费,不要放在众筹平台。

一句话:技术部分可以大胆写,素材和标题需要谨慎使用。


11. 总结与下一步

《黑城堡 2》作为 Game Boy 自制游戏项目,最值得尝试的点是“在极端硬件限制下完成一整个游戏循环”:地图加载、角色控制、碰撞检测、敌人 AI、音效播放、卡带分页。完成一个可玩的.gbROM,你对游戏引擎底层渲染方式的理解会比做十个网页小游戏都深。

拿到项目后,建议先验证这三件事:

  1. 能否用 GBDK 编译出可运行 ROM。
  2. 能否在模拟器中正常显示场景并移动角色。
  3. 能否在切换地图时不花屏、不崩溃。

最容易踩的坑有两个:一个是素材没有严格切成 8×8 Tile,导致显示混乱;另一个是忽略了单行 10 个 Sprite 的硬件限制,让同屏精灵直接消失。

后续可以继续扩展的方向很多:加入密码存档系统、用 GBT Player 做完整的 8-bit BGM、尝试 MBC5 扩容做多章节流程、甚至自己设计一块实体卡带 PCB 配合烧录器体验“从代码到卡带”的全链路。

如果你手里有按键手感不错的 Game Boy 兼容掌机,推荐把最终 ROM 烧录进去玩一遍,那感觉和模拟器完全不是一个级别。

建议收藏备用。等你的第一个.gb文件跑起来之后,再回来对照这篇的测试清单和排查表,大概率能直接定位问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 13:21:35

FPGA 100G UDP协议栈移植实战:从开源方案到线速收包

先把结论放在前面&#xff1a;这个项目本身不复杂&#xff0c;但真正的复杂度全藏在“移植”两个字里。我从拿到一块带 QSFP28 光口的 UltraScale 板卡&#xff0c;到把开源 100G UDP 协议栈跑起来、双侧验证线速收包&#xff0c;前后折腾了大概两个礼拜。期间踩过了光模块兼容…

作者头像 李华
网站建设 2026/9/8 13:21:18

PixVerse深度图控制:AI图像生成空间布局精准实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 13:20:25

从央媒到自媒体,2026优质GEO发稿平台推荐,适配各行企业

一、核心概念界定(一)GEO生成式引擎优化与传统软文发稿的本质分野本文聚焦GEO生成式引擎优化(GenerativeEngineOptimization)&#xff0c;将其与传统SEO、普通软文投放做出清晰区分。传统新闻发稿、软文推广更多以搜索引擎收录、网页可见度、关键词网页作为考核目标&#xff1b…

作者头像 李华
网站建设 2026/9/8 13:19:09

金蝶云星空集成Delphi DLL与FastReport实现企业级自定义打印

1. 方案选型&#xff1a;为什么是金蝶云星空加Delphi DLL1.1 一个老牌ERP遇上的新打印难题金蝶云星空企业版在制造业、流通业里用得很广&#xff0c;单据流转、存货核算、BOM管理都靠它。但真到了实际业务现场&#xff0c;总会碰到一类让人挠头的问题&#xff1a;标准打印模板不…

作者头像 李华
网站建设 2026/9/8 13:19:01

Spring Boot 3.x升级实战:新特性、自动装配及坑点解析

SpringBoot新版本出来的时候&#xff0c;圈子里总有一波“升还是不升”的争论。我个人的态度一向是&#xff1a;先搞清楚新特性解决什么问题&#xff0c;再决定要不要跟进。Spring Boot 3.x 系列推出已经有一段时间了&#xff0c;从 3.0 到 3.2、3.3&#xff0c;再到现在的 3.4…

作者头像 李华
网站建设 2026/9/8 13:18:54

Delphi集成Python结巴分词:老项目中文分词实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华