我在网上和旧的C64书籍中寻找答案,但没有找到,所以最后我不得不在这里发帖。我热爱C64编程的旧时光,虽然我目前没有在这个平台上编写游戏,但我很想知道当时是如何克服一些硬件限制的。
在所有现代游戏编程书籍和教程中,找到发射敌人子弹朝向玩家的正确方向的方法是使用带有浮点数的向量数学,大致如下面的伪代码所示:
bullet_velocity = (player.position - bullet.position).normalize();
现在,考虑到C64的限制,我在源代码中看到大量使用正弦表来提高速度,也许是我分心了,但我从未在阅读旧的C64书籍或C64程序员的注释程序时看到关于向量数学的只言片语,我很好奇当时是如何实现相同目标的。
请回答,我有成千上万这样的疑问,但回答这个问题也许我可以找到其他问题的答案!:-)
编辑:仅供参考:在C64游戏中,子弹瞄准玩家的例子有Silkworm和Cybernoid。
回答:
假设你对输出方向的数量足够少感到满意,最简单的做法是通过查找表。例如,对于64个输出方向,从源到目标获取向量(x, y)
,如果两者都是正数,则将两者左移,直到其中一个填满符号位,然后从每个的最高两位形成一个四位的表索引,并查找输出向量。
假设你在160×200分辨率下进行操作,我猜你需要在进入之前丢弃一些精度。
适当镜像以处理其他象限。假设对象位置使用8.8固定点格式,子弹速度为1,那么这是一个32字节的查找表。
因此,简单来说,类似于这样:
xPosYPos: ; 假设($00)有正的x差值 ; 并且($01)有正的y差值 lda $00 ora $01shiftLoop: asl $00 asl $01 asl a bpl shiftLoop ; 用A加载表索引 lda #$00 rol $00 rol a rol $00 rol a rol $01 rol a rol $01 rol a ; 查找向量 tax lda xVec, x ; ... 放置到某处 ... lda yVec, x ; ... 放置到某处 ...
… 更智能的解决方案可能涉及类似于以下内容:
lda $00 ora $01 asl a bmi shift1 asl a bmi shift2 ... 等等 ...shift1:... 等等,但你可以直接移位以形成表索引,而不必进行所有移位向量然后从最高位拼凑 ...
或者,你可以创建一个256字节的查找表,根据x|y
查找例程地址(因为它们都是正数,所以最多为127),并直接跳转到移位,而无需计算位数。
关于对象位置和固定点简介:
假设你在160×200模式下,那么你可以将对象位置的每个分量存储为一个字节。因此,一个字节用于x,一个字节用于y。许多游戏所做的是将每个位置存储为两个字节。因此,x和y总共四个字节。
其中一个字节与单字节格式相同——它是整数位置。另一个是小数部分。因此,如果你有一个位置和一个速度(以及欧拉积分),那么你对位置进行16位的速度加法。然后你只使用最高字节作为位置。
这通常称为固定点算术。与浮点数不同,整数中小数点的位置是固定的。在这里描述的方案中,它总是八位。
例如,要使用单字节量添加偏移量:
clclda xPositionadc xVelocitysta xPositionsta SomeHardwareRegisterForSpritePosition
要使用固定点方案添加偏移量:
clclda xFractionalPositionadc xFractionalVelocitysta xFractionalPositionlda xPositionadc xVelocitysta xPositionsta SomeHardwareRegisterForSpritePosition
优点是你的速度向量现在可以在任何方向上小至1/256像素。因此,例如,你可以存储一个速度,表示每一帧你的子弹将向左移动一个像素,向下移动32/256像素。而移动具有亚像素精度的子弹所需的只是每个向量额外的几个字节存储和额外的几个ADC操作。
根据上述建议,你可以通过从一个字节减去另一个字节来获取从源到目标的向量。结果将是两个单字节,都是输出的分数部分。因此,例如,你可能决定发射一个子弹,其向量为(87/256, 239/256),即20度的角度。