在DOSBox中用x86汇编开发类Flappy Bird游戏的问题求助
在DOSBox中用x86汇编开发类Flappy Bird游戏的问题求助
我搞不懂为什么松开空格键时我的鸟不会向下移动,而且上一个位置也没有清除。另外我还想让除了鸟之外的画面横向滚动,营造出鸟穿过柱子的效果。
以下是我当前的汇编代码:
[org 0x0100] jmp start value: dw 16 value2: dw 2896 pillar_color: db 0x26 space: db 0x20 bird_row: dw 1760 sky_color: db 0x13 bird_color: db 0x18 bird_char: db 'L' c: dw 3 delay: push cx mov cx, 0x09 delay_loop1: push cx mov cx, 0xFFFF delay_loop2: loop delay_loop2 pop cx loop delay_loop1 pop cx ret my_isr: push ax in al, 0x60 TEST al, al JZ released cmp al, 0x39 JNE released pressed: call move_up call delay ; Add a delay here if needed jmp sub_end released: ; Check if the released key is the space key (ASCII code 32) mov ah, 0 int 16h cmp al, 32 jne sub_end ; Space key is released, move down call move_down sub_end: mov al, 0x20 out 0x20, al pop ax iret print: push es push ax push cx push di mov ax, 0xb800 mov es, ax xor di, di mov ax, 0x1320 mov cx, 1520 cld rep stosw mov di, 3200 mov ax, 0x6820 mov cx, 2000 cld rep stosw mov dx, 9 mov di, 0 ; First Pillar mov dx, 9 mov di, 0 First_pillar: mov di, [value] mov cx, 8 p1: mov ah, [pillar_color] mov al, [space] cld stosw add di, 158 dec cx cmp cx, 0 jnz p1 mov ax, [value] add ax, 16 mov [value], ax dec dx cmp dx, 0 jnz First_pillar ; Second Pillar mov dx, 9 mov di, 0 Second_pillar: mov di, [value2] mov cx, 6 p2: mov ah, [pillar_color] mov al, [space] cld stosw sub di, 162 dec cx cmp cx, 0 jnz p2 mov ax, [value2] add ax, 16 mov [value2], ax dec dx cmp dx, 0 jnz Second_pillar pop di pop cx pop ax pop es ret draw_bird: call clear_bird ; Clear previous position mov ax, 0xb800 mov es, ax mov di, word [bird_row] ; Set the color attribute for the bird mov ah, [bird_color] ; Combine the character and color attribute shl ah, 8 mov al, [bird_char] ; Store the character and color attribute in the video memory mov [es:di], ax ret clear_bird: mov ax, 0xb800 mov es, ax mov di, word [bird_row] ; Set the color attribute for the sky mov ah, [sky_color] ; Combine the character (space) and color attribute shl ah, 8 mov ax, 0x20 ; Use a constant value for the space character ; Store the character and color attribute in the video memory using stosw stosw ret move_up: call delay call clear_bird ; Clear previous position sub word [bird_row], 160 call draw_bird ; Draw the bird in the new position ret move_down: call delay call clear_bird ; Clear previous position add word [bird_row], 160 ; Move down by 2 bytes (1 word) call draw_bird ; Draw the bird in the new position ret start: call print call draw_bird key_check_loop: ; Check if the pressed key is the space key (ASCII code 32) mov ah, 0 int 16h cmp al, 32 je space_pressed ; Check for key release mov ah, 1 int 16h cmp al, 32 jne not_space_pressed ; Space key is released, move down call move_down jmp key_check_loop not_space_pressed: jmp key_check_loop space_pressed: ; Space key is pressed call move_up jmp key_check_loop space_released: ; Space key is released call move_down jmp key_check_loop end: mov ax, 0x4c00 int 0x21
问题分析与解决方案
兄弟,我帮你拆解下这几个问题的根源,以及对应的修复方法:
1. 松开空格键鸟不向下移动
你写了my_isr键盘中断服务程序,但根本没把它安装到系统中断向量表!DOS下的键盘中断是INT 9,默认的中断处理程序只会处理按键按下,要捕获释放事件,得替换这个中断向量。另外你原来的key_check_loop用INT 16h的功能0是阻塞等待按键,功能1只是检查缓冲区有没有按键,完全没法处理按键释放事件。
修复步骤:
- 在
start开头先保存原有的INT 9中断向量,然后把my_isr设置为新的INT 9处理程序 - 退出前要恢复原中断向量,避免破坏系统
- 调整
my_isr里的释放判断逻辑:键盘扫描码的最高位为1时是释放事件,所以应该用test al, 0x80来判断,而不是TEST al, al(这判断的是扫描码是否为0,不对)
2. 鸟的上一个位置没清除
看你的clear_bird函数,这里有个低级错误:你先把ah设为天空颜色并左移8位,然后直接mov ax, 0x20,这就把刚才设置的ah给覆盖了!正确的做法是把al设为空格字符,然后ax就是ah(颜色) | al(字符)。
3. 实现画面横向滚动(柱子移动)
现在你的柱子是固定向下绘制(每次add ax,16),要营造鸟向前飞的效果,应该让柱子向左移动(每次x坐标减2,因为每个字符占2字节),移出屏幕左边后重置到右边,循环滚动。
修改后的完整代码
[org 0x0100] jmp start ; 全局变量 value: dw 16 ; 第一个柱子的起始位置 value2: dw 2896 ; 第二个柱子的起始位置 pillar_color: db 0x26 space: db 0x20 bird_row: dw 1760 ; 鸟的位置(初始在第11行中间:10*160 + 40*2 = 1760) sky_color: db 0x13 bird_color: db 0x18 bird_char: db 'L' old_int9: dd 0 ; 保存原INT9中断向量 ; 延迟函数 delay: push cx mov cx, 0x03 delay_loop1: push cx mov cx, 0xFFFF delay_loop2: loop delay_loop2 pop cx loop delay_loop1 pop cx ret ; 自定义键盘中断处理程序 my_isr: push ax push bx in al, 0x60 ; 读取键盘扫描码 ; 检查是否是空格键释放(扫描码0x39是按下,0xB9是释放) cmp al, 0xB9 jne check_press ; 空格键释放,鸟向下移动 call move_down jmp isr_end check_press: cmp al, 0x39 ; 空格键按下 jne isr_end call move_up isr_end: ; 发送EOI给中断控制器 mov al, 0x20 out 0x20, al ; 调用原INT9处理程序处理其他按键 pop bx pop ax jmp far [cs:old_int9] ; 清除鸟的原位置 clear_bird: mov ax, 0xb800 mov es, ax mov di, word [bird_row] mov ah, [sky_color] mov al, [space] mov [es:di], ax ret ; 绘制鸟 draw_bird: call clear_bird mov ax, 0xb800 mov es, ax mov di, word [bird_row] mov ah, [bird_color] mov al, [bird_char] mov [es:di], ax ret ; 鸟向上移动 move_up: call delay call clear_bird ; 防止鸟飞出屏幕顶部 cmp word [bird_row], 0 jle skip_up sub word [bird_row], 160 skip_up: call draw_bird ret ; 鸟向下移动 move_down: call delay call clear_bird ; 防止鸟飞出屏幕底部(25行:24*160 + 40*2 = 3920) cmp word [bird_row], 3920 jge skip_down add word [bird_row], 160 skip_down: call draw_bird ret ; 更新场景(背景+柱子) update_scene: push es push ax push cx push di mov ax, 0xb800 mov es, ax ; 清除屏幕(天空+地面) xor di, di mov ax, 0x1320 ; 天空颜色+空格 mov cx, 1520 ; 前19行:19*80=1520 cld rep stosw mov di, 3200 ; 第20行开始:20*160=3200 mov ax, 0x6820 ; 地面颜色+空格 mov cx, 400 ; 后5行:5*80=400 cld rep stosw ; 绘制第一个柱子(上半部分) mov dx, 9 First_pillar: mov di, [value] mov cx, 8 p1: mov ah, [pillar_color] mov al, [space] stosw add di, 158 ; 跳到下一行同位置(160-2=158) dec cx jnz p1 ; 柱子左移2字节(1个字符) mov ax, [value] sub ax, 2 ; 移出屏幕左边则重置到右侧 cmp ax, 0 jl reset_p1 mov [value], ax jmp p1_continue reset_p1: mov [value], 318 ; 屏幕右侧(80列*2 -2)+ 160*2(两行偏移) p1_continue: dec dx jnz First_pillar ; 绘制第二个柱子(下半部分) mov dx, 9 Second_pillar: mov di, [value2] mov cx, 6 p2: mov ah, [pillar_color] mov al, [space] stosw sub di, 162 ; 跳到上一行同位置(-160-2=-162) dec cx jnz p2 ; 柱子左移2字节 mov ax, [value2] sub ax, 2 cmp ax, 0 jl reset_p2 mov [value2], ax jmp p2_continue reset_p2: mov [value2], 318 + 160*10 ; 右侧偏下位置 p2_continue: dec dx jnz Second_pillar pop di pop cx pop ax pop es ret start: ; 保存原INT9中断向量 mov ax, 0x3509 int 0x21 mov [old_int9], bx mov [old_int9+2], es ; 设置自定义INT9中断向量 mov ax, 0x2509 mov dx, my_isr int 0x21 ; 游戏主循环 game_loop: call update_scene call draw_bird call delay ; 检查是否按下ESC退出 mov ah, 1 int 16h jz game_loop mov ah, 0 int 16h cmp al, 27 jne game_loop exit: ; 恢复原INT9中断向量 mov ax, 0x2509 lds dx, [old_int9] int 0x21 ; 退出程序 mov ax, 0x4c00 int 0x21
关键修改说明
- 安装了自定义
INT9中断处理程序,能正确捕获空格键的按下和释放事件 - 修复了
clear_bird函数的错误,现在能正确清除鸟的上一个位置 - 把
print改成update_scene,每次循环都重绘背景和向左移动的柱子,实现滚动效果 - 增加了鸟的边界检测,防止飞出屏幕
- 增加了ESC键退出游戏的功能,方便测试
备注:内容来源于stack exchange,提问作者M. Abdullah Ilyas
相关产品推荐
相关产品推荐

