谢邀=w=
这个其实看它的behavior就很容易猜出程序是怎么写的了
S抢的发射逻辑是,一枪发射前方扇形5颗子弹,屏幕上最多不超过10颗子弹
两枪之间并没有最小发射间隔(连射键是我们“中华FC”后加的功能,那时候谁tm能想到玩家能连续每秒15次20次的速度输入啊)
如果超过10颗,无法发射,如果这枪发射后多于10颗,那么多出来的部分就无法发射
本来按照街机的逻辑,五颗五颗应该是分组的,前五颗彻底飞出屏幕之后才应该能再发射五颗,但是可能负责移植的程序员懒还是怎么地,并没有如此判断
其实L枪也一样,本应该是飞出屏幕之后才能射第二枪,结果并没有判断,而是第二枪覆盖第一枪的数据,于是你可以连射变成短棍……
扇形嘛,肯定有先飞出去的和后飞出去的,或者打倒敌人消失的,10个槽位(可以这么想象)有空出来了,就可以发射补上了,但是总体不能超过10个啊,于是就只能吞掉扇形上下两边的子弹了
于是就变成长条了
用C代码写大概他们是写成了这样:
typedef struct _SPREADGUNBULLET { unsigned char available; unsigned char x; unsigned char y; unsigned char deltax; unsigned char deltay; } SPREADGUNBULLET; SPREADGUNBULLET sbullet[10]; char getSBullet() { int i; for (i = 0; i < 10; i++) { if (sbullet[i].available) return i; } return -1; } void shotSpreadGun() { int i; const static char dListX = { 8, 6, 6, 4, 4 } ; // 这数字我瞎写的 const static char dListY = { 0, -2, 2, -4, 4 } ; // 总之是扇形…… for (i = 0; i < 5; i++) { char slot = getSBullet(); if (slot == -1) break; sbullet[slot].available = 0; sbullet[slot].x = player.x + (player.direction ? 8 : -8); sbullet[slot].y = player.y; sbullet[slot].deltax = player.direction ? dListX[i] : -dListX[i]; sbullet[slot].deltay = dListY[i]; } }
准备给S枪子弹的位置一共10个,然后子弹或是飞出屏幕或是打倒敌人,某available变成1,我觉得接下来的东西都可以想象了……
评论区中出现了一位原教旨主义者(
@Applebloom,现在又多了一位
@blue dark),认为魂斗罗是用6502汇编写成的,所以我应该用6502汇编写出上面的程序,所以我决定现查6502指令集手册,写一个等同于上面C代码的6502汇编……没条件测试,我现在也很困,不确定100%正确,而且这个代码应该对理解本答案没有任何帮助……没兴趣的人可以无视,这是我第一次写6502汇编,如果有高手发现错误轻喷……
; typedef struct _SPREADGUNBULLET { ; unsigned char available; ; unsigned char x; ; unsigned char y; ; unsigned char deltax; ; unsigned char deltay; ; } SPREADGUNBULLET; ; typedef struct _PLAYER { ; unsigned char status; ; unsigned char x; ; unsigned char y; ; unsigned char direction; ; unsigned char gun; ; unsigned char life; ; } PLAYER; ; ; 假设已经定义S弹内存地址为SBULLET,对应SPREADGUNBULLET构造体 ; 假设已经定义1P数据为PLY1,对应PLAYER构造体 ; 直接访问PLY1构造体变量的各种宏也已定义 ; 发射S弹的子程序为SHOTSBUL GETSBUL LDX #45 ; X计数器45(5字节一组,对应SPREADGUNBULLET构造体,[9]~[0]) LOOPGETSBUL LDA (SBULLET),X ; 读取SBULLET[X/5].available BNE ENDSBUL ; 如果非0,结束循环 TXA ; X寄存器不能直接做数学运算…… SBC #05 ; 计数器-5 TAX ; 存回去 BPL LOOPSBUL ; 如果大于0(也就是还没到10组子弹的范围)则循环 ENDGETSBUL RTS ; return ; DLISTX .BYTE 4 .BYTE 4 .BYTE 6 .BYTE 6 .BYTE 8 DLISTY .BYTE 4 .BYTE -4 .BYTE 2 .BYTE -2 .BYTE 0 SHOTSBUL LDY #4 ; Y计数器4,对应5发子弹[4]~[0] SETSHOTSBUL JSR GETSBUL ; 获取空余子弹槽位 BMI ENDSHOTSBUL ; 如果结果小于0(也就是没有剩余)则结束 LDA #0 ; .available = 0 STA (SBULLET),X ; INX ; X现在指向.x LDA (PLY1DIR) ; 发射方向向玩家面朝方向平移8像素,对准枪口 BEQ SHOTSRIGHT ; 如果为0则朝右 LDA #-8 JMP SHOTSXSET SHOTSRIGHT LDA #8 SHOTSXSET ADC (PLY1X) STA (SBULLET),X ; .x = PLY1.x + (PLY1.direction ? 8 : -8) INX ; X现在指向.y LDA (PLY1Y) STA (SBULLET),X ; .y = PLY1.y INX ; X现在指向.deltax LDA (PLY1DIR) BEQ SHOTSDRIGHT ; 如果为0则朝右 LDA (DLISTX),Y EOR #$FF ; 因为朝左,所以取反 ADC #1 JMP SHOTSDXSET SHOTSDRIGHT LDA (DLISTX),Y SHOTSDXSET STA (SBULLET),X ; .deltax = PLY1.direction ? dListX[Y] : -dListX[Y] INX ; X现在指向.deltaY LDA (DLISTY),Y STA (SBULLET),X ; .deltay = dListY[Y] DEY BPL SETSHOTSBUL ; 如果Y大于0(还没到5个一组),则继续循环 ENDSHOTSBUL RTS ; return