Verilog VGA 仿Flappy Bird

游戏体验

为了仿一个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
  • 5
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值