R-Type 2
R-Type 2 uses the Amiga sprites to create a 64 pixel wide repeating pattern for the backgrounds on most levels. If sprites are disabled, the game looks almost identical to the Atari ST version. First up, here is midway through level 1 with sprites enabled on the left, and disabled on the right:
When sprites are disabled, you just see black pixels in the Bydo base.
Interestingly, at the start of the level sprites are not enabled at all, but when you approach the Bydo base, the sprite playfield will suddenly appear, covering the entire screen:
Curiously enough, the sprite layer scrolls onto the screen at the same speed as the background until the sprite layer completely covers the screen, then moves at half the speed to give a parallax effect. Have a look at approximately 1 minute 18 seconds in the following longplay to see the effect:
By examining memory, we can extract the sprites used for the pattern:
Those with a keen eye might be able to see that the pattern repeats every 64 pixels. Displaying them in blocks of 64 pixels shows each combination:
Only a selection is shown here, the full image is actually 1024 pixels across x 192 pixels high, which uses up 50kb of chip memory. Because there are 16 different indent levels to give pixel accurate scrolling, 64 pixels wide x 16 increments = 1024 pixels width required.
Amiga sprites are 16 pixels wide so 4 of them are required to draw a vertical strip of the background. Because each pair of sprites have their own colour palette, the game duplicates the sprite colours for all 4 sprite pairs:
Because the background pattern is 64 pixels wide, the game could multiplex the 4 sprites completely across the screen and only require 4 sprites. That would free up the remaining sprites for something else. For whatever reason, programmer Tim Round decided to multiplex all 8 across.
The Copperlist
The copperlist begins by setting up all 8 sprite pointers. Note that sprite 4 is set to the same address as sprite 0, ditto with 5 and 1, 6 and 2, and 7 and 3:
Chip Addr: Copper instructions ; Comments -------------------------------------------------- $00010378: 0096 87E0 ; DMACON = 0x87E0 $0001037C: 0120 0006 0122 3610 ; SPR0PT = 0x00063610 $00010384: 0124 0006 0126 3918 ; SPR1PT = 0x00063918 $0001038C: 0128 0006 012A 3000 ; SPR2PT = 0x00063000 $00010394: 012C 0006 012E 3308 ; SPR3PT = 0x00063308 $0001039C: 0130 0006 0132 3610 ; SPR4PT = 0x00063610 $000103A4: 0134 0006 0136 3918 ; SPR5PT = 0x00063918 $000103AC: 0138 0006 013A 3000 ; SPR6PT = 0x00063000 $000103B4: 013C 0006 013E 3308 ; SPR7PT = 0x00063308
Now the game sets up the display properties, the bitplane pointers and the entire colour palette:
$000103BC: 0092 0038 ; DDFSTRT = 0x0038 $000103C0: 0094 00C8 ; DDFSTOP = 0x00C8 $000103C4: 008E 3F91 ; DIWSTRT = 0x3F91 $000103C8: 0090 0FB1 ; DIWSTOP = 0x0FB1 $000103CC: 3D09 FFFE ; Wait for vpos >= 0x3D and hpos >= 0x08 $000103D0: 0100 4200 ; BPLCON0 = 0x4200 $000103D4: 0102 00FF ; BPLCON1 = 0x00FF $000103D8: 0104 0000 ; BPLCON2 = 0x0000 $000103DC: 0108 007A ; BPL1MOD = 0x007A $000103E0: 010A 007A ; BPL2MOD = 0x007A $000103E4: 00E0 0007 00E2 7400 ; BPL1PT = 0x00077400 $000103EC: 00E4 0007 00E6 7428 ; BPL2PT = 0x00077428 $000103F4: 00E8 0007 00EA 7450 ; BPL3PT = 0x00077450 $000103FC: 00EC 0007 00EE 7478 ; BPL4PT = 0x00077478 $00010404: 0180 0000 ; COLOR00 = 0x0000 - Transparent colour $00010408: 0182 0A74 ; COLOR01 = 0x0A74 $0001040C: 0184 0CA7 ; COLOR02 = 0x0CA7 $00010410: 0186 0CB9 ; COLOR03 = 0x0CB9 $00010414: 0188 0EDB ; COLOR04 = 0x0EDB $00010418: 018A 0666 ; COLOR05 = 0x0666 $0001041C: 018C 0AAA ; COLOR06 = 0x0AAA $00010420: 018E 0DDD ; COLOR07 = 0x0DDD $00010424: 0190 0000 ; COLOR08 = 0x0000 $00010428: 0192 00DF ; COLOR09 = 0x00DF $0001042C: 0194 005F ; COLOR10 = 0x005F $00010430: 0196 0C00 ; COLOR11 = 0x0C00 $00010434: 0198 0FB0 ; COLOR12 = 0x0FB0 $00010438: 019A 0080 ; COLOR13 = 0x0080 $0001043C: 019C 0D06 ; COLOR14 = 0x0D06 $00010440: 019E 0FFF ; COLOR15 = 0x0FFF $00010444: 01A0 0000 ; COLOR16 = 0x0000 - Sprite 0 and 1 colours $00010448: 01A2 0653 ; COLOR17 = 0x0653 $0001044C: 01A4 0774 ; COLOR18 = 0x0774 $00010450: 01A6 0996 ; COLOR19 = 0x0996 $00010454: 01A8 0000 ; COLOR20 = 0x0000 - Sprite 2 and 3 colours $00010458: 01AA 0653 ; COLOR21 = 0x0653 $0001045C: 01AC 0774 ; COLOR22 = 0x0774 $00010460: 01AE 0996 ; COLOR23 = 0x0996 $00010464: 01B0 0000 ; COLOR24 = 0x0000 - Sprite 4 and 5 colours $00010468: 01B2 0653 ; COLOR25 = 0x0653 $0001046C: 01B4 0774 ; COLOR26 = 0x0774 $00010470: 01B6 0996 ; COLOR27 = 0x0996 $00010474: 01B8 0000 ; COLOR28 = 0x0000 - Sprite 6 and 7 colours $00010478: 01BA 0653 ; COLOR29 = 0x0653 $0001047C: 01BC 0774 ; COLOR30 = 0x0774 $00010480: 01BE 0996 ; COLOR31 = 0x0996
Now for a small trick which explains how the sprites are turned off at the start of the level but instantly appear! At the start of the level, the 1st copper pointer is set to address $146d4, which happens to skip the entire sprite display section! The copperlist becomes active by strobing the jump address:
$00010484: 0080 0001 0082 46D4 ; COP1LC = 0x000146D4 $0001048C: 0084 00FF ; COPJMP1 = 0x00FF - Copper jumps to $146d4
When the sprite layer is required, the game alters the word at $1048c, inserting a no-operation instruction instead:
$00010484: 0080 0001 0082 46D4 ; COP1LC = 0x000146D4 $0001048C: 01FE 00FF ; NO-OP = 0x00FF - Copper continues to next instruction
Now the horizontal sprite multiplexing trick is setup on every line of the display. All 8 sprites are positioned 16 pixels from each other, giving an initial width of 128 pixels.
$00010490: 0140 3F48 ; SPR0POS = 0x3F48 $00010494: 0148 3F50 ; SPR1POS = 0x3F50 $00010498: 0150 3F58 ; SPR2POS = 0x3F58 $0001049C: 0158 3F60 ; SPR3POS = 0x3F60 $000104A0: 0160 3F68 ; SPR4POS = 0x3F68 $000104A4: 0168 3F70 ; SPR5POS = 0x3F70 $000104A8: 0170 3F78 ; SPR6POS = 0x3F78 $000104AC: 0178 3F80 ; SPR7POS = 0x3F80
The copper then waits for the beam to catch up to horizontal position $80, then quickly shifts all the sprites across to their new locations (128 pixels from where they started):
$000104B0: 3F81 FFFE ; Wait for vpos >= 0x3F and hpos >= 0x80 $000104B4: 0140 3F88 ; SPR0POS = 0x3F88 $000104B8: 0148 3F90 ; SPR1POS = 0x3F90 $000104BC: 0150 3F98 ; SPR2POS = 0x3F98 $000104C0: 0158 3FA0 ; SPR3POS = 0x3FA0 $000104C4: 0160 3FA8 ; SPR4POS = 0x3FA8 $000104C8: 0168 3FB0 ; SPR5POS = 0x3FB0 $000104CC: 0170 3FB8 ; SPR6POS = 0x3FB8 $000104D0: 0178 3FC0 ; SPR7POS = 0x3FC0
The game has now displayed 256 pixels worth of sprites, but the display is 288 pixels wide, so two more sprites are required on each line. The game waits for the beam to reach position $c0 and repositions the final 2 sprites:
$000104D4: 3FB1 FFFE ; Wait for vpos >= 0x3F and hpos >= 0xB0 $000104D8: 3FC1 FFFE ; Wait for vpos >= 0x3F and hpos >= 0xC0 $000104DC: 01FE 0000 ; NO-OP = 0x0000 $000104E0: 0140 3FC8 ; SPR0POS = 0x3FC8 $000104E4: 0148 3FD0 ; SPR1POS = 0x3FD0
In actual fact, the wait at this location is completely unnecessary because by the time the 8 sprites above are repositioned, the beam will have already displayed the first 2 sprites, so they could be moved directly after the first 8!
As you can see, the next line is identical except for a different vertical location so I will not bother pasting the other 190 lines of the display:
$000104E8: 0140 4048 ; SPR0POS = 0x4048 $000104EC: 0148 4050 ; SPR1POS = 0x4050 $000104F0: 0150 4058 ; SPR2POS = 0x4058 $000104F4: 0158 4060 ; SPR3POS = 0x4060 $000104F8: 0160 4068 ; SPR4POS = 0x4068 $000104FC: 0168 4070 ; SPR5POS = 0x4070 $00010500: 0170 4078 ; SPR6POS = 0x4078 $00010504: 0178 4080 ; SPR7POS = 0x4080 $00010508: 4081 FFFE ; Wait for vpos >= 0x40 and hpos >= 0x80 $0001050C: 0140 4088 ; SPR0POS = 0x4088 $00010510: 0148 4090 ; SPR1POS = 0x4090 $00010514: 0150 4098 ; SPR2POS = 0x4098 $00010518: 0158 40A0 ; SPR3POS = 0x40A0 $0001051C: 0160 40A8 ; SPR4POS = 0x40A8 $00010520: 0168 40B0 ; SPR5POS = 0x40B0 $00010524: 0170 40B8 ; SPR6POS = 0x40B8 $00010528: 0178 40C0 ; SPR7POS = 0x40C0 $0001052C: 40B1 FFFE ; Wait for vpos >= 0x40 and hpos >= 0xB0 $00010530: 40C1 FFFE ; Wait for vpos >= 0x40 and hpos >= 0xC0 $00010534: 01FE 0000 ; NO-OP = 0x0000 $00010538: 0140 40C8 ; SPR0POS = 0x40C8 $0001053C: 0148 40D0 ; SPR1POS = 0x40D0
The final part of the copperlist sets up the score panel at the bottom of the screen by switching to different bitplane pointers, a new palette and then preparing for the next frame:
$00014690: FF09 FFFE ; Wait for vpos >= 0xFF and hpos >= 0x08 $00014694: 0102 0000 ; BPLCON1 = 0x0000 $00014698: 00E0 0007 00E2 F600 ; BPL1PT = 0x0007F600 $000146A0: 00E4 0007 00E6 F628 ; BPL2PT = 0x0007F628 $000146A8: 00E8 0007 00EA F650 ; BPL3PT = 0x0007F650 $000146B0: 00EC 0007 00EE F678 ; BPL4PT = 0x0007F678 $000146B8: 0709 FFFE ; Wait for vpos >= 0x07 and hpos >= 0x08 $000146BC: 0190 0FFF ; COLOR08 = 0x0FFF $000146C0: 0182 0CFF ; COLOR01 = 0x0CFF $000146C4: 0184 0CFF ; COLOR02 = 0x0CFF $000146C8: 0080 0001 0082 0378 ; COP1LC = 0x00010378 $000146D0: FFFF FFFE ; Wait for vpos >= 0xFF and hpos >= 0xFE ; End of copperlist
At the start of the level when the sprites are disabled, the copper is pointed to $146d4, which sets up a different display, again with a jump instruction swapping between a no-op to skip part of the code:
$000146d4: 0080 0001 0082 46f0 ; COP1LC := 0x000146f0 $000146dc: 01fe 00ff ; NO-OP(NULL) := 0x00ff - Jump to $146f0 $000146e0: 0096 0020 ; DMACON := 0x0020 $000146e4: 0080 0001 0082 4690 ; COP1LC := 0x00014690 $000146ec: 0088 00ff ; COPJMP1 := 0x00ff - Jump to $14690 $000146f0: 0096 0020 ; DMACON := 0x0020 $000146f4: ff09 fffe ; Wait for vpos >= 0xff and hpos >= 0x08 $000146f8: 0102 0000 ; BPLCON1 := 0x0000 $000146fc: 00e0 0007 ; BPL1PTH := 0x0007 $00014700: 00e2 f600 ; BPL1PTL := 0xf600 $00014704: 00e4 0007 ; BPL2PTH := 0x0007 $00014708: 00e6 f628 ; BPL2PTL := 0xf628 $0001470c: 00e8 0007 ; BPL3PTH := 0x0007 $00014710: 00ea f650 ; BPL3PTL := 0xf650 $00014714: 00ec 0007 ; BPL4PTH := 0x0007 $00014718: 00ee f678 ; BPL4PTL := 0xf678 $0001471c: 0182 0000 ; COLOR01 := 0x0000 $00014720: 0184 0000 ; COLOR02 := 0x0000 $00014724: 0186 0000 ; COLOR03 := 0x0000 $00014728: 0188 0000 ; COLOR04 := 0x0000 $0001472c: 018a 0000 ; COLOR05 := 0x0000 $00014730: 018c 0000 ; COLOR06 := 0x0000 $00014734: 018e 0000 ; COLOR07 := 0x0000 $00014738: 0190 0000 ; COLOR08 := 0x0000 $0001473c: 0192 0000 ; COLOR09 := 0x0000 $00014740: 0194 0000 ; COLOR10 := 0x0000 $00014744: 0196 0000 ; COLOR11 := 0x0000 $00014748: 0198 0000 ; COLOR12 := 0x0000 $0001474c: 019a 0000 ; COLOR13 := 0x0000 $00014750: 019c 0000 ; COLOR14 := 0x0000 $00014754: 019e 0000 ; COLOR15 := 0x0000 $00014758: 0080 0001 ; COP1LCH := 0x0001 $0001475c: 0082 0378 ; COP1LCL := 0x0378 $00014760: ffff fffe ; Wait for vpos >= 0xff and hpos >= 0xfe ; End of Copperlist
Popular Sprite Tricks
2 sprites are displayed then repositioned horizontally right across the screen to create the colourful static background. The remaining sprites are used for the main character, the status bar and player bullets.
The 16-colour background layer was created by using all 8 hardware sprites and repositioning them across the screen. The same 64 pixel wide graphics are repeated across the entire play area.
One of the first jaw-droppingly beautiful Amiga games that still looks great today. Sprites were heavily re-used on the screen, along with priority changes to make them appear between the playfields.
The amazing tunnel sequence was created with a 6 frame animation sequence made with only 4 colours and mirrored vertically. The asteroid layer sits on top of this, with a status panel and player ship made of sprites sitting on top.
Post your comment
Comments
Is there also a repeating pattern to the tan-colored foreground? I really like the layout of the "hi-tech" Bydo machinery, but it does not seem to repeat.
Hepativore 28/02/2022 8:12pm (3 years ago)
The os only lets you multiplex sprites vertical, to give copper and bitplane dma plenty of time.
Horizontal multiplex in hardware is very easy. But as bitplanes depth increases and copper usage and multiplex of a sprite horizontal becomes very tricky. Timing critical and freedom of positioning becomes hard.
Hence os keeps it simple and compatible
Only display once a scan line.
Mindvoid 23/03/2021 10:35am (4 years ago)
Sodapop and Peter: Yes, you simply shift the position of the sprite sideways after it's been displayed. For example SPR0POS is set to 0x3f48, and after setting the other sprites, it's shifted further to the right by setting SPR0POS to 0x3f88 meaning it gets drawn again. And one final time at 0x3fc8, so it's drawn 3 times per line. The code is right there in the copperlist, that's all you need to do.
Codetapper 12/09/2018 6:22am (6 years ago)
Same as Sodapop I also though sprites can be multiplexed vertically but not horizontally. Could someone explain pls?
Peter 08/09/2018 1:51pm (6 years ago)
"Curiously enough, the sprite layer scrolls onto the screen at the same speed as the background until the sprite layer completely covers the screen, then moves at half the speed to give a parallax effect."Same as the arcade version.
VincentGR 03/09/2016 1:22pm (8 years ago)
There's one thing I find hard to understand in this sprite trick...
I thought sprites could be multiplexed vertically only, and not horizontally as it seems to be explained here (or I misread the whole thing). Do you really mean that a same sprite can be repeated a same scanline ???
Sodapop 14/09/2014 4:50pm (10 years ago)
When I see this huge copper lists, I'm always wondering why nobody ever seemed to do copper loops by using the vertical position compare enable bits and writes to cop1lc, even though there's an example in the hardware reference manual... no raster time for the additional SKIP + MOVE?
guest 28/04/2014 1:18pm (11 years ago)
Very interesting article ! Thank you dude !!
Sodapop 23/03/2014 5:30pm (11 years ago)
No one has commented on this page yet.
RSS feed for comments on this page | RSS feed for all comments