ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA双游戏系统设计复盘:钢琴块+弹球从仿真到上板

FPGA双游戏系统设计复盘:钢琴块+弹球从仿真到上板 简介本资源是面向电子信息、计算机、自动化等专业本科生的FPGA课程设计与竞赛实践项目完整实现钢琴块节奏游戏与物理弹球双系统解决数字电路综合应用、VGA图像驱动、状态机建模及跨时钟域处理等核心教学难点。压缩包含50个文件以Verilog源码.v、VHDL配置.vhd、约束文件.xdc、综合/实现报告.rpt/.tsm、日志.log及IP核.ipc/.adc为主覆盖从顶层模块al_top.v、显示驱动VGA_Dispay.v、游戏逻辑SBgame.v、VGA_piano.v到七段译码、PLL时钟管理的全链路工程总大小3.66MB。已有140人学习下载资源经高校课程设计评审获优秀等级在macOS/Windows/Linux多平台验证稳定运行。用户可直接部署上板演示亦可基于清晰分层架构开展功能扩展、算法优化或教学演示配套文档与完整工程结构显著降低FPGA图形交互类项目入门门槛。 这段时间刚把一个FPGA的双游戏系统从仿真推到板卡上跑通正好把整个项目的设计思路、代码结构和实机调试过程完整复盘一遍。这个项目基于FPGA实现了钢琴块和弹球两个游戏的实时运行完整源码和资料都在文末整理了出来适合正在做数字逻辑课程设计、FPGA入门进阶或者想找完整项目练手的人参考。整个项目覆盖了VGA显示驱动、按键输入消抖、游戏状态机、物理碰撞判定、音效输出这些FPGA开发的高频核心点而且代码量适中属于那种麻雀虽小五脏俱全的典型工程。我最初决定做这个双游戏系统理由其实很朴素FPGA做游戏的本质是把游戏逻辑全部翻译成并行硬件电路来描述这和单片机轮询、上位机事件驱动有本质区别。而钢琴块和弹球恰好代表了两种截然不同的游戏逻辑范式——前者是节奏判定核心在按时、按对位置按下按键后者是物理模拟核心在位置、速度、碰撞响应。一套硬件同时跑两套逻辑相当于用一份工程练了状态机设计、时序协调、计算逻辑三块基本功性价比非常高。1. 为什么是钢琴块弹球双游戏系统的核心设计价值1.1 两套游戏逻辑恰好互补先说说我为什么偏偏选这两个游戏。钢琴块的玩法很简单屏幕上有四列黑色方块往下掉方块落到判定线附近时玩家要按下对应的按键。这个游戏在FPGA里实现本质是二维坐标系的滚动与区间判定——你需要维护每个方块当前的Y坐标每个时钟周期或帧周期让Y递增然后判断玩家按键时手指对应的轨道上有没有一个方块落在允许误差范围内。这种周期性数据更新裁判式判定的逻辑练的是状态机的稳定性和判定窗口的设计。弹球游戏就不一样了。挡板左右移动、球以某个角度飞出去、撞到砖块反弹、砖块消去这套玩法在FPGA里需要实时维护球的坐标和速度分量每一帧都要判断球是否碰到挡板、碰到左右墙、碰到砖块。碰撞检测和反弹方向计算是纯数字逻辑里的计算密集型问题稍不留神就会出现穿模、卡边界、球飞出屏幕这种诡异现象。把这两个游戏放在同一个系统里意味着你需要设计一套通用性够好的顶层架构——显示部分、按键部分、音效部分最好能复用只是游戏逻辑各自独立。这种共性抽象、个性隔离的模块划分思想在真实工程里让人受益匪浅。如果只做一个游戏你可能不会思考这么多分层设计的问题。1.2 适合什么基础的人来做这个项目对基础的要求不算苛刻。你得会Verilog的基本语法知道什么是always块、什么是时序逻辑和组合逻辑的区别、能写简单的状态机最好还了解VGA显示的基本时序概念。如果这些还不太熟建议先花一周时间分别做两个小实验一个是让VGA显示一个静止的彩色方块另一个是用按键控制LED亮灭把这两个打通了再来看这个项目就不会被底层细节绊住。开发板方面我用的是常见的Altera系板卡板上带VGA接口、按键、蜂鸣器和50MHz晶振。Xilinx的EGO1或者黑金系列板卡也完全可以做代码里只涉及PLL原语、引脚约束这些需要按平台调整的部分游戏逻辑本身是全平台通用的。这里我习惯的做法是把PLL实例化单独放一个文件换平台时只改这个文件里的参数和原语名称。2. 顶层架构与数据通路从按键抖动到VGA像素流2.1 模块划分思想先看图1我这里用文字描述一下整体结构结构图做出来就是这样的分层关系顶层模块top ├─ 时钟管理模块PLL生成25MHz像素时钟 ├─ 按键消抖模块对按键信号打拍消抖 ├─ 游戏模式选择模块当前是钢琴块还是弹球 ├─ 钢琴块游戏逻辑模块输出该游戏下的方块坐标/判定结果 ├─ 弹球游戏逻辑模块输出该游戏下的球/挡板/砖块坐标/碰撞结果 ├─ VGA显示驱动模块根据当前坐标输出RGB颜色 ├─ 画图模块根据游戏逻辑的物体坐标生成像素颜色 └─ 音效输出模块分频产生不同频率的方波驱动蜂鸣器顶层模块要解决的最重要问题是两个游戏共享同一套显示和输入但各自维护各自的内部状态。我的做法是给顶层定义一组游戏对象的通用接口——每个游戏模块都输出一个物体列表物体类型、中心X、中心Y、宽度、高度、颜色画图模块只认这组接口不管具体是哪个游戏。这样做的直接好处是VGA驱动和画图模块只需要写一次两个游戏各自把自己的方块/球/挡板翻译成统一格式的矩形坐标剩下的事情交给画图模块。后续如果我想加第三个游戏比如贪吃蛇只需要新写一个游戏逻辑模块输出同样的接口即可显示端完全不用动。2.2 像素时钟与帧同步的配合关于时钟50MHz的板载晶振进来之后我用PLL分出一路25MHz作为VGA像素时钟。选择25MHz是因为我用的640x48060Hz显示模式每像素一个时钟每行需要800个像素时钟含消隐区每帧有525行25MHz刚好满足80052560≈25.2MHz这个标准。这样计算最简单同步信号计数器直接以像素时钟为基准数数不用做跨时钟域处理。这里有个初学者容易踩的坑如果把游戏逻辑的更新也放到像素时钟域里那么一个像素时钟才40ns游戏的方块以这个速度滚动会快到无法使用。所以游戏逻辑里的物体移动我单独做了一个帧计数信号——每次VGA扫描到一帧结束也就是VSYNC信号拉高的那个位置就产生一个单周期脉冲游戏逻辑只在收到这个脉冲时才更新物体坐标。这样就实现了显示连续跑、游戏按帧更新的效果整个逻辑清晰且不容易出时序问题。3. 钢琴块的实现拆解滚动节奏、落点判定与游戏状态机3.1 方块生成与滚动逻辑钢琴块的轨道我固定为四列每列宽度占屏幕的四分之一方块宽度约等于列宽减去边距。方块数据我用了一个移位寄存器组每个轨道维护当前可见的一组方块Y坐标新方块从屏幕顶部生成每个帧同步脉冲让所有方块的Y坐标加一个步进值当某个方块的Y坐标超过屏幕底部时把它移出队列。新方块的生成我写成了一个节拍计数器每隔N帧自动生成一个新方块轨道号用伪随机序列决定。注意这里伪随机序列最好不要用完整的LFSR去每次产生而是用一个简单的线性同余计数器取模避免出现连续好几个方块都在同一轨道的尴尬局面。我调试的时候遇到过连续三次同一轨道的方块玩家根本来不及按后来改成生成轨道与上一个轨道不重复的约束条件体验好很多。方块移动速度直接对应游戏难度。我把速度分成了三档用一个拨码开关或按键切换慢速档每个帧同步脉冲移动1个像素中速档移动2个快速档移动3个。移动速度改变的是每帧位移量而不是每帧是否移动这样滚动看起来更平滑不会出现一顿一顿的感觉。3.2 判定窗口设计为什么宽容度要给够钢琴块最核心的部分是落点判定。判断逻辑不复杂按键按下时检查该轨道上是否存在一个方块其Y坐标在判定线附近的一个范围内。真正的关键在范围的取值。太窄玩家会觉得很苛刻太宽游戏会失去挑战性。我最终取的判定窗口是方块高度的±1.5倍。也就是如果方块高40像素那么方块中心与判定线之间的偏差在60像素以内都算命中。换算成时间640x480模式下方块中速滚动时每秒移动约60像素也就是保守估计玩家有大约1秒的响应时间这与手机版钢琴块的Good/Perfect手感比较接近。这里有三个判定级别偏差在0.5倍方块高度以内判Perfect1.0倍以内判Good1.5倍以内判Normal。不同级别给不同分数。用分数量化手感之后调试目标就变成了让玩家能稳定打出Good以上的判定而不是靠感觉反复调参数。3.3 状态机设计与计分逻辑钢琴块的游戏状态机我设计了五个状态IDLE待机、PLAY运行中、PAUSE暂停、GAMEOVER结束。IDLE状态下屏幕显示标题和按按键开始的提示按START键进入PLAYPLAY状态下游戏正常运行按PAUSE键暂停再按一次恢复方块落到底部漏掉时生命值减一生命值归零则进入GAMEOVER。计分逻辑用了两个寄存器总分和连击数。每次命中时连击数加一总分按基础分*连击系数累加每次漏掉方块时连击数清零。连击系数的设计让玩家有动力保持连续命中而不是乱按一通。最终把总分显示到数码管上每次命中时让分数对应的LED电平翻转一次作为视觉反馈。状态之间的切换我全部用同步时序逻辑完成用一个状态寄存器存当前状态再用一个组合逻辑块根据输入信号和当前状态计算下一状态。这里特别提醒一下状态机的输出信号比如判定线是否高亮、分数是否清零不直接由状态信号驱动而是额外用寄存器寄存一拍再输出避免组合逻辑的毛刺传出去导致显示闪烁或误触发。4. 弹球游戏的数字物理碰撞检测、反弹方向与边界处理4.1 坐标建模与运动更新弹球游戏我把它建模成一个640x480画布上的物理系统。球用一个10x10的方块表示为了节省逻辑资源画圆需要算距离画方块只需比较坐标区间每个帧同步脉冲更新一次位置x x vx; // vx是x方向速度分量 y y vy; // vy是y方向速度分量vx和vy初始值我设成两个相邻的整数比如vx2、vy3。为什么要让两个分量不相等如果相等的话球的运动轨迹永远是45度角来回反弹会形成固定的周期路径玩家的可操作性会大大降低。用两个不等的速度分量球的轨迹会更杂乱更有可玩性。挡板宽度设置成屏幕宽度的1/6大约106像素高度8像素位于屏幕底部上方约40像素的位置。挡板位置由两个方向键控制每次按下移动8像素松开就不动。这里需要注意挡板不能用按住就一直动的连续扫描方式否则在VGA逐帧模式下移动速度太快根本控制不住。我的做法是检测到按键消抖后的上升沿挡板才移动一格按一下动一下节奏稳定。4.2 碰撞检测AABB矩形相交判定碰撞检测统一用AABBAxis-Aligned Bounding Box方式也就是轴对齐包围盒相交判定。对两个矩形A和B相交条件是A.x B.x B.width A.x A.width B.x A.y B.y B.height A.y A.height B.y这里有个隐蔽的坑判定时用的是球移动前的位置还是移动后的位置。我最初用移动后的位置检测碰撞结果帧率稍高时球会穿透挡板——因为球速度很快一帧移动了10像素上一帧球还在挡板上方8像素处这一帧直接跑到了挡板内部甚至下方矩形相交判定只会在恰好有交集的帧触发中间那帧没有交集于是穿透了。解决方案是运动分解子步检测把一帧的位移拆成多个小步每小步做一次碰撞检测。我写的是把位移拆成4个子步每帧分4次更新坐标并做碰撞检测这样单步位移最多3个像素就基本不可能跳过只有8像素高的挡板了。代价是游戏逻辑的运算量增加了4倍但对FPGA来说这点逻辑资源完全不是问题。4.3 反弹方向计算从直接翻转到分量重分配碰撞之后的反弹处理是弹球游戏设计上最有意思的部分。最粗糙的做法是碰到任何东西都简单地翻转速度分量碰顶边vy取反、碰左右墙vx取反、碰挡板vy取反。这样做逻辑最简单但问题很明显球永远是按固定入射角弹来弹去玩家无法通过挡板控制球的走向游戏就成了听天由命的抽奖模拟器基本没有可玩性。我最终采用的是按碰撞位置调整vx的方案挡板宽约106像素命中点离挡板中心越远反弹后的水平速度分量就越大。具体实现是在挡板上定义7个区间每个区间对应一个vx的档位——中心区间vx不变越靠外vx的绝对值越大最外沿的两档vx翻倍。判定方式就是比较命中点坐标与挡板中心的差值落在哪个区间然后查表更新vx。这里我不建议用浮点乘除法去算精确的反弹角度FPGA上浮点代价太大而且这个游戏根本不追求物理精确度。用区间查表的方式逻辑资源消耗小调试时还能直观地看到挡板左边缘把球弹向左边这种手感玩家很快就能找到控制球路的规律。砖块消去的逻辑也值得一提。砖块阵列我用了6行x8列每个砖块用一个valid位记录是否存在。碰撞检测时遍历所有valid的砖块做AABB判定命中后清掉valid位并把vy取反。这里为了省事我一开始只检测了球的上边与砖块下边的相交导致球从侧面撞砖块时发生穿侧壁现象。后来改成遍历所有砖块用一个方向标志记录这次碰撞是从哪个方向来的再决定翻转vx还是vy代码量多了不少但行为终于正常了。5. VGA显示驱动与画图模块让两个游戏共用一套像素流水线5.1 640x48060Hz时序复盘VGA显示这部分虽然网上各种教程满天飞但真正自己从头写一遍还是有不少细节。640x48060Hz模式下行同步时序是每行总共800个像素时钟其中数据有效区640个前沿front porch16个同步脉冲96个后沿back porch48个。帧同步类似每帧总共525行其中有效行480行前沿10行同步脉冲2行后沿33行。我的VGA驱动模块里维护了两个计数器h_count0-799和v_count0-524。当h_count在有效区间即0-639并且v_count在有效行区间即0-479时当前扫描坐标(x, y)就是(h_count, v_count)这时才输出有效的RGB数据否则RGB输出全黑。一个容易忽略的细节是同步信号的有效极性。VGA标准的行同步HSYNC和场同步VSYNC在640x48060Hz下都是低电平脉冲有效——也就是说同步信号正常是高电平在指定的同步脉冲区间拉低。如果极性搞反很多显示器会直接黑屏或者不同步这属于仿真完全正常、上板毫无显示的经典原因之一。5.2 画矩形、画方块、画背景组合逻辑判断画图模块的思路是纯组合逻辑在每个像素时钟沿到来时根据当前扫描坐标(x, y)以及游戏逻辑模块输出的物体坐标判断这个像素应该是什么颜色。不存在把图像存到RAM再读出来这种帧缓冲方案因为以640x480分辨率的裸VGA驱动直接用逻辑门实时判断即可覆盖全部像素帧缓存反而增加不必要的BRAM消耗和读写时序复杂度。画一个矩形需要知道四个参数左上角坐标(rect_x, rect_y)、宽度rect_w、高度rect_h。然后判断(x rect_x) (x rect_x rect_w) (y rect_y) (y rect_y rect_h)成立则这个像素属于该矩形。画图模块里有一系列的矩形比较器分别对应背景、四轨方块、判定线、挡板、球等物体。比较器按优先级排列先判断球弹球模式、再挡板、再砖块最后才是背景保证覆盖关系的正确性。这里有个性能上的小优化物体数量一多逐个比较会消耗大量LUT。我用的办法是区域过滤——先粗粒度判断当前扫描坐标是否落在某个可能包含物体的区域是才做细粒度的矩形比较否则直接输出背景色。比如弹球的球在x方向只有10像素宽如果当前扫描x不在球的范围内就直接跳过球的所有相关比较。这种优化对这种像素级判定的架构非常有效综合后在1050T级别的板卡上逻辑占用率一下子降了百分之十几。5.3 双游戏共用显示驱动的切换设计两个游戏共用一套VGA驱动和画图模块切换方式我用了一个mode信号和二选一多路器。mode0时画图模块参考钢琴块游戏逻辑输出的物体列表mode1时参考弹球游戏逻辑输出的物体列表。两种模式的物体都在各自的游戏逻辑模块里实时计算画图模块不关心当前是哪个游戏只负责把拿到的物体绘制出来。为了在两款游戏切换时不出现画面残留我在mode切换的同一帧里让VGA驱动产生一个整屏清空信号清空信号有效的那一帧画图模块无条件输出背景色。这样切换瞬间虽然会闪一下黑屏但不会出现上半屏是钢琴块、下半屏是弹球残影的诡异画面。6. 音效与按键处理蜂鸣器分频音调与20ms消抖6.1 按键消抖计数器稳定性判定按键输入如果不做消抖直接接进游戏逻辑会引入大量毛刺——机械按键的抖动时间通常在5-20ms左右按下一次可能被识别成三四次。最常见的消抖方案是20ms延迟确认检测到按键电平变化后启动一个计数器持续20ms后再次采样如果电平还是同一个值则判断为有效按键事件。我的消抖模块实例化了多个通道的通用消抖器每个通道用一个计数器。计数器在输入电平和稳定电平时清零当电平持续保持新值超过20ms时把stable_out更新为新值。同时我专门输出一个按键有效沿信号每当stable_out发生跳变从0到1或从1到0时这个信号拉高一个时钟周期。游戏逻辑用的是这个有效沿信号而不是电平信号这样可以避免长按按键导致同一事件反复触发的问题。6.2 音调生成计数器分频输出方波音效部分我用的板上蜂鸣器蜂鸣器分为有源和无源两种——有源蜂鸣器通电就响无源蜂鸣器需要外部给特定频率的方波才能发声。如果是无源蜂鸣器FPGA可以通过计数器分频产生不同频率的方波来驱动它发声。钢琴块的七个音阶对应频率大概是这样音名频率(Hz)计数器分频系数25MHz时钟Do (C4)26247709Re (D4)29442517Mi (E4)33037878Fa (F4)34935745Sol (G4)39231887La (A4)44028409Si (B4)49425203分频系数的计算方法很简单目标频率是f系统时钟是25MHz那么计数器计数到f/25000000时输出电平翻转一次实际输出方波频率就是目标频率。上面表格里的系数我按周期翻转来算也就是每计满一次就翻转输出所以实际分频值要对半处理。比如Do音25MHz/262Hz≈95420电平每47709个时钟翻转一次。音长控制方面每个音符播放200ms后自动停止也就是用一个计数器按下按键时清零计数到5百万25MHz*0.2s后输出静音。这样玩家快速连按时音符不会拖尾重叠听感干净很多。弹球游戏里的音效和钢琴块不同我用的逻辑是球撞到挡板时播放一个短促的嘟声约80ms撞到砖块时播放一个更高频率的嗒声约50ms球出界时播放一个低频长音。所有音效共用一套分频器只不过驱动分频器的目标频率和播放时长由不同事件决定。7. 实机调试踩坑记录仿真全绿上板翻车的三个真实案例7.1 问题一VGA画面整体偏色红色变成了紫色第一版代码上板之后画面能正常显示但颜色完全不对——应该是红色的方块显示成了红紫相间的条纹状。我第一步先怀疑是VGA硬件连线的问题用万用表测了所有颜色通道的电平发现R、G、B三个引脚输出正常。然后就开始排查代码。最终找到的原因很隐蔽我把颜色寄存器定义成了12位RGB各4位但VGA输出的R、G、B信号在顶层模块里只取了高两位。比如红色应该输出4b1111但顶层把低两位的11当成了独立信号接到了G通道的引脚上导致红色信号泄漏到了绿色通道。这种位宽不匹配导致的错位在仿真里极难发现因为仿真波形上每个通道的值都是完整的只有真正上板看到颜色才会意识到不对。解决办法也很简单顶层模块里把颜色信号的每个通道按正确的位宽赋值R通道只接红色寄存器的对应位不做任何交叉连接。模块接口处严格做好位宽声明不让综合器擅自截断或扩展。这个问题虽然低级但很能说明仿真通过不代表硬件正确。7.2 问题二钢琴块判定偶发失灵连续按几次漏掉一次钢琴块玩起来发现偶尔明明按到了却提示漏掉。我反复测试后发现规律按键正好在方块刚进入判定窗口那一帧按下时判定容易失败。定位过程比较费劲我最后用板上LED把当前方块Y坐标是否在判定窗口内这个信号引出来实时观察发现按下按键的瞬间判定窗口信号恰好处于还没拉高的阶段。原因出在帧同步脉冲和按键沿信号的时序竞争上。帧同步脉冲每帧产生一次按键沿信号可能在任何时刻到来。如果按键沿到来时游戏状态机正处于根据帧脉冲更新方块坐标的同一时钟周期就会产生不确定的时序行为——有些实现里方块坐标先更新有些实现里判定逻辑先读到旧坐标结果就不稳定。修复方式把按键沿信号和帧脉冲信号在游戏逻辑里做两级同步寄存确保两个信号不会在同一周期参与判定同时在判定逻辑里明确优先级按键沿到来时先执行基于当前坐标的判定再执行方块坐标更新。经过这轮修改后再测试连续按压一百次再也没有出现过漏判。7.3 问题三弹球在屏幕底部穿模直接消失弹球游戏运行时发现球到达屏幕底部时偶尔会直接消失而不是被挡板反弹回去。我检查了很久最终通过SignalTapQuartus的在线逻辑分析仪Xilinx的对应工具是ILA抓取了球坐标和碰撞信号发现球出界的一瞬间vx和vy同时被零覆盖了。罪魁祸首是一个很典型的Verilog编码错误我在一个always块里同时用非阻塞赋值给vy赋了新值又在另一个always块里对vy做了条件赋值两个驱动源对同一个寄存器反复驱动导致综合后出现多驱动冲突。仿真时由于事件调度顺序的特殊性结果看起来是正常的但综合之后硬件行为就不确定——有些位被一个always块覆盖有些位被另一个覆盖球的速度矢量彻底紊乱表现为球突然消失或飞向诡异方向。修复后我统一了赋值规范同一个寄存器只允许在一个always块里赋值所有速度更新逻辑全部收拢进同一个模块的时序块中。这是个老生常谈的Verilog规范但真遇到故障时才发现平时不遵守规范欠下的债迟早要还。这三个问题排查下来我的经验是仿真最擅长验证逻辑正确性但很难发现硬件特殊性——位宽不匹配、多驱动冲突、跨时钟域竞争这些问题靠仿真覆盖很难提前暴露。所以上板调试时板上LED和在线逻辑分析仪是最重要的两个工具比仿真波形直观得多。8. 源码目录结构与可扩展方向给后续改造留下空间8.1 工程文件组织建议整个项目的源码我按功能做了目录划分结构大致是fpga_games/ ├── rtl/ # 所有RTL源码 │ ├── top.v # 顶层模块例化各子模块 │ ├── pll.v # 时钟管理PLL │ ├── vga_ctrl.v # VGA时序驱动 │ ├── draw_module.v # 画图模块 │ ├── piano_game.v # 钢琴块游戏逻辑 │ ├── breakout_game.v # 弹球游戏逻辑 │ ├── key_debounce.v # 按键消抖 │ └── sound_ctrl.v # 音效控制 ├── sim/ # 仿真文件和testbench ├── constr/ # 引脚约束文件 ├── doc/ # 项目文档和说明 └── README.md # 项目总览这种组织方式的好处是rlt目录下每个文件职责单一换平台时只需要改top.v的引脚例化和pll.v的时钟参数仿真文件独立出来之后可以针对每个模块单独写testbench不必每次都要跑整个系统。建议每个模块写完就顺手写一个最简testbench哪怕只是看几个关键信号的波形对后期定位问题帮助巨大。8.2 值得尝试的扩展方向如果做完这个项目觉得意犹未尽我个人觉得这几个方向很值得继续搞第一个是给钢琴块加上真实的音乐播放能力。现在只有蜜蜂器分频的简单音调如果要播放完整的曲子需要把简谱转换成音符长度序列存到ROM里再设计一个音乐状态机按节拍读取ROM、控制音调切换。这相当于给游戏加上音序器能力做完之后音效体验会上一个台阶。第二个是弹球游戏加双人模式。用两块PS/2键盘或者两组按键分别控制上下两个挡板中间区域绘制一排中场砖块双方各守半边球打到中场砖块会造成可破坏的防守屏障。这类需求在架构上只需要扩展游戏逻辑模块显示驱动完全不用动。第三个是往显示端加复杂度——用OSDOn-Screen Display在游戏画面上叠加菜单界面。我现在只在IDLE状态显示静态标题如果加上简单的菜单光标移动和选项高亮游戏会更有完整产品的感觉。这个方向需要额外处理菜单渲染和游戏渲染的切换但本质上就是我们已经在做的mode多选器思路只是把mode从两个变成三个。这些扩展都不会伤筋动骨因为我当初做顶层划分时就是按显示与逻辑分离、逻辑与输入分离的原则设计的。只要遵守好模块间接口约定后续加功能基本是搭积木式的工作量。最后再分享一点我做FPGA游戏的个人体会这类项目最忌一上来就打开编辑器写代码先花半天把需求拆成模块、把模块间的接口信号定义清楚比节省的半天编码时间要值得多。代码写完之后就算仿真全绿也先别高兴上板才是验证实战能力的开始。这三个坑——位宽错位、多驱动、时序竞争——哪一个在仿真里都很难提前暴露但真踩过一遍之后你对FPGA硬件到底怎么执行你的代码这个问题的理解会比你写十篇教程提升得都快。希望这篇复盘能帮你少走几个弯路真正把自己的双游戏系统从仿真一路跑到显示器上。本文还有配套的精品资源点击获取
返回列表