news 2026/9/28 18:08:06

带你认识老派编程语言:Delphi 2007 复刻童年《坦克大战》

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
带你认识老派编程语言:Delphi 2007 复刻童年《坦克大战》

先扯点这门语言的历史。因为作为一个新生代的程序员对此语言多半是不了解,甚至没听说过的。
1995 年,Borland 推出 Delphi 1.0,首席架构师是传奇人物 Anders Hejlsberg(就是后来跑去微软搞出 C# 和 TypeScript 的那位)。当年的 Delphi 是带着"VB 杀手"的名号出道的:编译成原生机器码快得飞起、VCL 组件拖完就能跑、数据库开发体验吊打一众对手,风头一度盖过 Visual Basic。整个 90 年代末到 2000 年代初,它在中国企业级开发里是绝对的王者——进销存、医院系统、银行前端、各类 MIS,几乎半壁江山都是 Delphi 写的,2002 年的 Delphi 7 至今被很多老程序员封为"神版本"。但辉煌之后就是下坡路:.NET 和 Java 生态崛起,Anders 被微软挖走,Borland 经营失策,开发工具部门几经转手(CodeGear → Embarcadero),Delphi 的市场份额一路萎缩,到 2010 年前后在国内基本淡出了主流视野。然而它没有死——无数当年用 Delphi 写的企业系统至今还在银行、医院、工厂里跑着,而维护这些系统的程序员(比如我),就成了这门语言活着的证明。

在维护公司客户的一套老 Delphi 代码时,天天和 VCL、TQuery 这些"老伙计"打交道。某天迭代完一个需求,突然冒出一个念头:既然目前天天写这套东西,为什么不干脆用它做点让自己快乐的项目?于是就有了这个用 Delphi 2007 复刻的联机坦克大战。没有游戏引擎、没有多线程库,整个游戏核心只有一个 2589 行的 MainForm.pas,却实现了双人联机对战、AI 寻路、A* 算法、地图编辑器一整套东西。本文把开发过程里的设计思路逐模块拆给大家看,也顺便聊聊:这门"过气"了的语言,为什么至今依然好使。

目录

一、项目概览

二、开发环境与技术选型

三、整体架构:一个窗体就是一个游戏引擎

四、游戏循环:十几个 TTimer 的排班表

五、AI 设计:普通 AI 三板斧,BOSS 坦克真会 A*

5.1 普通坦克:三层决策

5.2 BOSS 坦克:递归式 A* 寻路

六、联机同步:教科书级的"瘦客户端"方案

6.1 两条通道各司其职

6.2 控制信令:手搓的 VK_ 协议

6.3 状态快照:一个 record 打包整个战场

七、渲染:GDI 双缓冲 + 摄像机跟随

八、地图编辑器

九、踩坑实录:老技术栈教会我的事

十、总结


一、项目概览

项目目录结构非常清爽,典型的"三件套":

  1. Source/—— 游戏主工程(TankWar.dpr),约 3400 行 Pascal 代码;
  1. MapEditer/—— 独立的地图编辑器工程,用来生成 .map 地图文件;
  1. Bin/—— 编译产物和两张示例地图(Map1.map、Map2.map)。

主工程一共 7 个单元,分工如下:

单元

职责

MainForm.pas

游戏主窗体,几乎全部游戏逻辑都在这里(2589 行)

Unit_CreateGame.pas

创建游戏(选地图、开房间)

Unit_JoinGame.pas

加入游戏(输入 IP 连接主机)

Unit_Chat.pas

联机聊天窗口

Unit_SelectMap.pas

地图选择

TankWar_TLB.pas

类型库(拖控件自动生成的)

UnitAppface.pas

皮肤库相关

说个开发中的小事故:MainForm.pas 的中文注释曾在一次文件转码中全军覆没,2500 多个汉字变成了清一色的?号。痛定思痛后我把所有源码固定用 GBK 保存并纳入版本管理,才没让惨剧重演。写 Delphi 的朋友大概率都懂这种痛——这也是我后来给客户代码做维护时格外谨慎的原因。

二、开发环境与技术选型

先看工程配置(TankWar.dproj):

  1. 编译器:DCC32,工程文件版本 7.0,即 Delphi 2007;
  1. GUI 框架:经典 VCL;
  1. 网络:双栈混搭 ——ScktComp的 TServerSocket / TClientSocket(TCP)+ Indy 9 的 IdUDPServer / IdUDPClient(UDP)+ IdIcmpClient(Ping 测延迟);
  1. 绘图:纯 GDI,TPaintBox + TBitmap 手绘,没有 DirectX / OpenGL;
  1. 音效:MMSystem(Windows API 层面的播放)+ Sound.RES 资源文件;
  1. 游戏循环:十几个 TTimer 排班上岗,一个真正的线程都没有。

这套选型放到今天看处处是"反面教材",但我要为它说句话:对一个下班后凭兴趣写的小游戏,VCL 的成熟度、Delphi 编译器的速度、组件拖完就能跑的开发体验,正是当年它能统治 Windows 桌面开发半壁江山的原因。整个项目从搭框架到能联机对战,实际编码时间并不长——这就是 RAD 的含金量。

三、整体架构:一个窗体就是一个游戏引擎

整个游戏没有独立的 Game 类,Tfrm_Main就是引擎。我用一组 record 定义了全部游戏状态:

几个自己比较得意的设计点:

  1. 坦克是圆形的。不用矩形 AABB,全部用GetDistance(两点距离)做圆形碰撞检测,半径 20 像素左右。这个偷懒的做法在这类网格游戏里意外地好用;
  1. 每辆坦克自带一张 25×25 的寻路缓存表。PathValues 直接长在 record 里,6 辆坦克就是 6 份独立寻路状态,简单粗暴但有效;
  1. Steps 字段是 AI 的节拍器。各种 AI 逻辑用Steps mod 40 = 0、Steps mod 200 = 0这类条件触发,相当于给不同 AI 事件设置了不同的"心跳周期"。

四、游戏循环:十几个 TTimer 的排班表

没有 while(True) 主循环,游戏被拆成了十几个定时器协程:

TTimer

职责

YellowTankMoveThread

黄方玩家坦克移动(节拍驱动)

GreenTankMoveThread

绿方玩家坦克移动

AutoFireThread

AI 坦克自动开火

TankCreateThread / WaitForCreateThread

坦克出生动画与排队

WaitForDisappearThread

爆炸消失动画

DataSendThread / DataReciveThread

联机状态收发

DrawScreenThread

画面重绘

RefreshLabelThread

HUD 刷新(比分、存活数)

CheckGameInforThread

胜负判定

CalculateDelayThread

网络延迟计算

单机模式下,键盘中断直接改状态,TTimer 们按各自周期推着状态走——本质上还是"状态 + 帧驱动",只是每一帧被拆到了不同频率的定时器里。为什么不用真线程?因为 VCL 控件只能主线程摸,TTimer 反而是最安全、零锁、零同步原语的选择。代价是帧率受 Windows 定时器精度限制(约 15.6ms),逻辑周期不可能精确——但对坦克大战这个量级的游戏,完全够用。

五、AI 设计:普通 AI 三板斧,BOSS 坦克真会 A*

5.1 普通坦克:三层决策

AutoTankControl里普通 AI 的决策优先级是:

  1. 贴边逃生:撞到地图边界就按随机概率(Random(31) 分三档)反向转向;
  1. 避让/避墙:发现别的坦克或不可通行格子(Kind = 2/3/5/6/7 的砖墙、铁墙、水、草丛)挡路,就朝反方向转向(AIChangeAspectA);
  1. 索敌追击:Kind = 3 的坦克每 200 步判断一次,朝距离更近的玩家转向(AIChangeAspectB)。

5.2 BOSS 坦克:递归式 A* 寻路

Kind = 4 的 BOSS 坦克每 100 步重建一次路径场。核心的SearchPath是一个递归实现的四方向最短路搜索:

严格说这是记忆化递归搜索(类似 Dijkstra 场 flood),但它干了 A* 该干的活:算出全图每格到目标的代价,然后 BOSS 每一步都朝代价最小的邻格走(BossChangeAspect)。一个有意思的细节是转向平滑处理——当新旧朝向发生 90 度转弯时,代码会把坦克坐标"吸附"到 40 像素网格中线上,防止斜穿墙角。21×21 的小地图喂这个递归毫无压力,2007 年的家用机都跑得飞快。

六、联机同步:教科书级的"瘦客户端"方案

这是整个项目我自己最满意的部分。架构是经典的主机权威 + 状态广播:

  1. 主机(Server):运行全部游戏逻辑,是唯一的状态权威;
  1. 客户端(Client):基本只做渲染和输入转发,收到什么画什么。

6.1 两条通道各司其职

通道

协议

内容

TServerSocket / TClientSocket

TCP

控制信令 + 聊天文本

IdUDPServer / IdUDPClient

UDP

键盘输入上行 + 战场快照下行

6.2 控制信令:手搓的 VK_ 协议

TCP 上跑了一套自定义文本协议,全部以VK_前缀命名(和 Windows 虚拟键码 VK_ 撞个满怀,算是写代码时的小恶趣味):

信令

含义

VK_MAP_LOADED

客户端:地图收到了

VK_BEGI / VK_NEXT_BEGIN

主机:开始游戏

VK_CONN

连接确认

VK_WINS / VK_LOSE

胜 / 负

VK_PAUS / VK_RECO

暂停 / 恢复

VK_ERROR

主机出错

VK_BLA1

开火音效同步

VK_TEST

UDP 通道测试

客户端按键后根本不处理移动,直接IdUDPClient.Send('VK_UP')把键码原样发给主机:

6.3 状态快照:一个 record 打包整个战场

主机端DataSendThread定时把整个战场状态打包进一个巨型 record 广播:

客户端DecodeBuffer收到后逐字段拷贝回本地状态数组,下一帧 DrawScreen 直接画。地图格被摧毁这种增量信息靠Pos + Kind两个字段捎带。整包大约 700 字节,按 TTimer 周期广播,局域网里丝般顺滑。延迟显示用 IdIcmpClient 直接 Ping 主机实现,简单诚实。

后来我意识到,这套"TCP 信令 + UDP 快照 + 主机权威"的三分结构,其实就是今天多人游戏同步的标准答案——只是当年的实现粗糙,现在的实现精致,骨架是一模一样的。

七、渲染:GDI 双缓冲 + 摄像机跟随

地图是 21×21 网格,每格 40 像素,整张地图 840×840,而游戏窗口小于地图,所以做了一个小小的"摄像机"系统:

  1. LoadMap把 .map 文件读进 21×21 的字节数组,DrawLowerMapGrids/DrawUpperMapGrids分别绘制地面层(水、地面)和可遮挡层(砖、铁、草丛);
  1. 所有元素先画到一张 840×840 的离屏 TBitmap 上(双缓冲,防闪烁);
  1. ChangeScreenPos实现摄像机跟随:当玩家坦克离屏幕边缘小于5×Radius且继续朝该方向移动时,镜头跟着平移——就是现在游戏里常见的 camera dead-zone 手法;
  1. 最后DrawScreen把离屏位图按 ScreenPosition 裁剪贴到 GameBox 上。

坦克的四个朝向不是旋转生成的,而是老老实实各画了一张,ProcessBitmap负责载入时预处理。

八、地图编辑器

MapEditer是个独立的 Delphi 工程,用来可视化摆放 21×21 网格里的各种格子(砖墙、铁墙、水、草丛等 7 种 Kind),保存为 .map 文件。联机时主机把整个TMap(21×21 = 441 字节)通过 TCP 一把发给客户端,客户端校验Size = Sizeof(TMap)后载入并回VK_MAP_LOADED——用 record 的内存长度做包完整性校验,简单有效。

九、踩坑实录:老技术栈教会我的事

开发过程也是踩坑过程,几处非常有代表性的问题,写出来给同样在维护 Delphi 老项目的兄弟避坑:

  1. 编码是 Delphi 项目的头号地雷:中文注释在转码中全部变?的教训前面说过了。现在我的原则是:老代码不乱转码,要转先备份;
  1. TServerSocket 已废弃:Delphi 2007 里 ScktComp 已被标记 deprecated,官方推荐 Indy。项目里我两个都用了(TCP 走 ScktComp、UDP 走 Indy),如果是新项目,建议直接全 Indy 或 System.Net.Socket;
  1. Sleep(1000) 卡死主线程:开局动画我直接在主线程 Sleep 一秒,整个 UI 冻结——自己写着爽,但放在正式代码里肯定要被同事喷;
  1. 递归寻路无深度限制:SearchPath是纯递归,靠 TriedPos 数组防死循环,最坏情况栈深度与地图格数同阶。还好地图只有 441 格,地图再大就得改成显式队列;
  1. 魔法数字的债:格子 Kind 的 2/3/5/6/7、坦克 Kind 1~4、坐标偏移 20/40 全是裸数字。注释被毁之后,这些魔法数字成了最难读的部分——这也是我后来在客户代码里坚持见到裸数字就补枚举的原因。

十、总结

写这个小项目,最初只是突发奇想,写完却发现收获比预期大:

  1. 情怀层面,坦克大战是童年,Delphi 是饭碗,用饭碗复刻童年,快乐加倍;
  1. 架构层面,用最少的抽象(一个窗体 + 一组 record + 一堆 TTimer)实现了一个功能完备的联机游戏,证明了"窗体即引擎"在 VCL 时代完全可行,也让我更理解了自己维护的那套客户老代码当年的设计语境;
  1. 技术层面,"TCP 信令 + UDP 快照 + 主机权威"的同步骨架至今未过时,手搓记忆化寻路也让我重新捡回了被现成算法库惯坏的基本功;
  1. 工作层面,踩过的编码坑、废弃组件坑,反过来提升了我在公司老代码维护上的警觉性——副业项目反哺主业,这是最大的意外收获。

如果有人想在 Delphi 上做类似的小游戏,我建议的入手顺序是:先用 TTimer 搭出帧驱动骨架,再用 record 把游戏状态定死,最后才加网络和 AI——顺序反了会很痛苦。

项目开源地址


如果你也维护着一套 Delphi 老代码,或者也想用"过时"的技术做点让自己快乐的东西,欢迎评论区交流。觉得有收获的话,点赞 + 收藏再走。

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

从“替代人的手”到“替代人的脑”:工业AI正在跨越哪道分水岭?

一个工厂的“AI管家”,管的是什么?2026年9月,湖北某车间。生产线全速运转,却少见操作工的身影。AI系统全面接管生产流程后,这个百万吨级工厂年增效近2000万元。同月,容知日新发布“观星OS”设备智能运维操作…

作者头像 李华
网站建设 2026/9/28 18:07:09

电商3C小件退货率高:为什么插舌自锁箱售后处理效率更高

电商3C小件退货率高:为什么插舌自锁箱售后处理效率更高一、电商3C小件的售后包装痛点3C数码小件如配件、外设、数码周边等,退货率相对较高,售后环节需要开箱验视、检测、重新包装二次销售。普通胶带封箱纸箱在售后处理中,存在开箱…

作者头像 李华
网站建设 2026/9/28 18:07:07

Java+JSP企业宣传网站源码解析:从环境搭建到二次开发毕设指南

简介:本资源是一套基于Java与JSP开发的企业宣传网站毕业设计完整源码,面向计算机相关专业学生及Java Web初学者,帮助解决毕业设计选题、项目实战与技能提升需求。压缩包共879个文件,约21.27MB,涵盖gif、jpg、png等图片…

作者头像 李华
网站建设 2026/9/28 18:06:30

String、StringBuffer、StringBuilder 三者的区别(面试必问 + 源码级解析)

String、StringBuffer、StringBuilder 三者的区别(面试必问 源码级解析)这是一道 Java 面试的“开胃菜”,几乎每一场初级/中级面试都会问到。但很多人只能背出“String 不可变、StringBuilder 不安全但快、StringBuffer 安全但慢”这一句&am…

作者头像 李华
网站建设 2026/9/28 18:05:55

威胁情报驱动的恶意软件检测:从情报采集到证据链闭环

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

作者头像 李华