Retro Game Coders 社区介绍Retro Game Coders 是一个复古电脑/游戏机游戏 开发社区。网站有关于该社区的介绍、复古电脑收藏、博客、复古资源、社区等板块其中复古资源板块包含众多内容如复古游戏时间线、在线复古 IDE、复古像素艺术编辑器等还有多种模拟器。本期游戏新特性在《C64 BASIC 地下城爬行游戏哥布林来袭》第 8 部分中游戏有了可重新定义的按键、会追人的哥布林、20 面骰战斗还解决了让游戏自我覆盖的内存漏洞。在第 7 部分地下城的哥布林没什么威胁只会待在原地。但这次哥布林会追着玩家跑虽然代码改动不大却极大提升了游戏体验。不过在实现此功能时遇到了一个破坏字符集的诡异内存漏洞这或许是本部分最有价值的教训。点击RGC 在线 IDE 中的 part8d.bas可在浏览器中打开完整程序并编辑。回顾上一次进展在第 7 部分结尾实现了从磁盘加载自定义字符集地图由 3x3 元图块随机生成玩家有类似《龙与地下城》风格的属性通过 PEEK 函数读取屏幕实现碰撞检测还有传送键。但当时游戏缺乏威胁没有紧张感。这一部分有四处改动可重新定义按键哥布林有了位置、生命值和“大脑”引入真实的 20 面骰战斗修复游戏自我覆盖问题。现在可使用在线复古 IDE 在浏览器中跟随教程编辑代码无需下载和配置完全免费。自定义按键的实现有人指出设定的 QAOP 按键组合不合理因为这是开发者成长过程中的常用组合并非人人适用。若换成 WASD又会惹恼另一部分人。所以解决办法是让玩家自定义按键。实现此功能游戏需询问“玩家是否按下了当前存储为上方向的按键”因此要先存储默认按键REM DEFAULT KEYS QKEY$Q AKEY$A OKEY$O PKEY$P FKEY$SPACE变量名陷阱Commodore BASIC 只使用两个字节存储变量名如 PLAYERHEALTH 实际只记录 PL这会让 PLAYERHEALTH 和 PLAYERX 成为同一个变量导致游戏出现难以调试的问题。所以五个按键变量的前两个字符不同QK、AK、OK、PK、FK。此外变量名中不应包含 BASIC 关键字如 GOLD 会让 BASIC 标记器出错因为它会将 GO 替换为标记。常见易犯错的关键字有 TO、IF、ON、OR 和 AND若程序运行异常可先检查变量名。按键读取方式将默认按键存储在变量后输入例程从测试具体字母变为测试变量IF P$OKEY$ THEN PXPX-1 IF P$PKEY$ THEN PXPX1 IF P$QKEY$ THEN PYPY-1 IF P$AKEY$ THEN PYPY1 IF P$FKEY$ THEN GOSUB FIRE重新定义按键的屏幕需将新值写入五个变量这里要用 GET 而非 INPUT。INPUT 会等待输入并按 RETURN 键还对逗号有严格要求玩家可能按错键导致出错。GET 函数从键盘缓冲区读取一个字符并立即返回需用循环等待它返回有用内容CHANGETHISKEY: GET K$: IF K$ THEN GOTO CHANGETHISKEY RETURN然后按顺序询问问题并记录答案CHANGEKEYS: PRINT {CLR}{GREY} PRINT CHANGE KEYS PRINT PRINT PRINT DEFAULT: QAOP AND SPACE PRINT PRESS THE KEY FOR UP GOSUB CHANGETHISKEY QKEY$K$ PRINT PRESS THE KEY FOR DOWN GOSUB CHANGETHISKEY AKEY$K$ PRINT PRESS THE KEY FOR LEFT GOSUB CHANGETHISKEY OKEY$K$ PRINT PRESS THE KEY FOR RIGHT GOSUB CHANGETHISKEY PKEY$K$ PRINT PRESS THE KEY FOR FIRE GOSUB CHANGETHISKEY FKEY$K$ PRINT HAPPY WITH YOUR CHOICES? (Y/N) HOLDFORKEY: GET A$ IF A$ THEN GOTO HOLDFORKEY IF A$Y THEN RETURN IF A$N THEN GOTO CHANGEKEYS RETURN最后确认选择很重要若按错键输入 N 可重新开始。在真实机器上输入代码标签CHANGEKEYS、CHANGETHISKEY在 RGC 在线 IDE 中会解析为行号在标准 C64 上会变成 GOSUB 4000 之类的形式。追踪哥布林信息此前程序无法追踪哥布林不知道其数量和具体位置。现在通过 FOR GC1 TO 10 循环将每个生命值槽初始化为 10 再重置为 0节省一个变量。在元图块绘制例程中若图块编号为 17说明有哥布林将其添加到列表DRAWMT: PRINT MT$(MT,0);{DOWN}{LEFT}{LEFT}{LEFT}; PRINT MT$(MT,1);{DOWN}{LEFT}{LEFT}{LEFT}; PRINT MT$(MT,2); REM IF MT17 THEN IT CONTAINS A GOBBO IF MT17 THEN GOSUB ADDGOBBO RETURN ADDGOBBO: REM ADD TO THE GOBBO LIST - GXCOL (X), GYROW (Y), MATCHES PX/PY IF GC10 THEN RETURN GCGC1 GX(GC)COL1 GY(GC)ROW1 RETURN每个坐标加 1 是因为哥布林在 3x3 图块中间而非左上角。若写错坐标会看到哥布林站在墙里。当 GC 达到 11 时会因向维度为 10 的数组写入数据而报错目前这样可避免游戏崩溃。简单有效的敌人“AI”游戏采用回合制玩家移动敌人移动最后检查游戏是否结束GAMELOOP: OXPX : OYPY REM PLAYER INPUT AND DRAWING GOSUB KEYS IF PXOX OR PYOY THEN GOSUB ERASEPLAYER GOSUB DRAWPLAYER REM HERE IS WHERE YOU PUT THE ENEMY LOGIC GOSUB ENEMYLOGIC REM CHECK GAME OVER CONDITIONS HERE REM -------------------------------- REM RETURN TO GAMELOOP GOTO GAMELOOP回合制让敌人在玩家按键后行动无时间限制也不担心占用 CPU 资源是在 BASIC 中实现敌人 AI 的可行方法。最简单且有威胁的敌人是总是朝着玩家移动的敌人其“AI”逻辑如下ENEMYLOGIC: IF GC0 THEN RETURN FOR G1 TO GC REM SKIP DEAD GOBBOS (CBM BASIC CANT SKIP FORWARDS) IF GH(G)0 THEN GOTO NEXTENEMY REM START FROM CURRENT CELL, THEN NUDGE TOWARD PLAYER IXGX(G) : IYGY(G) IF GX(G)PX THEN IXGX(G)-1 IF GX(G) PY THEN IYGY(G)-1 IF GY(G)提议的移动位置先存储在 IX 和 IY 中碰撞检测用 PEEK 函数若目标单元格是地板点哥布林可移动否则静止。MOVEENEMY 函数按顺序擦除、更新、绘制哥布林MOVEENEMY: REM SET CURSOR POSITION TO OLD GOBBO CELL AND ERASE GOSUB ERASEENEMY REM SET TO NEW CELL AND DRAW GX(G)IX : GY(G)IY GOSUB DRAWENEMY RETURN ERASEENEMY: ROWGY(G) : COLGX(G) : GOSUB CURSORSET : PRINT {GREY}. RETURN DRAWENEMY: ROWIY : COLIX : GOSUB CURSORSET : PRINT {GREEN}; RETURN标签的作用NEXTENEMY: 标签在 NEXT G 上方因为 Commodore BASIC 没有 CONTINUE 语句用 GOTO 跳转到该标签可跳过部分代码相当于实现了 CONTINUE 功能。若跳转到循环外且未执行 NEXT 语句会导致 ?OUT OF MEMORY ERROR难以调试。战斗掷骰机制战斗可从两个方面触发。若哥布林移动到玩家旁边会发起战斗IF ABS(GX(G)-PX)1 AND ABS(GY(G)-PY)1 THEN GOSUB COMBAT玩家也可主动与哥布林碰撞发起战斗REM GOBLINS HURT HP REM - COMBAT WILL ERASE THE GOBBO WHEN THE GOBBO DIES IF CH38 THEN G0:GOSUB COMBAT : RETURN与第 7 部分相比现在是真正的战斗。G 0 标记表示通过碰撞进入战斗战斗函数会据此判断是否有具体的哥布林索引。战斗代码如下COMBAT: REM ROLL D20 RRND(-TI) D20INT(RND(1)*20)1 REM ADD MODIFIER FROM STRENGTH MODINT((STATS(0)-10)/2) D20D20MOD REM CHECK IF HIT GOBBO AC AND DETERMINE DAMAGE IF D208 THEN MSG$MISS!:GOSUB ALERT: HPHP-5:GOSUB SHOWHUD:RETURN DMGINT(RND(1)*8)1MOD MSG$HIT GOBBO WITH STR$(DMG) DAMAGE!:GOSUB ALERT GH(GC)GH(GC)-DMG IF GH(G)0 AND G0 THEN MSG$GOBBO DEAD!:GOSUB ERASEENEMY:GOSUB ALERT RETURNINT(RND(1)*20)1 是掷 20 面骰的代码INT((STATS(0)-10)/2) 是《龙与地下城》中的能力修正公式。IF D20 8 中 8 是哥布林的护甲等级可修改此值调整游戏难度。开头的 R RND(-TI) 会根据系统时钟重新设置随机数生成器的种子。注意 STR$(DMG) 会在正数前加空格可用 MID$(STR$(DMG),2) 去掉。字符集漏洞解决添加哥布林数组后游戏出现屏幕故障、文字显示混乱的问题原因是有东西覆盖了字符集。内存使用方式BASIC 程序从地址 2049 开始存储在内存底部接着是标量变量再是数组数据向上增长。字符串从 BASIC 内存顶部开始向下增长。内存顶部地址存储在零页的 55 和 56 字节只有 BASIC 遵守此限制VIC-II 视频芯片会从 BASIC 使用的内存中读取字符数据。字符集问题根源VIC-II 一次只能访问 16K 内存默认是地址 0 到 16383 的底部 16K。自定义字符集必须存储在该区域否则视频芯片无法看到。最初的 4K 自定义字符集文件包含大写/图形集和小写集放在地址 12288 到 16383 处但游戏未使用小写集这 4K 中的一半被浪费且挡住了数组增长路径。解决方案将字符集裁剪到 2K移到 VIC 可访问区域顶部然后告诉 BASIC 不要再使用这部分内存地址第 7 部分4K 字符集第 8 部分2K 字符集2049 以上BASIC 程序、变量、数组BASIC 程序、变量、数组12288字符集开始冲突BASIC 仍可使用14336字符集小写未使用字符集开始BASIC 停止使用16383VIC-II 的 16K 窗口结束VIC-II 的 16K 窗口结束将字符集从 12288 移到 14336 为 BASIC 释放了 2048 字节内存所以此版本加载 CHARS8C.BIN 而非 CHARS.BIN。让 VIC 指向新地址通过寄存器 53272 告诉视频芯片字符集的位置POKE 53272,(PEEK(53272)AND240)14这行代码先读取寄存器值屏蔽低四位再写入新的字符集设置。14 表示 14336因为低四位中的三位是字符内存指针最低位未使用值先翻倍再使用。值字符内存位置备注00不可用零页和栈存储在此处44096881921212288我原来 4K 字符集的起始位置1414336裁剪后 2K 字符集的存储位置限制 BASIC 使用内存移动字符集后还需缩小 BASIC 的“活动范围”REM MEMORY LIMITATION MITIGATION: IF PEEK(2)73 THEN POKE 55,0:POKE 56,56:POKE 2,73:CLRPOKE 55,0 和 POKE 56,56 设置内存顶部指针为 14336CLR 是关键仅改变指针无用。