; A ball that goes behind some pillars and in front of others. ; ; Four pillars, each ONE 8 by 8 TILE stretched to its own width and height, at four different ; distances. One ball, moving across all of them. Which pillars it passes behind is decided ; per screen column by the depth buffer, and that is the thing worth watching: the ball is in ; front of the far pillars and behind the near ones IN THE SAME FRAME, without the program ; sorting anything or drawing anything twice. ; ; ---- Why that needs a buffer rather than an ordering ---- ; ; Sprites are drawn in table order, so a program can put one in front of another by numbering ; them. That is enough when things are flat. It stops being enough the moment a thing is ; nearer than one part of the scenery and further than another - a ball halfway past a pillar ; is in front of the floor beside it and behind the pillar itself, and there is no order of ; two sprites that means "behind, on those columns only". ; ; So the scenery says how far away it is, a column at a time, and the ball says how far away ; IT is. The device compares them per pixel. ; ; Written by Anachronaut #Include services.asm #Program #Base 0x5000 start: ; The atlas: the tiles, the sprite table and the depth buffer are all in it. INIA 0d4 OUTA 0xE3 INIA 0x30 OUTA 0xE2 INIA 0x03 OUTA 0xE8 SETD.0 Message SWI osPrintString INIA 0x0A OUTA 0x00 CALL putArt CALL clearDepth CALL putPillars CALL putBall INIA 0x01 OUTA 0x02 ; Key mode. everyFrame: CALL waitFrame CALL stepBall CALL moveBall INA 0x01 INIB 0x01 ; READY AND BRQ everyFrame INA 0x00 RSTA OUTA 0x02 ; Line mode. The sprites and the buffer are the system's to clear. SWI osExit ; ---- Two tiles ---- ; ; Tile 200 is the ball, which has a shape and so comes out of the Data Segment. Tile 201 is ; the pillar, which is a solid block and so is a Fill: 64 bytes of index one, no art needed. putArt: INIA 0x01 OUTA 0xE0 ; SourceBank: Data Memory. SETD.1 BallArtAt SETD.0 BallArt STD.0.1 LDA.1 OUTA 0xE1 INCD.1 LDA.1 OUTA 0xE2 INIA 0d4 OUTA 0xE3 INIA 0x32 OUTA 0xE4 ; Tile 200 is at 200 times 64, which is 0x3200. RSTA OUTA 0xE5 OUTA 0xE6 INIA 0x40 OUTA 0xE7 INIA 0x01 OUTA 0xE8 ; Blit. INIA 0x32 OUTA 0xE4 INIA 0x40 OUTA 0xE5 ; Tile 201 is 64 bytes further on, at 0x3240. INIA 0x01 OUTA 0xE2 ; Index ONE, which is the only index a scheme gives a colour to: ; the default palette sets each scheme's paper and ink and ; nothing between them. RSTA OUTA 0xE6 INIA 0x40 OUTA 0xE7 INIA 0x02 OUTA 0xE8 ; Fill. RET ; ---- The buffer, emptied ---- ; ; Nought in a column means nothing is there. It is the program's buffer and nobody clears it ; between programs, so a program that means "nothing yet" has to say so. clearDepth: INIA 0d4 OUTA 0xE3 INIA 0xD0 OUTA 0xE4 RSTA OUTA 0xE5 OUTA 0xE2 ; Fill takes the byte it writes from SourceLow. INIA 0x02 OUTA 0xE6 INIA 0x80 OUTA 0xE7 ; 640 bytes, one a column. INIA 0x02 OUTA 0xE8 RET ; ---- The pillars ---- ; ; Four records of seven bytes: x, y, height, depth. Each becomes a sprite entry and a run of ; the depth buffer saying how far away that part of the scenery is. putPillars: INIA 0d4 SETD.0 PillarLeft STA.0 ; Sprite one is at 0xC010; sprite nought is the ball. INIA 0x10 SETD.0 EntryLow STA.0 SETD.3 Pillars putOnePillar: ; ---- The entry, written straight through ---- ; ; Sixteen bytes out of one port with the address named once, because the controller's Data ; port steps on after every byte. DP3 walks the record and survives the calls below. INIA 0d4 OUTA 0xE3 INIA 0xC0 OUTA 0xE4 SETD.0 EntryLow LDA.0 OUTA 0xE5 INIA 0xC9 OUTA 0xE9 ; Tile 201, the pillar block. INIA 0x01 OUTA 0xE9 ; Attribute one. ; Kept as they go past, because putPillarDepth needs the column and a Data Pointer cannot ; be walked backwards to find it again. LDA.3 OUTA 0xE9 ; X low. SETD.0 PillarXLow STA.0 INCD.3 LDA.3 OUTA 0xE9 ; X high. SETD.0 PillarXHigh STA.0 INCD.3 LDA.3 OUTA 0xE9 ; Y low. INCD.3 LDA.3 OUTA 0xE9 ; Y high. INCD.3 INIA 0x11 OUTA 0xE9 ; One tile by one, which is the art. The size below is the look. RSTA OUTA 0xE9 ; No flags. INIA 0d32 OUTA 0xE9 RSTA OUTA 0xE9 ; Thirty two pixels wide, whatever the tile is. LDA.3 OUTA 0xE9 ; Height low. INCD.3 LDA.3 OUTA 0xE9 ; Height high. INCD.3 ; ---- And a depth of NOUGHT, which is not the pillar's distance ---- ; ; It is tempting to put the pillar's own distance here, and it is wrong: the depth buffer ; is what has already been DRAWN, and a sprite's depth is a QUESTION ASKED OF IT. A pillar ; that both wrote 20 into its own columns and carried 20 would be asked whether it was in ; front of itself, and 20 is not nearer than 20, so it would vanish. Which it did. ; ; Scenery writes the buffer. It does not ask. RSTA OUTA 0xE9 CALL putPillarDepth ; On to the next record and the next entry. INCD.3 SETD.0 EntryLow LDA.0 INIB 0d16 CCF ADD STQ.0 SETD.0 PillarLeft LDA.0 DECA STA.0 BNA putOnePillar RET ; ---- How far away those thirty two columns are ---- ; ; A Fill, because the whole run is one number: the pillar is flat on, so every column of it ; is the same distance. DP3 is on the depth byte of the record and stays there. ; ; The buffer begins at 0xD000, whose low byte is nought - so the column's low byte IS the ; address's low byte, and its high byte only has to be added to 0xD0. No sixteen bit sum. putPillarDepth: LDA.3 OUTA 0xE2 ; The byte to fill with, which is the depth. INIA 0d4 OUTA 0xE3 SETD.0 PillarXHigh LDA.0 INIB 0xD0 CCF ADD OUTQ 0xE4 SETD.0 PillarXLow LDA.0 OUTA 0xE5 RSTA OUTA 0xE6 INIA 0d32 OUTA 0xE7 ; Thirty two columns of it. INIA 0x02 OUTA 0xE8 RET ; ---- The ball ---- ; ; Sprite nought, so it is in front of every pillar as far as the TABLE is concerned. What ; puts it behind some of them is its depth and nothing else, which is the whole point. putBall: INIA 0d4 OUTA 0xE3 INIA 0xC0 OUTA 0xE4 RSTA OUTA 0xE5 INIA 0xC8 OUTA 0xE9 ; Tile 200. INIA 0x02 OUTA 0xE9 ; Attribute two. RSTA OUTA 0xE9 OUTA 0xE9 ; X, low then high. INIA 0d230 OUTA 0xE9 RSTA OUTA 0xE9 ; Y. INIA 0x11 OUTA 0xE9 ; One tile by one. RSTA OUTA 0xE9 ; No flags. INIA 0d48 OUTA 0xE9 RSTA OUTA 0xE9 INIA 0d48 OUTA 0xE9 RSTA OUTA 0xE9 ; Forty eight by forty eight, from eight by eight of art. INIA 0d45 OUTA 0xE9 ; And forty five away, which is between the pillars. RET waitFrame: INA 0x30 INIB 0x01 AND BRQ waitFrame RET ; Two pixels to the right, wrapping past the far side. Sixteen bits in two bytes, so the high ; one steps only when the low one came round to nought. stepBall: SETD.0 BallX LDA.0 INCA INCA STA.0 BNA stepCheck SETD.0 BallXHigh LDA.0 INCA STA.0 stepCheck: SETD.0 BallXHigh LDA.0 INIB 0x02 CCF SUB BNQ stepDone ; Not past 0x0200, so nowhere near the end. RSTA SETD.0 BallX STA.0 SETD.0 BallXHigh STA.0 stepDone: RET moveBall: INIA 0d4 OUTA 0xE3 INIA 0xC0 OUTA 0xE4 INIA 0x02 OUTA 0xE5 ; X is bytes two and three of the entry. SETD.0 BallX LDA.0 OUTA 0xE9 SETD.0 BallXHigh LDA.0 OUTA 0xE9 RET #Data #Base 0x3000 Message: "A ball, behind the near pillars and in front of the far ones. Press a key." ; x, y, height, depth. Nearer pillars are taller, which is the only thing making this look ; like distance rather than like four bars. Pillars: 0x50 0x00 0x64 0x00 0xF0 0x00 0d20 ; x 80, y 100, 240 tall, near 0xC8 0x00 0xDC 0x00 0x78 0x00 0d60 ; x 200, y 220, 120 tall, far 0x68 0x01 0xA0 0x00 0xB4 0x00 0d30 ; x 360, y 160, 180 tall, nearish 0xF4 0x01 0x04 0x01 0x50 0x00 0d80 ; x 500, y 260, 80 tall, furthest ; Where putPillarDepth reads the column from. DP3 is on the record's depth byte by then, and ; a Data Pointer cannot be read backwards, so the two halves are kept here as they go past. PillarXLow: 0x00 PillarXHigh: 0x00 PillarLeft: 0x00 EntryLow: 0x00 BallArt: 0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00 0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00 0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00 BallArtAt: #Reserve 0d2 BallX: 0x00 BallXHigh: 0x00