起因是想在一台什么 GUI 库都没装的 Linux 机器上写个坦克大战。装不了 SDL、Qt、FLTK,最后发现最底层的 libX11 是有的——那就直接裸写 Xlib。

为什么是裸 X11

一台只有 libX11 的机器,能做的 GUI 上限就是直接调 X 协议:创建窗口、抓按键、画点、刷帧。没有事件循环框架,没有绘图抽象,连 fillrect 都要自己发请求。好处是零依赖,一条命令编译:

g++ -O2 tank.cpp -o tank -lX11

核心结构

  • 固定步长帧循环:用 usleep 控制 ~60fps,输入在帧首统一采集,逻辑、渲染分离。
  • 地图用二维字符数组:1 是砖墙(可打碎,四格一块)、2 是钢墙、3 是河流、4 是草地,基地是老鹰。
  • 碰撞检测:坦克和子弹都按"先算目标位置、再查格子"处理,坦克转弯只查角点。
  • AI 坦克:每 N 帧随机换方向,撞墙或出界时重新选;难度用移动速度区分。
  • 子弹:砖墙打碎四格砖块,钢墙和基地直接消失;子弹互相碰撞时同归于尽。

踩过的坑

双缓冲在 X11 里最朴素的实现就是:先画到一块 pixmap,再 XCopyArea 一次贴到窗口。不这么做的话,每一帧的中间状态都会闪。

另一个坑是 KeyPress 事件要用 XLookupKeysym 转成 keysym 再比较,直接比 event code 在不同键盘布局下会失灵。方向键的 keysym 是 XK_Up / XK_Down / XK_Left / XK_Right。

最后

整个游戏不到 1000 行 C。裸 X11 写起来确实啰嗦,但每一步都在眼前,没有任何黑盒。对想搞清楚"窗口到底怎么画出来的"的人来说,是一次很值钱的练习。