九、贪吃蛇之蛇身控制
九、贪吃蛇之蛇身控制
1. 目标
(1) 游戏难度决定蛇身移动的速度;
(2) 蛇身增长;
(3) 蛇身移动。
2. 蛇身速度控制
用计数器来控制蛇身移动的时间间隔,间隔短,移动快,游戏难度就越难。在游戏难度选择界面,用SW[2:0]选择难度。
//蛇身移动速度
else
begin
clk_cnt <= clk_cnt + 1;
if(clk_cnt == speed)
begin
clk_cnt <= 0;
速度分3档,0.5s、0.25s、0.125s蛇身移动一次。
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
speed <= 24'd12500000;
direct_r <= RIGHT;
end
else begin
direct_r <= direct_next;
case (sw[2:0]) //根据难度设置相应的速度
3'b001:speed <= 24'd12500000; //0.5s
3'b010:speed <= 24'd6250000; //0.25s
3'b100:speed <= 24'd3125000; //0.125s
default:speed <= 24'd12500000;
endcase
end
end
3. 蛇身增长
cube_x,cube_y表示蛇身各节的坐标。第1节代表蛇头。
获取蛇头坐标:
assign head_x = cube_x[0];
assign head_y = cube_y[0];
蛇和食物初始位置:
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
clk_cnt <= 0;
cube_x[0] <= 10; //初始化蛇身有3节,这是第1节
cube_y[0] <= 5;
cube_x[1] <= 9; //初始化蛇身有3节,这是第2节
cube_y[1] <= 5;
cube_x[2] <= 8; //初始化蛇身有3节,这是第3节
cube_y[2] <= 5;
cube_x[3] <= 0;
cube_y[3] <= 0;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
clk_cnt <= 0;
apple_x <= 24;
apple_y <= 10;
add_cube <= 0;
当蛇头和食物的坐标相同时,代表吃了一个食物,蛇身要增长,add_cube为1.每吃下一个食物,蛇身加1,相应的is_exist位置变为1,is_exist有16位,即蛇身最长为16格,is_exist
为1,那一格显示,反之,则不显示。
每次add_cube为1时,表示蛇身长度的信号cube_num加1.is_exist[cube_num] = 1表示让第cube_num位显示出来。
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
is_exist <= 16'd7;
cube_num <= 3; //蛇身初始长度为3,is_exist = 0000_0000_0000_0111表示前3位出现
addcube_state <= 0;
end
else if(game_status == RESTART) begin
is_exist <= 16'd7;
cube_num <= 3;
addcube_state <= 0;
end
else begin
case(addcube_state) //判断蛇头是否与食物坐标重合
0:begin
if(add_cube) begin
cube_num <= cube_num + 1;
is_exist[cube_num] <= 1;
addcube_state <= 1; //吃下食物信号
end
end
1:begin
if(!add_cube)
addcube_state <= 0;
end
endcase
end
end
4. 蛇撞墙或撞自身
(1) 撞墙
当蛇头坐标跟墙的坐标重合,代表撞墙:
//撞墙
if((direct_r == UP && cube_y[0] == 1) ||
(direct_r == DOWN && cube_y[0] == 28) ||
(direct_r == RIGHT && cube_x[0] == 38) ||
(direct_r == LEFT && cube_x[0] == 1))
hit_wall <= 1;
(2) 撞自身
当蛇头坐标跟身体某一节的坐标重合,代表撞到自身:
//撞自身
else if((cube_x[0] == cube_x[1] && cube_y[0] == cube_y[1] && is_exist[1] == 1) || //比较第1节
(cube_x[0] == cube_x[2] && cube_y[0] == cube_y[2] && is_exist[2] == 1) || //比较第2节
(cube_x[0] == cube_x[3] && cube_y[0] == cube_y[3] && is_exist[3] == 1) || //比较第3节
(cube_x[0] == cube_x[4] && cube_y[0] == cube_y[4] && is_exist[4] == 1) || //比较第4节
(cube_x[0] == cube_x[5] && cube_y[0] == cube_y[5] && is_exist[5] == 1) || //比较第5节
(cube_x[0] == cube_x[6] && cube_y[0] == cube_y[6] && is_exist[6] == 1) || //比较第6节
(cube_x[0] == cube_x[7] && cube_y[0] == cube_y[7] && is_exist[7] == 1) || //比较第7节
(cube_x[0] == cube_x[8] && cube_y[0] == cube_y[8] && is_exist[8] == 1) || //比较第8节
(cube_x[0] == cube_x[9] && cube_y[0] == cube_y[9] && is_exist[9] == 1) || //比较第9节
(cube_x[0] == cube_x[10] && cube_y[0] == cube_y[10] && is_exist[10] == 1) || //比较第10节
(cube_x[0] == cube_x[11] && cube_y[0] == cube_y[11] && is_exist[11] == 1) || //比较第11节
(cube_x[0] == cube_x[12] && cube_y[0] == cube_y[12] && is_exist[12] == 1) || //比较第12节
(cube_x[0] == cube_x[13] && cube_y[0] == cube_y[13] && is_exist[13] == 1) || //比较第13节
(cube_x[0] == cube_x[14] && cube_y[0] == cube_y[14] && is_exist[14] == 1) || //比较第14节
(cube_x[0] == cube_x[15] && cube_y[0] == cube_y[15] && is_exist[15] == 1) || //比较第15节
)
5. 蛇身的移动路径
蛇头移动有三种情况:撞墙、撞自身、移动到下一个位置。蛇身每一节把坐标传递给下一节。
//蛇身移动,前位坐标传给后位
else begin
cube_x[1] <= cube_x[0];
cube_y[1] <= cube_y[0];
cube_x[2] <= cube_x[1];
cube_y[2] <= cube_y[1];
cube_x[3] <= cube_x[2];
cube_y[3] <= cube_y[2];
cube_x[4] <= cube_x[3];
cube_y[4] <= cube_y[3];
cube_x[5] <= cube_x[4];
cube_y[5] <= cube_y[4];
cube_x[6] <= cube_x[5];
cube_y[6] <= cube_y[5];
cube_x[7] <= cube_x[6];
cube_y[7] <= cube_y[6];
cube_x[8] <= cube_x[7];
cube_y[8] <= cube_y[7];
cube_x[9] <= cube_x[8];
cube_y[9] <= cube_y[8];
cube_x[10] <= cube_x[9];
cube_y[10] <= cube_y[9];
cube_x[11] <= cube_x[10];
cube_y[11] <= cube_y[10];
cube_x[12] <= cube_x[11];
cube_y[12] <= cube_y[11];
cube_x[13] <= cube_x[12];
cube_y[13] <= cube_y[12];
cube_x[14] <= cube_x[13];
cube_y[14] <= cube_y[13];
cube_x[15] <= cube_x[14];
cube_y[15] <= cube_y[14];
6. 蛇头方向控制
蛇头方向通过四个按键KEY[3:0]控制,用状态机实现,参考(八、状态机设计)。
7. 蛇色块显示
当坐标pos_x[9:4],pos_y[9:4]与蛇的坐标吻合时,显示蛇身。墙壁的显示也是这样。
//显示墙和蛇身
always @(pos_x or pos_y) begin
if(pos_x >=0 && pos_x <640 && pos_y >= 0
&& pos_y < 480) begin
if(pos_x[9:4] == 0 || pos_y[9:4] == 0
|| pos_x[9:4] == 39 || pos_y == 29)
snake_show = WALL; //显示墙
else if(pos_x[9:4] == cube_x[0] &&
pos_y[9:4] == cube_y[0] &&
is_exist[0] == 1)
snake_show = (snake_display == 1)?
HEAD : NONE; //蛇头
else if
((pos_x[9:4] = cube_x[1] && pos_y[9:4] == cube_y[1] &&
is_exist[1] == 1) ||
(pos_x[9:4] = cube_x[2] && pos_y[9:4] == cube_y[2] &&
is_exist[2] == 1) ||
(pos_x[9:4] = cube_x[3] && pos_y[9:4] == cube_y[3] &&
is_exist[3] == 1) ||
(pos_x[9:4] = cube_x[4] && pos_y[9:4] == cube_y[4] &&
is_exist[4] == 1) ||
(pos_x[9:4] = cube_x[5] && pos_y[9:4] == cube_y[5] &&
is_exist[5] == 1) ||
(pos_x[9:4] = cube_x[6] && pos_y[9:4] == cube_y[6] &&
is_exist[6] == 1) ||
(pos_x[9:4] = cube_x[1] && pos_y[9:4] == cube_y[1] &&
is_exist[1] == 1) ||
(pos_x[9:4] = cube_x[2] && pos_y[9:4] == cube_y[2] &&
is_exist[2] == 1) ||
(pos_x[9:4] = cube_x[3] && pos_y[9:4] == cube_y[3] &&
is_exist[3] == 1) ||
(pos_x[9:4] = cube_x[4] && pos_y[9:4] == cube_y[4] &&
is_exist[4] == 1) ||
(pos_x[9:4] = cube_x[5] && pos_y[9:4] == cube_y[5] &&
is_exist[5] == 1) ||
(pos_x[9:4] = cube_x[6] && pos_y[9:4] == cube_y[6] &&
is_exist[6] == 1) ||
(pos_x[9:4] = cube_x[7] && pos_y[9:4] == cube_y[7] &&
is_exist[7] == 1) ||
(pos_x[9:4] = cube_x[8] && pos_y[9:4] == cube_y[8] &&
is_exist[8] == 1) ||
(pos_x[9:4] = cube_x[9] && pos_y[9:4] == cube_y[9] &&
is_exist[9] == 1) ||
(pos_x[9:4] = cube_x[10] && pos_y[9:4] == cube_y[10] &&
is_exist[10] == 1) ||
(pos_x[9:4] = cube_x[11] && pos_y[9:4] == cube_y[11] &&
is_exist[11] == 1) ||
(pos_x[9:4] = cube_x[12] && pos_y[9:4] == cube_y[12] &&
is_exist[12] == 1) ||
(pos_x[9:4] = cube_x[13] && pos_y[9:4] == cube_y[13] &&
is_exist[13] == 1) ||
(pos_x[9:4] = cube_x[14] && pos_y[9:4] == cube_y[14] &&
is_exist[14] == 1) ||
(pos_x[9:4] = cube_x[15] && pos_y[9:4] == cube_y[15] &&
is_exist[15] == 1))
snake_show = (snake_display == 1) ? BODY : NONE; //身体
else
snake_show = NONE;
end
为了便于分辨蛇身的每节,把每节第一列前三个像素显示黑色。
//给蛇身每节第一列前三个像素显示黑色,便于分辨
else if (game_status == PLAY | game_status == START) begin
cnt <= 0;
if(pos_x[9:4] == apple_x && pos_y[9:4] == apple_y) begin
vga_rgb = PINK;
end
else if(snake_show == NONE) begin
vga_rgb = BLACK;
end
else if(snake_show == WALL) begin
vga_rgb = RED;
end
else if(snake_show == HEAD | snake_show == BODY) begin
case({pos_x[3:0],pos_y[3:0]})
8'b00000000:vga_rgb = BLACK;
8'b00000001:vga_rgb = BLACK;
8'b00000010:vga_rgb = BLACK;
default:vga_rgb = (snake_show == HEAD) ? GREEN : BLUE;
endcase
end
else begin
vga_rgb = BLACK;
end
end
参考文献:
https://mp.weixin.qq.com/s/4qgOI4xP1nzufEQLQpFUHA