如何在C64的边框中显示精灵?

40

我曾经看过一些很酷的C64演示,展示了在屏幕边框区域中显示精灵。这似乎是不可能的;我认为他们以某种方式成功地欺骗了图形芯片。他们是如何做到的呢?

我见过一些很酷的C64演示,在屏幕边框区域中展示了精灵。这本应该是不可能的,我认为他们以某种方式成功地欺骗了图形芯片。他们究竟是如何实现的?

要获得超过允许的精灵数量,您必须使精灵相对于电视刷新率移动足够快:精灵在屏幕上的位置A上绘制,您将其移动到位置B并再次绘制,等等,然后当电视屏幕完成绘制时,返回到位置A或现在A已经移动到的任何位置。 - KM.
4
我刚刚将C64添加到我的“感兴趣标签”列表中。我支持! - Paolo Tedesco
在它的大兄弟(姐妹?)Amiga上,他们加入了一个整个协处理器(copper),以定义在哪些屏幕扫描线上必须进行视频硬件更改。这消除了复杂的汇编中断程序的需要。 - Toad
3
这个问题为什么会被标记为“不具建设性”?我的答案包含了如何在边框中显示内容的代码,这难道不是很具有建设性吗?请问还有什么更具建设性的方法吗? - Peter Kofler
@TylerCrompton 与“如何在屏幕边缘打印文本”不同,精灵“不应该”出现在边框上。 - wizzwizz4
1
@KM(抱歉翻出旧评论),但您实际上并没有以更快的方式移动精灵相对于电视。相反,您告诉VIC-II在特定行上生成中断,并且当光栅束到达该行时,然后再次设置精灵指针。根据需要重复进行多行操作。仅在每个精灵高度之间留下一点空间(除非您涉及精灵压缩)。VIC-II将处理其余部分。 - cbmeeks
6个回答

34

是的,你需要使用汇编语言。这是一个中断定时技巧。VIC能够在边框中显示精灵,但帧只是将它们隐藏起来,所以精灵可以滑动到其后面。它与VIC显示的扫描线相连。对于下/上边框来说,它非常简单:

  • 编写一个中断程序,在特定扫描行开始同步,比如在下边框之前的7个像素或类似的东西。
  • 设置VIC寄存器使边框变小。(有一个可以做到这一点的寄存器。)
  • VIC现在认为边框已经开始并且不开始绘制它。
  • ->底部没有边框。
  • 在真正的边框之后再设置另一个中断程序以将其恢复为原始状态。

对于左/右边框中的精灵,情况更加复杂,因为必须为每个扫描线重复这个过程:

  • 编写一个中断程序,在特定扫描行开始同步。
  • 然后执行一些NOP操作,直到距离右边框还有7个像素。
  • 设置VIC寄存器使边框变小。
  • -> 右侧没有边框。
  • 执行一些NOP操作,直到超过真正的边框,并将寄存器设置回原始值。
  • 再次执行一些NOP操作,直到步骤2。

问题在于所有这些NOP操作都是忙碌等待,会占用你用于其他事情的周期。

我找到了一些代码,可以从下边框中的精灵滚动器中获得。这是代码。(它被从某个演示中剥离出来的。)

C198  78        SEI
C199  20 2E C1  JSR C12E     # clear sprite area
C19C  20 48 C1  JSR C148     # init VIC
C19F  A9 BF     LDA #BF      # set up IRQ in C1BF
C1A1  A2 C1     LDX #C1
C1A3  8D 14 03  STA 0314
C1A6  8E 15 03  STX 0315
C1A9  A9 1B     LDA #1B
C1AB  8D 11 D0  STA D011
C1AE  A9 F7     LDA #F7
C1B0  8D 12 D0  STA D012
C1B3  A9 01     LDA #01
C1B5  8D 1A D0  STA D01A
C1B8  A9 7F     LDA #7F
C1BA  8D 0D DC  STA DC0D
C1BD  58        CLI
C1BE  60        RTS

----------------------------------
# init VIC
C148  A2 00     LDX #00
C14A  BD 88 C1  LDA C188,X
C14D  9D 00 D0  STA D000,X   # set first 16 values from table
C150  E8        INX
C151  E0 10     CPX #10
C153  D0 F5     BNE C14A
C155  A9 FF     LDA #FF
C157  8D 15 D0  STA D015
C15A  A9 00     LDA #00
C15C  8D 1C D0  STA D01C
C15F  A9 FF     LDA #FF
C161  8D 17 D0  STA D017
C164  8D 1D D0  STA D01D
C167  A9 C0     LDA #C0
C169  8D 10 D0  STA D010
C16C  A9 F8     LDA #F8
C16E  A2 00     LDX #00
C170  9D F8 07  STA 07F8,X
C173  18        CLC
C174  69 01     ADC #01
C176  E8        INX
C177  E0 08     CPX #08
C179  D0 F5     BNE C170
C17B  A9 0E     LDA #0E
C17D  A2 00     LDX #00
C17F  9D 27 D0  STA D027,X
C182  E8        INX
C183  E0 08     CPX #08
C185  D0 F8     BNE C17F
C187  60        RTS

----------------------------------
# data set into VIC registers
C188  00 F7 30 F7 60 F7 90 F7
C190  C0 F7 F0 F7 20 F7 50 F7

----------------------------------
# main IRQ routine
C1BF  A2 08     LDX #08
C1C1  CA        DEX
C1C2  D0 FD     BNE C1C1
C1C4  A2 28     LDX #28      # 40 or so lines
C1C6  EA        NOP          # "timing"
C1C7  EA        NOP
C1C8  EA        NOP
C1C9  EA        NOP
C1CA  CE 16 D0  DEC D016     # fiddle register
C1CD  EE 16 D0  INC D016
C1D0  AC 12 D0  LDY D012
C1D3  88        DEY
C1D4  EA        NOP
C1D5  98        TYA
C1D6  29 07     AND #07
C1D8  09 18     ORA #18
C1DA  8D 11 D0  STA D011
C1DD  24 EA     BIT   EA
C1DF  EA        NOP
C1E0  EA        NOP
C1E1  CA        DEX
C1E2  10 E4     BPL C1C8     # repeat next line
C1E4  A9 1B     LDA #1B
C1E6  8D 11 D0  STA D011
C1E9  A9 01     LDA #01
C1EB  8D 19 D0  STA D019
C1EE  20 00 C0  JSR C000   # call main code
C1F1  4C 31 EA  JMP EA31   # finish IRQ

我认为位于0xC148的“init VIC”代码主要是为了在精灵滚动器中设置边框中的精灵(它与禁用边框有关)。但是,这些精灵必须存在,否则VIC /光栅线的定时会不同,整个事情就无法正常工作。而且你肯定希望有精灵存在 :-) - Peter Kofler

10

如果您想学习有关在C64上打开边框的技巧,可以查看Pasi Ojala在C=Hacking Issue 6中撰写的优秀文章。

不过,不需要过于技术化。该技巧利用了VIC芯片的一个特性,使您可以在文本/图形的25/24行和40/38列之间切换,并涉及在恰当时刻进行此切换,以欺骗VIC芯片认为已经打开了边框,而实际上并没有打开。请参阅上述文章,了解更详细的解释及代码示例。


10
一切都取决于时机。C64有一种方法在绘制屏幕时查询电子束的确切垂直位置。当新行开始时,您必须等待几个周期(可以使用NOP指令计时),然后必须设置视频芯片的硬件寄存器,该寄存器负责设置屏幕模式(和边框宽度)。通过精确定时,并在每个扫描线上都这样做,整个侧边框消失了。
底部边框也用类似的技巧去掉。在垂直边框开始的确切扫描线上,您也必须设置视频模式,以便为该帧禁用底部边框。
实际上,整个过程都必须使用汇编语言完成,否则就无法精确定时。
另外,我认为侧边框技巧归功于1001组(荷兰组),我不确定谁首先使用了底边框技巧。

是的,但那不是你需要设置以更改屏幕模式的寄存器。 - Toad
我知道,但你写得有点“不够具体”。所以我想补充一些细节。我相信 $d011 是选择你是否使用 25 或 24 行的那个,这会使边框收缩或不收缩。http://zh.wikipedia.org/wiki/MOS_Technology_VIC-II - BerggreenDK

1

那是很久以前的事了。

我知道有一种解决方案依赖于监视器的频率。

在CRT中,即使当前像素位于正常屏幕之外,也可以确定其位置。因此,您可以操纵射线。

我的废品堆里肯定有一些C64图书。

离题了,但是用VIC20(C64的前身)制作图形很有趣。没有办法操纵每个像素,但您可以更改现有字符。因此,您可以使用从0到...的所有字符填充屏幕,并更改字符以将像素设置到屏幕上。;-)。


1
在C64上改变像素并不是非常简单的事情。图形模式本质上是模拟了一个由0、1等字符填充的字符模式屏幕。 :-) - peterh
您可以使用高分辨率位图模式操纵每个像素。或者在多彩位图模式下使用它们的配对,但这总是使用8000字节(而不是8k),并且您并不总是拥有这些字节。顺便说一句,您不会更改现有字符,它们存储在ROM中,因此您无法更改它们,但您可以将它们复制到RAM中,然后进行操作。 - user14699696

1
正如之前所说的那样,你需要欺骗VIC认为边框已经开始了,但我写这篇文章的原因是因为顶部答案有点不准确:我完全找不到一个寄存器来使边框变小,所以这就是你要做的事情(至少对于顶部和底部):等待VIC达到第25个字符行,然后启用24行($D011,位3)。你可以用38列($D016,位3)做同样的事情来处理左右边框,但是你需要非常精确的时间控制,并且还需要通过设置垂直滚动寄存器来消除坏行,这样扫描线模8永远不会等于滚动值。当然,你不能再使用正常的显示,因为坏行实际上不仅仅是坏的,它们还用于加载字符数据,我想是一些在非边框区域每8行重复的东西。当我读到顶部答案时,我个人有点困惑,希望这可以帮助你。(另外,顶部答案有一个错误:你不是使边框变小,而是使它变大)

-3

时间是关键。通过改变超扫描(边框)颜色,随着CRT的光束从左到右移动,在边框中创建图像。生成图像需要两个定时信号 - 垂直刷新和水平刷新。通过检测水平和垂直刷新发生的时间,您可以开始一系列汇编指令来更改边框颜色以生成图像。您需要计算每个边框像素的CPU时钟周期数,并使用它来创建在正确时间更改边框颜色的代码。

当涉及编写游戏时,它的效果并不好,因为CPU开销太大,没有足够的时间处理用户输入和游戏状态。


这不是它的正确做法... 处理器速度太慢了,无法快速更改颜色,以至于任何单个像素都无法点亮。最好的方法是在边框开始/停止的确切时刻计时,并更改显示模式。(请参见我的答案) - Toad
我刚刚做了一些计算:每秒25帧,每帧250行,总共6250行,即每行需要0.00016秒。在1Mhz的情况下,这相当于160条指令,因此使用这种方法最多只能在屏幕上获得160个像素。我曾经看到过用这种方法制作的边框图像,它们非常粗糙。 - Skizz
一旦你掌握了正确的时机并打开/禁用了垂直边框,放置在那里的精灵(如果有)就会变得可见。这就是你如何将图形呈现出来的。 - BerggreenDK
1
不确定你的计算是否正确,Skizz。例如,增加一个非零页内存位置的指令需要6个周期。或将累加器存储到内存的指令需要4个周期。一个机器周期是8个高分辨率像素(或4个多彩像素)的栅格时间。 - Героям слава

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接