游戏体验
为了仿一个Flappy Bird,专门去4399玩了一下,总结了一下
- 小鸟:小鸟只在垂直方向移动,不点会飞快地往下掉
- 柱子:由远及近,每个柱子间通道位置随机,高度相同
- 操作: 只能点,点一下只往上跳一骨碌
- 死法:大致就是撞柱子会死,摔地上会死
- 可玩性:得分制,操作简单而不是趣味,越往后柱子跑的越快
游戏构想
- 小鸟:水平方向位置为常数确定,通过上下界变量确定实时位置
- 玩家按键:按一次,小鸟向上跳一下
- 小鸟移动:当有按键按下,小鸟上下界同时减少,并通过一个布尔值确保一次按键只跳一次;当无按键,小鸟自动下降,上下界同时增加
- 柱子:简化起见,只有一个柱子,通过左右界确定位置,当柱子移动到左边界令其复位到起始位置,并重新选取通道位置。
- 通道:使用一个时钟生成一定范围的随机数,通过上界确定通道位置,高度固定为150
- 难度递增:每次重新开始(复位)后,使用时钟计时,隔一定时间就增加一次柱子移动的速度
- 死亡检测:当小鸟与柱子的水平位置有重叠时(相遇),判断竖直方向是否有重叠(简化了,掉地上不会死了),没成功通过就加一分,须通过一布尔变量防止通过一个通道时重复加分
- 得分显示:在屏幕上显示数字点阵也挺麻烦的,干脆用上之前学的数码管好了
图片素材
- 小鸟
- 游戏结束
Verilog代码
- 顶层模块lab2.v
module lab2(input wire clk, rst,//时钟和复位
up,//按键,控制小鸟向上跳
output wire [2:0] r, g, output wire [1:0] b,//红蓝绿
output wire hs, vs, //VGA行、场扫描信号
output wire [6:0] a_to_g, //数码管数据信号
output wire [3:0] smgen //数码管位选信号
);
wire mclk;
wire ven;
wire [9:0] hc;
wire [9:0] vc;
wire [7:0] defen;
//分频
clkdiv clock(
.clk(clk),
.clr(rst),
.mclk(mclk)
);
//生成扫描信号
vgaSync syn(
.clk(mclk),
.rst(rst),
.hs(hs),
.vs(vs),
.videoen(ven),
.hc(hc),
.vc(vc)
);
//VGA显示
vgaRGB rgb(
.hc(hc),
.vc(vc),
.mclk(mclk),
.rst(rst),
.up(up),
.videoen(ven),
.r(r),
.g(g),
.b(b),
.defen(defen)
);
//得分显示
vgaDefenxianshi de(
.defen(defen),
.clk(clk),
.clr(rst),
.a_to_g(a_to_g),
.en(smgen)
);
endmodule
- 分频clkdiv.v(获得VGA扫描时钟频率)
module clkdiv(input wire clk, clr,
output wire mclk
);
reg [1:0]count = 0;
always @(posedge clk or posedge clr)
begin
if (clr)
count <= 0;
else
count <= count + 1'b1;
end
assign mclk = count[1];
endmodule
- 生成扫描信号vgaSync.v
module vgaSync(input wire clk, rst,
output reg hs, vs, videoen, output reg [9:0]hc, vc
);
reg vsenable;
always @ (posedge clk)
begin
if(rst == 1)
hc <= 0;
else
begin
if(hc == 10'd799)
begin
hc <= 0;
vsenable <= 1;
end
else
begin
hc <= hc + 1'b1;
vsenable <= 0;
end
end
end
always @ (*)
begin
if(hc < 10'd96)
hs = 0;
else
hs = 1;
end
always @ (posedge clk)
begin
if(rst == 1)
vc <= 0;
else
if(vsenable == 1)
begin
if(vc == 10'd520)
vc <= 0;
else
vc <= vc + 1'b1;
end
end
always @ (*)
begin
if(vc < 2)
vs = 0;
else
vs = 1;
end
always @ (*)
begin
if((hc < 10'd784) && (hc >= 10'd144) && (vc < 10'd511) && (vc >= 10'd31))
videoen = 1;
else
videoen = 0;
end
endmodule
- 游戏显示vgaRGB.v
module vgaRGB(input wire [9:0]hc, vc,
input wire mclk, rst, videoen,
up, //玩家按键,控制小鸟向上跳
output reg [2:0] r, g, output reg [1:0] b,
output reg [7:0] defen //游戏得分
);
//准备多个时钟
reg [31:0] clkdiv = 0;
wire dclk,sjclk,sclk,lrclk;
always @ (posedge mclk or posedge rst)
begin
if(rst)
clkdiv <= 0;
else
clkdiv <= clkdiv + 1'b1;
end
reg [9:0] ubird = 10'd100,dbird = 10'd131; //小鸟的上下界
//lbird = 10'd200, rbird = 10'd231
reg [9:0] ublock = 10'd50, lblock = 10'd500, rblock = 10'd550; //障碍,lblock、rblock为左右两边,ublock为通道上边,通道高150
assign dclk = clkdiv[20];//控制小鸟运动的时钟
reg firstup = 1;//控制一次按键只跳一次,防止按住按键一直向上跳
always @ (posedge dclk)
begin
if(rst == 1)
begin
ubird <= 10'd100;
dbird <= 10'd131;
end
else
begin
if(up == 1 && ubird > 31)//up botton pushed
begin
//一次按键只跳一次
if(firstup == 1)
begin
ubird <= ubird - 10'd50;
dbird <= dbird - 10'd50;//小鸟向上跳
firstup <= 0;
end
else if(dbird < 510)
begin
ubird <= ubird + 10'd10;
dbird <= dbird + 10'd10;//下落
end
end
else if(dbird < 510)
//不按键时会自动下落
begin
ubird <= ubird + 10'd10;
dbird <= dbird + 10'd10;
firstup <= 1;
end
end
end
//35 - 360的随机数,使通道的位置每次不同
assign sjclk = clkdiv[15]; //用于生成随机数的时钟
reg [9:0] usuiji = 35;
always @(posedge sjclk)
begin
if(rst)
usuiji <= 35;
else
begin
usuiji <= usuiji + 1'b1;
if(usuiji == 360)
usuiji <= 35;
end
end
//障碍移动速度会慢慢变快,加大难度(3~7)
assign sclk = clkdiv[29]; //控制难度随时间递增的时钟
reg [3:0] sudu = 3;
always @(posedge sclk)
begin
if(rst)
sudu <= 3;
else
begin
if(sudu < 7)
sudu <= sudu + 1'b1;
end
end
//障碍移动
assign lrclk = clkdiv[20]; //控制障碍移动的时钟
always @ (posedge lrclk or posedge rst)
begin
if(rst)
begin
lblock <= 10'd500;
rblock <= 10'd550;
ublock <= 10'd50;
end
else
begin
lblock <= lblock - sudu;//一次移动多少由sudu决定
rblock <= rblock - sudu;
if(lblock < 145)//到左边界复位,使障碍永不停息
begin
lblock <= 10'd500;
rblock <= 10'd550;
ublock <= usuiji; //通道的上边赋为随机数
end
end
end
reg success = 1;//游戏是否结束
reg first = 1;//防止通过一次阻碍时重复加分
reg [7:0] fenshu = 0;//分数
always @ (posedge dclk or posedge rst)
begin
if(rst)
begin
success <= 1;
defen <= 0;
first <= 1;
end
else if(lblock < 231 && rblock > 200)//鸟与障碍相遇
begin
if(ubird < ublock || dbird > ublock + 150)//碰撞
begin
success <= 0;
end
else
if(first == 1)
begin
first <= 0;
defen <= defen + 1'b1;//从通道通过,加1分
end
end
else
first <= 1;
end
//bird的rom
reg [9:0] addrbird = 0;
wire [7:0] databird;
ip Rom( .clka(mclk), .addra(addrbird), .douta(databird) );
//gameover图片的rom
reg [15:0] addrover = 0;
wire [7:0] dataover;
over over( .clka(mclk), .addra(addrover), .douta(dataover) );
always @ (posedge mclk)
begin
if(videoen == 1)
begin
//游戏失败
if(success == 0)
begin
//gameover图片显示 300*200
if(vc < 351 && vc > 150 && hc < 601 && hc > 300)
begin
addrover <= (vc - 150 - 1) * 300 + (hc - 300) - 1;
r <= dataover[7:5];
g <= dataover[4:2];
b <= dataover[1:0];
end
else
begin
r <= 0;
g <= 0;
b <= 0;
end
end
else
begin
//小鸟显示
if(vc < dbird && vc > ubird && hc < 231 && hc > 200)
begin
addrbird <= (vc - ubird - 1) * 30 + (hc - 200) - 1;
r <= databird[7:5];
g <= databird[4:2];
b <= databird[1:0];
end
//障碍显示
else if(hc < rblock && hc > lblock && (vc > ublock + 150 || vc < ublock))
begin
r <= 3'b000;
g <= 3'b111;
b <= 2'b00;
end
else
begin
//背景颜色
r <= 3'b000;
g <= 3'b100;
b <= 2'b10;
end
end
end
else
begin
r <= 3'b0;
g <= 3'b0;
b <= 2'b0;
end
end
endmodule
- 得分显示vgaDefenxianshi.v
(别问我为什么得分是16进制)
module vgaDefenxianshi(
input wire [7:0] defen, //得分
input wire clk, input wire clr,
output reg [6:0] a_to_g, output reg [3:0] en //数据及位选
);
wire a;
reg [3:0] digit;
reg [19:0] clkdiv;
assign a = clkdiv[19];
always @ (*)
case (a)
//表示为两位16进制数
0 : digit = defen[3:0];
1 : digit = defen[7:4];
endcase
always @ (*)
case (digit)
0: a_to_g = 7'b000_0001;
1: a_to_g = 7'b100_1111;
2: a_to_g = 7'b001_0010;
3: a_to_g = 7'b000_0110;
4: a_to_g = 7'b100_1100;
5: a_to_g = 7'b010_0100;
6: a_to_g = 7'b010_0000;
7: a_to_g = 7'b000_1111;
8: a_to_g = 7'b000_0000;
9: a_to_g = 7'b000_0100;
10: a_to_g = 7'b000_1000;
11: a_to_g = 7'b110_0000;
12: a_to_g = 7'b011_0001;
13: a_to_g = 7'b100_0010;
14: a_to_g = 7'b011_0000;
15: a_to_g = 7'b011_1000;
endcase
always @ (*)
begin
if(clr)
en = 4'b1111;
else
begin
en = 4'b1111;
en[a] = 0;
end
end
always @ (posedge clk or posedge clr)
begin
if (clr == 1)
clkdiv <= 0;
else
clkdiv <= clkdiv + 1'b1;
end
endmodule