MIPS游戏开发:如何获取并修改字符地址实现WASD移动?
MIPS游戏开发:WASD控制字符移动问题
我正在用MIPS开发一款游戏,已经能成功打印字符,但不知道怎么让字符在屏幕上移动,想用WASD按键实现控制。
初始代码
controls: switchCase: li $v0, 12 # input for controls syscall move $a0, $v0 la $t6, leftArrow lb $t6, ($t6) beq $a0, $t6, moveLeft la $t6, rightArrow lb $t6, ($t6) beq $a0, $t6, moveRight la $t6, upArrow lb $t6, ($t6) beq $a0, $t6, moveUp la $t6, downArrow lb $t6, ($t6) beq $a0, $t6, moveDown moveLeft: addi $t5, $t5, -4 j switchCase #jr $ra moveRight: addi $t5, $t5, 4 j switchCase #jr $ra moveUp: addi $t5, $t5, -16 j switchCase #jr $ra moveDown: addi $t5, $t5, 16 j switchCase #jr $ra
问题现象
编译运行后,程序只显示按下的按键,字符完全没移动(字符存在$t5寄存器里)。分步汇编时会卡在syscall 12指令处。我猜需要获取$t5的地址,同时不确定移动函数的代码是否正确。
更新后的代码
controls: li $t8, 8 li $s5, 15 switchCase: jal rowMajor # addy in s5 input: li $v0, 12 # input for controls syscall move $a0, $v0 la $t6, leftArrow lb $t6, ($t6) beq $a0, $t6, moveLeft la $t6, rightArrow lb $t6, ($t6) beq $a0, $t6, moveRight la $t6, upArrow lb $t6, ($t6) beq $a0, $t6, moveUp la $t6, downArrow lb $t6, ($t6) beq $a0, $t6, moveDown moveLeft: addi $s5, $s5, -4 j input #jr $ra moveRight: addi $s5, $s5, 4 j input #jr $ra moveUp: addi $s5, $s5, -16 j input #jr $ra moveDown: addi $s5, $s5, 16 j input #jr $ra
核心问题与修复方案
阻塞输入导致的卡顿:
syscall 12是阻塞式读字符且会回显输入,这会让程序停在输入步骤直到按键,还会干扰屏幕显示。改用syscall 11(无回显读字符,MARS/SPIM等模拟器支持非阻塞,未按键时返回0),或者结合中断实现实时输入。缺少屏幕重绘逻辑:你只修改了位置寄存器的值,但没有更新屏幕——必须先清除原位置的字符(用空格覆盖),再在新位置绘制目标字符,否则旧字符会残留,看起来没移动。
位置偏移的合理性:
- 从
±16的偏移看,你假设屏幕每行16个字符,这个逻辑没问题;但左右移动用±4可能错误,正常字符在显存中按字节存储,左右移动应偏移±1,除非显存按4字节对齐存储每个字符,需根据实际布局调整。 - 必须添加边界检测,防止字符移出屏幕范围(比如左移不能小于0,右移不能超过总字符数-1,上下移动不能超出行数限制)。
- 从
寄存器使用规范:
$t系列是临时寄存器,可能被jal调用的函数(比如rowMajor)破坏,用$s系列寄存器保存位置(你更新后的$s5是正确选择),但要遵循MIPS调用约定,调用函数前将$s寄存器保存到栈中。
关键修复代码片段
# 假设显存起始地址为0x10008000(MARS默认),目标字符为'X',屏幕为8行×16列 la $t0, 0x10008000 # 显存起始地址 li $s5, 15 # 初始位置索引 main_loop: # 非阻塞读字符(未按键返回0) li $v0, 11 syscall beq $v0, $zero, redraw # 无按键直接重绘 # 处理WASD按键(用ASCII值直接判断) move $t6, $v0 li $t7, 'a' beq $t6, $t7, moveLeft li $t7, 'd' beq $t6, $t7, moveRight li $t7, 'w' beq $t6, $t7, moveUp li $t7, 's' beq $t6, $t7, moveDown j main_loop moveLeft: bgtz $s5, left_valid # 边界检测:不能小于0 j main_loop left_valid: addi $s5, $s5, -1 j main_loop moveRight: li $t7, 127 # 8×16-1=127,屏幕最大位置索引 blt $s5, $t7, right_valid j main_loop right_valid: addi $s5, $s5, 1 j main_loop moveUp: bge $s5, 16, up_valid # 上移一行,至少在第二行 j main_loop up_valid: addi $s5, $s5, -16 j main_loop moveDown: li $t7, 111 # 最后一行起始索引为112,下移后不能超过127 ble $s5, $t7, down_valid j main_loop down_valid: addi $s5, $s5, 16 j main_loop redraw: # 清除原位置字符 add $t1, $t0, $s5 li $t2, ' ' sb $t2, ($t1) # 在新位置绘制目标字符 li $t2, 'X' sb $t2, ($t1) j main_loop
额外提示
- 避免使用箭头键:特殊按键的ASCII值因模拟器而异,WASD的ASCII码固定,更易处理。
- 显存地址确认:不同模拟器的显存起始地址可能不同,需提前确认。
- 中断处理:如果需要更流畅的移动,可配置键盘中断,让程序在按键时触发移动逻辑,而非轮询输入。
内容的提问来源于stack exchange,提问作者dominic99
相关产品推荐
相关产品推荐

