遊戲體驗
為了仿一個Flappy Bird,專門去4399玩了一下,總結了一下
- 小鳥:小鳥隻在垂直方向移動,不點會飛快地往下掉
- 柱子:由遠及近,每個柱子間通道位置随機,高度相同
- 操作: 隻能點,點一下隻往上跳一骨碌
- 死法:大緻就是撞柱子會死,摔地上會死
- 可玩性:得分制,操作簡單而不是趣味,越往後柱子跑的越快
遊戲構想
- 小鳥:水準方向位置為常數确定,通過上下界變量确定實時位置
- 玩家按鍵:按一次,小鳥向上跳一下
- 小鳥移動:當有按鍵按下,小鳥上下界同時減少,并通過一個布爾值確定一次按鍵隻跳一次;當無按鍵,小鳥自動下降,上下界同時增加
- 柱子:簡化起見,隻有一個柱子,通過左右界确定位置,當柱子移動到左邊界令其複位到起始位置,并重新選取通道位置。
- 通道:使用一個時鐘生成一定範圍的随機數,通過上界确定通道位置,高度固定為150
- 難度遞增:每次重新開始(複位)後,使用時鐘計時,隔一定時間就增加一次柱子移動的速度
- 死亡檢測:當小鳥與柱子的水準位置有重疊時(相遇),判斷豎直方向是否有重疊(簡化了,掉地上不會死了),沒成功通過就加一分,須通過一布爾變量防止通過一個通道時重複加分
- 得分顯示:在螢幕上顯示數字點陣也挺麻煩的,幹脆用上之前學的數位管好了
圖檔素材
- 小鳥
- 遊戲結束
Verilog代碼
- 頂層子產品lab2.v
module lab2(input wire clk, rst,//時鐘和複位
up,//按鍵,控制小鳥向上跳
output wire [:] r, g, output wire [:] b,//紅藍綠
output wire hs, vs, //VGA行、場掃描信号
output wire [:] a_to_g, //數位管資料信号
output wire [:] 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 [:]count = ;
always @(posedge clk or posedge clr)
begin
if (clr)
count <= ;
else
count <= count + 'b1;
end
assign mclk = count[];
endmodule
- 生成掃描信号vgaSync.v
module vgaSync(input wire clk, rst,
output reg hs, vs, videoen, output reg [:]hc, vc
);
reg vsenable;
always @ (posedge clk)
begin
if(rst == )
hc <= ;
else
begin
if(hc == 'd799)
begin
hc <= ;
vsenable <= ;
end
else
begin
hc <= hc + 'b1;
vsenable <= ;
end
end
end
always @ (*)
begin
if(hc < 'd96)
hs = ;
else
hs = ;
end
always @ (posedge clk)
begin
if(rst == )
vc <= ;
else
if(vsenable == )
begin
if(vc == 'd520)
vc <= ;
else
vc <= vc + 'b1;
end
end
always @ (*)
begin
if(vc < )
vs = ;
else
vs = ;
end
always @ (*)
begin
if((hc < 'd784) && (hc >= 'd144) && (vc < 'd511) && (vc >= 'd31))
videoen = ;
else
videoen = ;
end
endmodule
- 遊戲顯示vgaRGB.v
module vgaRGB(input wire [:]hc, vc,
input wire mclk, rst, videoen,
up, //玩家按鍵,控制小鳥向上跳
output reg [:] r, g, output reg [:] b,
output reg [:] defen //遊戲得分
);
//準備多個時鐘
reg [:] clkdiv = ;
wire dclk,sjclk,sclk,lrclk;
always @ (posedge mclk or posedge rst)
begin
if(rst)
clkdiv <= ;
else
clkdiv <= clkdiv + 'b1;
end
reg [:] ubird = 'd100,dbird = 'd131; //小鳥的上下界
//lbird = 10'd200, rbird = 10'd231
reg [:] ublock = 'd50, lblock = 'd500, rblock = 'd550; //障礙,lblock、rblock為左右兩邊,ublock為通道上邊,通道高150
assign dclk = clkdiv[];//控制小鳥運動的時鐘
reg firstup = ;//控制一次按鍵隻跳一次,防止按住按鍵一直向上跳
always @ (posedge dclk)
begin
if(rst == )
begin
ubird <= 'd100;
dbird <= 'd131;
end
else
begin
if(up == && ubird > )//up botton pushed
begin
//一次按鍵隻跳一次
if(firstup == )
begin
ubird <= ubird - 'd50;
dbird <= dbird - 'd50;//小鳥向上跳
firstup <= ;
end
else if(dbird < )
begin
ubird <= ubird + 'd10;
dbird <= dbird + 'd10;//下落
end
end
else if(dbird < )
//不按鍵時會自動下落
begin
ubird <= ubird + 'd10;
dbird <= dbird + 'd10;
firstup <= ;
end
end
end
//35 - 360的随機數,使通道的位置每次不同
assign sjclk = clkdiv[]; //用于生成随機數的時鐘
reg [:] usuiji = ;
always @(posedge sjclk)
begin
if(rst)
usuiji <= ;
else
begin
usuiji <= usuiji + 'b1;
if(usuiji == )
usuiji <= ;
end
end
//障礙移動速度會慢慢變快,加大難度(3~7)
assign sclk = clkdiv[]; //控制難度随時間遞增的時鐘
reg [:] sudu = ;
always @(posedge sclk)
begin
if(rst)
sudu <= ;
else
begin
if(sudu < )
sudu <= sudu + 'b1;
end
end
//障礙移動
assign lrclk = clkdiv[]; //控制障礙移動的時鐘
always @ (posedge lrclk or posedge rst)
begin
if(rst)
begin
lblock <= 'd500;
rblock <= 'd550;
ublock <= 'd50;
end
else
begin
lblock <= lblock - sudu;//一次移動多少由sudu決定
rblock <= rblock - sudu;
if(lblock < )//到左邊界複位,使障礙永不停息
begin
lblock <= 'd500;
rblock <= 'd550;
ublock <= usuiji; //通道的上邊賦為随機數
end
end
end
reg success = ;//遊戲是否結束
reg first = ;//防止通過一次阻礙時重複加分
reg [:] fenshu = ;//分數
always @ (posedge dclk or posedge rst)
begin
if(rst)
begin
success <= ;
defen <= ;
first <= ;
end
else if(lblock < && rblock > )//鳥與障礙相遇
begin
if(ubird < ublock || dbird > ublock + )//碰撞
begin
success <= ;
end
else
if(first == )
begin
first <= ;
defen <= defen + 'b1;//從通道通過,加1分
end
end
else
first <= ;
end
//bird的rom
reg [:] addrbird = ;
wire [:] databird;
ip Rom( .clka(mclk), .addra(addrbird), .douta(databird) );
//gameover圖檔的rom
reg [:] addrover = ;
wire [:] dataover;
over over( .clka(mclk), .addra(addrover), .douta(dataover) );
always @ (posedge mclk)
begin
if(videoen == )
begin
//遊戲失敗
if(success == )
begin
//gameover圖檔顯示 300*200
if(vc < && vc > && hc < && hc > )
begin
addrover <= (vc - - ) * + (hc - ) - ;
r <= dataover[:];
g <= dataover[:];
b <= dataover[:];
end
else
begin
r <= ;
g <= ;
b <= ;
end
end
else
begin
//小鳥顯示
if(vc < dbird && vc > ubird && hc < && hc > )
begin
addrbird <= (vc - ubird - ) * + (hc - ) - ;
r <= databird[:];
g <= databird[:];
b <= databird[:];
end
//障礙顯示
else if(hc < rblock && hc > lblock && (vc > ublock + || vc < ublock))
begin
r <= 'b000;
g <= 'b111;
b <= 'b00;
end
else
begin
//背景顔色
r <= 'b000;
g <= 'b100;
b <= 'b10;
end
end
end
else
begin
r <= 'b0;
g <= 'b0;
b <= 'b0;
end
end
endmodule
-
得分顯示vgaDefenxianshi.v
(别問我為什麼得分是16進制)
module vgaDefenxianshi(
input wire [:] defen, //得分
input wire clk, input wire clr,
output reg [:] a_to_g, output reg [:] en //資料及位選
);
wire a;
reg [:] digit;
reg [:] clkdiv;
assign a = clkdiv[];
always @ (*)
case (a)
//表示為兩位進制數
: digit = defen[:];
: digit = defen[:];
endcase
always @ (*)
case (digit)
: a_to_g = 'b000_0001;
: a_to_g = 'b100_1111;
: a_to_g = 'b001_0010;
: a_to_g = 'b000_0110;
: a_to_g = 'b100_1100;
: a_to_g = 'b010_0100;
: a_to_g = 'b010_0000;
: a_to_g = 'b000_1111;
: a_to_g = 'b000_0000;
: a_to_g = 'b000_0100;
: a_to_g = 'b000_1000;
: a_to_g = 'b110_0000;
: a_to_g = 'b011_0001;
: a_to_g = 'b100_0010;
: a_to_g = 'b011_0000;
: a_to_g = 'b011_1000;
endcase
always @ (*)
begin
if(clr)
en = 'b1111;
else
begin
en = 'b1111;
en[a] = ;
end
end
always @ (posedge clk or posedge clr)
begin
if (clr == )
clkdiv <= ;
else
clkdiv <= clkdiv + 'b1;
end
endmodule