天天看點

Verilog VGA 仿Flappy Bird

遊戲體驗

為了仿一個Flappy Bird,專門去4399玩了一下,總結了一下

  • 小鳥:小鳥隻在垂直方向移動,不點會飛快地往下掉
  • 柱子:由遠及近,每個柱子間通道位置随機,高度相同
  • 操作: 隻能點,點一下隻往上跳一骨碌
  • 死法:大緻就是撞柱子會死,摔地上會死
  • 可玩性:得分制,操作簡單而不是趣味,越往後柱子跑的越快

遊戲構想

  • 小鳥:水準方向位置為常數确定,通過上下界變量确定實時位置
  • 玩家按鍵:按一次,小鳥向上跳一下
  • 小鳥移動:當有按鍵按下,小鳥上下界同時減少,并通過一個布爾值確定一次按鍵隻跳一次;當無按鍵,小鳥自動下降,上下界同時增加
  • 柱子:簡化起見,隻有一個柱子,通過左右界确定位置,當柱子移動到左邊界令其複位到起始位置,并重新選取通道位置。
  • 通道:使用一個時鐘生成一定範圍的随機數,通過上界确定通道位置,高度固定為150
  • 難度遞增:每次重新開始(複位)後,使用時鐘計時,隔一定時間就增加一次柱子移動的速度
  • 死亡檢測:當小鳥與柱子的水準位置有重疊時(相遇),判斷豎直方向是否有重疊(簡化了,掉地上不會死了),沒成功通過就加一分,須通過一布爾變量防止通過一個通道時重複加分
  • 得分顯示:在螢幕上顯示數字點陣也挺麻煩的,幹脆用上之前學的數位管好了

圖檔素材

  • 小鳥
    Verilog VGA 仿Flappy Bird
  • 遊戲結束
    Verilog VGA 仿Flappy Bird

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
           

繼續閱讀