☰
FPGA弹球小游戏实战:VGA时序、按键消抖与碰撞检测全解析
2026/10/15 12:55:55 网站建设 项目流程

简介:面向FPGA初学者与数字逻辑设计爱好者,这份压缩包提供一套按键控制弹球小游戏的完整工程,设计将VGA显示信号送到显示器,玩家通过按键控制挡板反弹小球,覆盖VGA时序驱动、按键消抖、状态控制、分频等典型知识点,适合课程设计或入门练手。包内共115个文件,以源码、工程文件及编译产物为主,兼具引脚约束与编译报告,整体仅3.79MB,目录结构保留原始项目布局,便于打开修改并重新综合;已有1232人学习下载。解压后可查看核心逻辑模块,也可利用生成的配置文件在开发板上直接验证显示效果。编译报告和数据库文件还能帮助排查时序约束、引脚分配与资源占用问题,对梳理FPGA从编码、综合到下载运行的完整流程很有参考价值。

1. FPGA弹球小游戏:按键控制、VGA直出,从原理到板载调试

在FPGA开发板上用按键控制一个弹球小游戏,把VGA显示信号直接送到显示器——这个工程听起来不大,真正动手才发现它把时序设计、状态机、消抖处理和像素绘制全串在了一起。刚接触时容易把它当成“画方块加位移判断”,但调通之后回看,VGA黑屏、按键失灵、球穿墙,每个坑都有明确的排查路径。这份资源适合刚学完Verilog语法、想用一个完整工程巩固时序概念的新手,也适合课程设计前先跑通整个流程的同学。跟着下面的章节走,先理清VGA和输入原理,再逐模块写代码,最后上板验证,一小时内就能让球在屏幕上动起来。

2. VGA与输入基础:时序参数、按键消抖与绘制模型

2.1 VGA时序参数:640x480@60Hz的行场同步怎么算

VGA输出的本质是让HSYNC、VSYNC两根同步线按固定节拍工作,同时把每个像素的RGB值按顺序送到数据引脚上。常见做法是跑640x480@60Hz,像素时钟约25.175MHz,很多开发板上的50MHz晶振刚好二分频得到25MHz,显示效果差异几乎看不出来,我一般就直接用25MHz省掉PLL。

25MHz下扫描一行总共要经历800个像素周期,其中行同步脉冲占96周期、后沿48周期、有效数据640周期、前沿16周期。一帧总共525行,其中场同步脉冲占2行、后沿33行、有效行480行、前沿10行。把这几个数记住,VGA驱动就完成了一大半。hsync和vsync都是负极性,也就是空闲时高电平、同步时拉低。参数汇成一张表放在下面,调试时对照它核数据。

参数行方向场方向
总周期800像素525行
同步脉冲962
后沿4833
有效区域640480
前沿1610
极性负负

行方向差一个周期,大部分显示器能容忍;场方向的参数差超过一行,很多显示器会直接提示“不支持信号”。所以写VGA驱动时一定要按表里的数值,别为了“整数好看”把后沿改成32或改成34。实际刷新率是25MHz除以800再除以525,约等于59.52Hz,和60Hz几乎重合,扫描时序正确画面就稳定。如果板载晶振不是50MHz,或者想要更高的分辨率,就必须用PLL生成精确的像素时钟,否则行场参数怎么调都会闪。

2.2 按键消抖:机械抖动、去抖计时器与电平沿检测

按键是机械触点,按下和松开的瞬间会有几毫秒到十几毫秒的抖动,电平在0和1之间来回跳。FPGA采样频率是百万级赫兹,一次按下会被当成十几次翻转,反映到游戏里就是球或挡板突然抖一下。常规做法是20ms去抖:检测到电平变化后连续计时20ms,只有信号稳定持续那么久才认为是一次有效动作。

去抖模块我习惯写成“同步—比较—计时”三步。先用两级触发器把按键信号同步到时钟域,消除亚稳态;再把同步后的信号和上一拍信号比较,不一致就说明正在变化,把计数器清零;只有当计数器连续计数满20ms才输出稳定电平。下面这段是资源里去抖模块的核心部分。

module debounce #( parameter CNT_MAX = 20_000_000 // 20ms @ 50MHz )( input clk, rst_n, input btn_in, output reg btn_out ); reg [24:0] cnt; reg btn_syn1, btn_syn2, btn_syn3; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin btn_syn1 <= 1'b1; btn_syn2 <= 1'b1; btn_syn3 <= 1'b1; btn_out <= 1'b1; cnt <= 0; end else begin btn_syn1 <= btn_in; btn_syn2 <= btn_syn1; btn_syn3 <= btn_syn2; if (btn_syn2 != btn_syn3) begin cnt <= 0; end else if (cnt < CNT_MAX) begin cnt <= cnt + 1'b1; end else begin btn_out <= btn_syn2; end end end endmodule

逻辑说明:btn_syn1和btn_syn2用于同步输入信号,btn_syn3保存上一拍值。每当btn_syn2和btn_syn3不一致,说明按键电平还在跳变,计数器清零重新计时。只有信号连续稳定20ms,btn_out才会更新成当前电平。CNT_MAX对应20ms的前提是输入时钟50MHz,也就是20_000_000个周期;如果你的工程用的是25MHz像素时钟来跑这个模块,一定要把参数改成5_000_000,否则去抖时间会翻倍到40ms,操作手感明显发黏。

去抖之后,游戏逻辑里还有一个细节要分清:挡板移动用的是电平,按住不动就持续移动;而计分、菜单这类功能用的是边沿,一次按键只触发一次动作。边沿信号可以从btn_out的下降沿获取,也可以用两级寄存器再取异或。很多初学工程在挡板控制上直接拿原始按键电平去做,结果一次按下导致挡板连续跳动好几格,这就是没分清电平与沿的差别。

2.3 绘制模型:球场边界、挡板和弹球的坐标关系

游戏画面全部画在640x480的有效区域内。为了好看,我会在有效区域再留一圈“隐形墙”,也就是逻辑上限定球的x范围在0到639、y范围在0到479,但绘制时默认边界附近几像素是墙的颜色。这样球碰到边缘之前就反弹,看起来更像真实的球桌,而不是球直接消失在屏幕外。

挡板是一个宽80像素、高10像素的矩形,放在y等于450附近。它的x坐标由左右按键控制,取值范围限制在0到559,也就是640减去挡板宽度80。如果不管这个钳位,挡板快速移动时一半会跑到屏幕外面。弹球本身是一个边长12到16像素的方块,为了视觉上更圆润,可以把它画成两个颜色叠出来,但核心碰撞逻辑按方块来算就行。

碰撞检测用AABB方式,也就是比较两边的x区间和y区间是否重叠。球的x加上半宽大于等于挡板的x,且球的x小于等于挡板右边,同时球的y位于挡板的y范围附近,就判定反弹。上边界和左右边界同理,翻转对应方向的速度符号。资源里开放的球速度是vx等于2、vy等于1,单位是像素每帧,帧率约60Hz,也就是每秒水平120像素、垂直60像素,跑起来节奏适中。想让游戏更刺激就把vx和vy调成4和2,但要注意,速度越快碰撞穿透风险越高,后面避坑章节会专门讲这个问题。

3. 逐模块实现:时钟分频、VGA驱动、弹球逻辑与挡板控制

3.1 顶层模块:信号定义与模块例化

顶层负责把时钟、复位、左右按键和VGA各引脚接起来。资源里的顶层端口如下,RGB各3位是很多入门板的标配,总共能显示26万色;如果你的板子只有1位或8位色深,改一下位宽直接接过去就行,游戏逻辑不需要动。

module top( input clk_50m, input rst_n, input btn_left, input btn_right, output hsync, output vsync, output [2:0] vga_r, output [2:0] vga_g, output [2:0] vga_b ); wire clk_25m; wire [9:0] px_x, px_y; wire active; wire btn_left_db, btn_right_db; wire [8:0] rgb; clk_div u_clk( .clk_in (clk_50m), .clk_out(clk_25m) ); debounce #(.CNT_MAX(5_000_000)) u_db_left( .clk(clk_25m), .rst_n(rst_n), .btn_in(btn_left), .btn_out(btn_left_db) ); debounce #(.CNT_MAX(5_000_000)) u_db_right( .clk(clk_25m), .rst_n(rst_n), .btn_in(btn_right), .btn_out(btn_right_db) ); vga_ctrl u_vga( .clk(clk_25m), .rst_n(rst_n), .hsync(hsync), .vsync(vsync), .px_x(px_x), .px_y(px_y), .active(active) ); game_logic u_game( .clk(clk_25m), .rst_n(rst_n), .btn_left(btn_left_db), .btn_right(btn_right_db), .px_x(px_x), .px_y(px_y), .active(active), .rgb(rgb) ); assign vga_r = rgb[8:6]; assign vga_g = rgb[5:3]; assign vga_b = rgb[2:0]; endmodule

这里有个值得注意的地方:去抖模块和VGA驱动都挂在clk_25m下。按键消抖不需要50MHz那么高的采样率,25MHz完全够用,而且所有模块在同一时钟域里,不会出现跨时钟域比较的麻烦。如果板载时钟是50MHz而VGA要求25MHz,最简单的方式是在顶层例化一个二分频模块,而不是在VGA驱动内部直接数两个周期,那样会让VGA驱动的计数值每一步都翻倍,容易把自己绕晕。

hsync和vsync从VGA驱动直接输出到引脚,RGB则走游戏逻辑合成后再拼成9位。把显示时序和游戏逻辑拆成两个模块,是我觉得这个资源做得最舒服的地方。调试时可以先让VGA驱动单独输出纯色测试信号,排除时序问题再处理游戏逻辑,比一上来就调整个系统省力得多。

3.2 VGA驱动与像素坐标:25MHz下的行场计数器

VGA驱动模块的核心是两个计数器:水平计数器cnt_h在0到799之间循环,垂直计数器cnt_v在0到524之间循环。水平计数器每走完一整行,垂直计数器加1。两个计数器的值决定hsync、vsync什么时候拉低,同时可以算出当前像素坐标。

module vga_ctrl ( input clk, // 25MHz input rst_n, output reg hsync, vsync, output reg [9:0] px_x, px_y, output reg active ); localparam H_TOTAL = 800, H_SYNC = 96, H_BACK = 48; localparam V_TOTAL = 525, V_SYNC = 2, V_BACK = 33; reg [9:0] cnt_h, cnt_v; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_h <= 0; cnt_v <= 0; end else begin cnt_h <= (cnt_h == H_TOTAL - 1) ? 0 : cnt_h + 1'b1; if (cnt_h == H_TOTAL - 1) begin cnt_v <= (cnt_v == V_TOTAL - 1) ? 0 : cnt_v + 1'b1; end end end always @* begin px_x = cnt_h - H_SYNC - H_BACK; px_y = cnt_v - V_SYNC - V_BACK; active = (px_x >= 0 && px_x < 640 && px_y >= 0 && px_y < 480); hsync = (cnt_h < H_SYNC) ? 1'b0 : 1'b1; vsync = (cnt_v < V_SYNC) ? 1'b0 : 1'b1; end endmodule

这段代码的坐标计算逻辑是:当cnt_h处于行同步和后沿之后,px_x从0开始递增。cnt_h等于143时px_x等于0,也就是说有效像素的第一个点从水平方向第144个时钟周期开始送出。cnt_h从783再往后走到799时,px_x已经超过639,active拉低,这段二十几个周期就是行前沿,用来给显示器一点准备时间。同理,px_y在cnt_v等于35时变成0,有效行从第36行开始,到第515行结束,剩下10行是场前沿。每帧最后送出一段没有画面的前沿,显示器才能稳定地回到下一帧起点。

需要留意的是px_x和px_y在没有有效数据时可能算出来是负数,比如cnt_h小于144时px_x为负,所以active必须同时判断坐标落在0到640和0到480范围内。游戏逻辑里绘制颜色时,第一步永远先判断active,这一步漏掉,屏幕边框上会出现杂色。

3.3 弹球运动与碰撞检测:vx/vy刷新与边界反弹

游戏逻辑按帧更新,每一帧在active输出从0变为1时触发一次,也就是说每场画面开始扫描时刷新一次球的位置和挡板位置。这样做的原因是:如果在任意像素时钟沿都更新位置,球会在同一帧内被画到不同位置,看起来就是撕裂的残影。帧同步更新是所有类似游戏工程的通用做法。

localparam BALL_SIZE = 12; localparam PADDLE_W = 80; localparam PADDLE_H = 10; localparam PADDLE_Y = 450; reg signed [3:0] vx, vy; reg [9:0] ball_x, ball_y; reg [9:0] paddle_x; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin ball_x <= 320; ball_y <= 240; vx <= 2; vy <= 1; paddle_x <= 280; end else if (frame_start) begin // 挡板移动与边界钳位 if (btn_left == 1'b0 && paddle_x > 0) paddle_x <= paddle_x - 3; if (btn_right == 1'b0 && paddle_x < 640 - PADDLE_W) paddle_x <= paddle_x + 3; // 更新球位置 ball_x <= ball_x + vx; ball_y <= ball_y + vy; // 左右边界反弹 if (ball_x + BALL_SIZE >= 640) vx <= -vx; if (ball_x <= 0) vx <= -vx; // 上边界反弹 if (ball_y <= 0) vy <= -vy; // 挡板碰撞:下一帧位置穿过挡板区间也判为碰撞 if (ball_y + BALL_SIZE >= PADDLE_Y - vy && ball_y + BALL_SIZE <= PADDLE_Y + PADDLE_H - vy && ball_x + BALL_SIZE >= paddle_x && ball_x <= paddle_x + PADDLE_W) vy <= -vy; // 掉出底部则重置 if (ball_y >= 480) begin ball_x <= 320; ball_y <= 240; vx <= 2; vy <= 1; end end end

位置更新和速度翻转写在同一个always块里,用的是非阻塞赋值,这是关键。综合工具会先把右边的旧值全部取出来,再统一赋给左边,所以同一时刻ball_x加的是旧vx而不是刚翻转的新vx。如果把赋值符号断成多个块或者混用阻塞赋值,高速运动时会出现球撞墙后还在往里走一两帧的现象。

挡板碰撞条件里加了一个“穿越区间”的判断:如果球的底部在移动前已经位于挡板上一帧的位置到挡板底边之间,即使这一帧移动后越过了整个挡板区域,也会被识别为碰撞。这个技巧在vx或vy等于2以上时非常有用,否则球速提高到一定值后,球会直接穿过挡板落到屏幕底部,看起来就是穿墙。

3.4 绘制优先级:背景、墙壁、挡板与球的输出顺序

绘制部分用组合逻辑根据当前像素坐标判断该显示成什么颜色。优先级从低到高依次是:背景、墙壁、挡板、球。低优先级写在if-else链的前面,满足高优先级条件时后者的分支直接输出颜色。

reg [8:0] rgb; always @* begin if (!active) rgb = 9'b000000000; else if (draw_wall) rgb = 9'b111000000; else if (draw_paddle) rgb = 9'b000111000; else if (draw_ball) rgb = 9'b111111111; else rgb = 9'b000010000; end

draw_wall、draw_paddle、draw_ball这三个信号由坐标比较直接生成,例如draw_paddle就是当前px_x落在paddle_x到paddle_x+79范围、同时px_y落在450到459范围。它们和rgb的赋值都在同一个组合always块里,逻辑简单直接。有人喜欢用多级if嵌套去处理“球和挡板重叠时谁画在上面”,其实只要把优先级写成上面的顺序,重叠区域自然就是球挡住挡板,不需要额外判断。

RGB输出按3位红、3位绿、3位蓝拼接,9位颜色值在active为低时输出全零,保证消隐区不带任何颜色。如果RGB引脚走的是电阻网络,偏色基本来自电阻阻值匹配;如果输出是全数字信号接转接板,则需要确认转接板协议兼容。后面第5章会讲到怎么用纯色信号排查这条链路。

4. 避坑与常见问题排查:VGA黑屏、按键失灵、弹球穿墙

4.1 VGA黑屏或显示器提示不支持信号

现象:上电后显示器黑屏,或者显示“超出工作频率范围”“不支持信号”。

原因集中在两处:一是像素时钟和行场参数不匹配。比如板载晶振不是规划的50MHz,而VGA驱动代码用默认的25MHz去数,实际行频和场频偏得离谱,显示器直接拒绝显示。二是工程里使用了PLL但PLL输出频率没做约束,导致像素时钟偏离标准值,显示器能收到信号但画面滚动。

解决:先确认系统时钟频率,用二分频或PLL得到标准25MHz,然后对照第2章的表格核对H_TOTAL和V_TOTAL参数。验证方法是用一时钟周期计数器查看PC端示波器测得的hsync频率,应接近31.47kHz;vsync频率接近59.52Hz。如果偏差超过1%,优先检查时钟分频,而不是继续调后沿参数。RGB通道的偏色问题优先级更低,建议用纯色画面先排除显示链路。

4.2 按键按下无效或一次按下触发多次

现象:按住左键挡板不动,或者按一下动了两三格,甚至挡板自己往一个方向漂。

原因:按键信号没有做同步直接进了游戏逻辑,采样时偶尔采到亚稳态。更常见的是去抖模块的CNT_MAX没有跟着模块时钟改动,比如把整个工程换成25MHz时钟,去抖参数还在用50MHz对应的20_000_000,实际去抖时间变成40ms甚至更久,按键响应慢到像失灵。至于“按一下动两下”,基本是把电平信号当成沿信号用了,挡板应该只在检测到下降沿时移动一次。

解决:先给每个按键加两级同步寄存器和20ms去抖,再用LED做按键状态回显,按下亮、松开灭,单独验证按键链路。确认无误后再接到游戏逻辑。挡板如果要做“按一下走一步”的效果,不要拿btn_out电平直接判断,要用下降沿或者边沿检测后的单周期脉冲。

4.3 弹球穿墙、飞出屏幕或速度越来越快

现象:球到达边界后消失,或者反弹后反而加速,最后变成一道闪烁的光线。

原因:速度符号翻转的寄存器没声明成signed。比如vx是reg [3:0],赋值为4'b1110代表-2,寄存器本身不知道符号,综合出来后把它当成正数14,球每次向一个方向加14像素,自然是飞出去。另一个原因是位置更新和速度翻转用了多个always块且混用阻塞赋值,导致同一个时钟沿内先更新速度再更新位置,球的位置用了新速度。

解决:把vx和vy声明成signed,位宽至少要比“最大单帧位移”多一位符号位。资源里vx最大设为4,用signed [3:0]足够。同时保证每个时钟周期内,先计算新位置,再更新速度,顺序要固定。穿墙问题用第3章的穿越区间检测兜底,这样即使球速到了4像素/帧,也不会穿过挡板。

4.4 挡板移动卡顿或画面抖动

现象:球运动正常,但挡板一顿一顿,或者画面边缘出现水平滚动。

原因:挡板和球的帧同步信号不是同一个。如果你把挡板逻辑挂在另一个计数器的比较条件上,挡板更新频率和球更新频率不一致,就会看到挡板“一段一段”地走。水平滚动更多是VGA参数问题,比如H_TOTAL误写成799,或者后沿数值不对,导致行同步周期和标准值差一个像素时钟。

解决:确保游戏逻辑里所有帧更新条件都使用唯一的frame_start脉冲,这个脉冲最好从VGA驱动的active上升沿生成。参数上把H_TOTAL锁死为800,别手滑改成799或801。垂直方向如果底部或顶部有多余线条,检查V_BACK是不是写成了32或34。调整后重新综合烧录,一次改一个变量,别同时动两处参数。

5. 复现与验证:从建立工程到板载测试的完整流程

5.1 建立工程与引脚约束

拿到这份资源后,先建立空工程,把顶层、VGA驱动、去抖、游戏逻辑四个文件依次添加进去。语言选Verilog-2001,综合工具按默认配置即可。如果工程里用了PLL,必须在约束里写时钟频率,比如create_clock指定输入时钟为50MHz,否则布线工具按照无约束模式跑,关键路径可能不满足时序要求,具体表现就是“偶尔黑屏一下”。

引脚约束是整个复现过程最容易翻车的地方。VGA的hsync、vsync、RGB三组引脚要按板上丝印逐一分配,按键引脚同理。样式大致是下面这样,实际引脚号要根据开发板原理图改。

set_property PACKAGE_PIN P17 [get_ports clk_50m] set_property PACKAGE_PIN L17 [get_ports rst_n] set_property PACKAGE_PIN R16 [get_ports btn_left] set_property PACKAGE_PIN N16 [get_ports btn_right] set_property PACKAGE_PIN R2 [get_ports hsync] set_property PACKAGE_PIN R1 [get_ports vsync] set_property PACKAGE_PIN T3 [get_ports {vga_r[2]}] set_property PACKAGE_PIN T4 [get_ports {vga_r[1]}] set_property PACKAGE_PIN T5 [get_ports {vga_r[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {clk_50m rst_n btn_left btn_right hsync vsync vga_r[*]}]

约束时注意三点:一是RGB三个位要分开写,不能只写一个端口名;二是hsync和vsync的极性代码里已经处理,约束里不需要再加;三是如果板子RGB引脚不到9个,需要在顶层把端口位宽调整成实际位数,并在约束里一一对应。这部分建议抄完引脚后逐个用LED测试验证,别一次性全写完再烧录。

5.2 综合烧录与板载验证的完整步骤

复现流程按四步走,每步都有明确检查点,能省很多时间。

第一步,综合。先跑综合看语法错误、未定义信号和意外锁存器。常见的警告是“signal assigned but never used”,多数不影响功能但会占用额外寄存器,建议顺手清除。第二步,实现。布线结束后查看时序报告,重点看最小时钟周期是否满足25MHz。如果fmax只有二十几兆,说明关键路径太深,把碰撞判断拆成两级寄存器,或者把组合逻辑里的比较条件简化,基本能解决。第三步,生成比特流并烧录。烧录后先复位,正常情况下显示器进入游戏画面。第四步,功能测试,先测按键单独响应,再测球反弹,最后测球掉落重置。

我习惯把功能测试分成静态和动态两轮。静态测试是把球速度临时设成0,只显示挡板和球的初始位置,确认分子绘制和位置正确,这个时候看不到任何运动逻辑,问题好定位。动态测试再放开速度,观察球是否在撞墙后按预期方向反弹。两轮都通过后,这个小游戏工程就算真正跑通了。

如果显示器只有HDMI接口而不带VGA,接一个转接头后可能出现画面右移或裁边,这不是FPGA逻辑的问题,而是转接芯片对640x480x60Hz支持不完整。换一台带原生VGA的显示器测试,通常就正常了。要从根上解决,可以把分辨率改成800x600x60Hz重新计算行场参数,但像素时钟要升到40MHz,工程需要重新做PLL配置。

6. 进阶玩法:动态难度、计分、多球与工程化习惯

基础版跑通之后,可以沿着三个方向扩展,提升都很直接。

第一是计分和难度递增。在画面上显示分数,最省像素的做法是字模表:把0到9按3x5点阵编码,扫描到对应区域时按bit输出颜色。挡板每接住一次球,让vy的绝对值在1到5之间递增,球的反弹速度逐步变快。注意增加上限,否则速度超过每帧5像素时,即便有穿越区间检测,球也容易从挡板侧边绕过判定区域。

第二是多球和道具。把球的坐标和速度从普通寄存器改成数组,用多个寄存器组分别保存每颗球的状态。道具掉落可以用线性反馈移位寄存器生成伪随机位置,占用的逻辑单元很少,比真随机数电路划算得多。这个扩展的关键约束是:更新逻辑和绘制逻辑必须严格分离。绘制只能在active期间读当前状态,更新只能在frame_start时写状态,否则多个球会互相覆盖状态,画面出现闪变。

第三是参数化工程结构。把分辨率参数全部提到文件头部,我在自己的工程里长这样:

localparam H_ACTIVE = 640, H_SYNC = 96, H_BACK = 48, H_TOTAL = 800; localparam V_ACTIVE = 480, V_SYNC = 2, V_BACK = 33, V_TOTAL = 525;

以后要换分辨率,只需要改这几行和像素时钟,游戏逻辑本身完全不用动。这个习惯让我后来做图像处理demo时省了很多事,改分辨率不再需要重写VGA驱动。

最后说一个我从这个工程里带出来的血泪习惯:每次上板前,先用纯色测试信号验证VGA链路,再加载游戏逻辑。有一次我调PLL时参数写错,输出像素时钟变成24.3MHz,显示器居然能亮,但画面右侧出现1厘米黑边,hsync频率也差了一截。当时我以为是后沿参数问题,花了一晚上调整后沿和前沿,最后才意识到时钟频率就不对。从那以后,我每次上板都强制走一遍完整的验证顺序:纯色通道检查、静态绘制检查、动态运动检查,每步确认没问题再往下走。这套顺序救了我很多次,也希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询