You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.20 11:33:14