27 December 2018, 12:48 | #1 |
Registered User
Join Date: Dec 2013
Location: Auckland
Posts: 3,542
|
Kiwi's Tale dev notes
Just some rough notes about how I made the underlying engine. Happy to answer any questions.
Scrolling is 8-way using a hybrid splitscreen (vertical) and corkscrew (horizontal) setup - so each tile is blitted once during a scroll, there's no "blitting at both sides of the screen" techniques used. Corkscrew has been covered elsewhere so I'll just briefly touch on it here - the core idea is that your bitmap is treated as one long bitmap, wrapping around to the next line on the edges (when the display goes off the right end of the bitmap, it wraps around to the left but on the next line down). Something I found useful to do was to alter the _xclip and _yclip values of the bitmap so that I could blit with the debugger attached and not have Blitz complain that I was blitting over the x-edge. Code:
;Fake increase the X clip size *b.bitmap = Addr Bitmap(2+i) *b\_xclip = $7FFF Code:
wrappedCameraY = CameraY mod #BufferHeight DisplayBitMap #CopList_Main,FrontBuffer,CameraX,wrappedCameraY splitline = #BufferHeight - wrappedCameraY CopperReset #CopList_Main, 0 ;if we're displaying over the bottom edge if splitline < 208 ;scroll to the right position splitoffset = CameraX / 8 ;wait until the bottom edge CopperWait 0, splitline + 44 ;reset the display to the top of the bitmap bp.l = *b\_data[0] + splitoffset CopperMove $0e0, bp lsr 16 CopperMove $0e2, bp & $ffff bp.l = *b\_data[1] + splitoffset CopperMove $0e4, bp lsr 16 CopperMove $0e6, bp & $ffff bp.l = *b\_data[2] + splitoffset CopperMove $0e8, bp lsr 16 CopperMove $0ea, bp & $ffff bp.l = *b\_data[3] + splitoffset CopperMove $0ec, bp lsr 16 CopperMove $0ee, bp & $ffff bp.l = *b\_data[4] + splitoffset CopperMove $0f0, bp lsr 16 CopperMove $0f2, bp & $ffff else ;If we're not displaying over the bottom edge, just wipe out our copper commands CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 CopperMove $1fe, 0 endif Blitting itself adds complications, the best solution I could come up with for blitting near the edge of the bitmap was: Code:
wrappedy = y mod #BufferHeight yoverscan = (wrappedy + 32) - #BufferHeight if yoverscan > 0 ClipBlit frame, x, wrappedy ClipBlit frame, x, -32 + yoverscan else Blit frame, x, wrappedy endif - Here, "x" is the real world value of the object - even if it's outside of the width bounds of the bitmap, due to the corkscrew effect it'll correctly blit to the right place, which is why I haven't used mod on the value (unlike the "y") - Because I've increased the xclip value to the maximum possible word length, the ClipBlit function will correctly handle blitting over the right side edge. I also used interleaved bitmaps (covered in my demos pack), and I borrowed a few tricks from other developers for redrawing the display. Essentially, I didn't use the QBlit or BBlit commands, every time I drew an enemy I simply did the standard Blit command and "flagged" the underlying tile as dirty and needed to be redrawn from the tileset. The simple way to do this would be to have a two dimensional array of X * Y tiles and loop through the lot to see if a tile is flagged as dirty. But there's a far faster way to handle it than to simply loop through each X and Y value to check if it's dirty (and thus needs redrawing). You simply have a one dimensional array of words corresponding to each row, and flag the bit corresponding to the column value. That way, if an entire row doesn't need to be redrawn, you can skip checking it entirely. Example Code:
;This sets the X/Y tile as dirty BlocksDirty(y) = BlocksDirty(y) BITSET x ;This checks to see if there's ANY dirty tiles on that entire row if BlocksDirty(y) ;Finally, we can loop through the X values for x = 0 to 15 if BlocksDirty(y) BITTST x ;redraw this block if we reach here endif next endif |
27 December 2018, 14:08 | #2 |
Inviyya Dude!
Join Date: Sep 2016
Location: Amiga Island
Posts: 2,773
|
Thanks for explaining, Earok...
Always nice to read how other devs do their thing... |
27 December 2018, 15:11 | #3 |
Banana
Join Date: Jul 2016
Location: Darmstadt
Posts: 1,214
|
Nice, thanks!
For dirty object management... A thing I did in Father Christmas vs Dinosaurs was to maintain a list of objects needing updates. You just push objects as you find them and iterate the list once at the end - no looping through arrays checking an "is dirty" flag. Use KillItem to remove the things as you go through doing the updates. In my case I had an array of all possible dinosaurs, and a list of pointers maintaining which ones are active. |
27 December 2018, 15:27 | #4 | |
Banana
Join Date: Jul 2016
Location: Darmstadt
Posts: 1,214
|
Quote:
Spiral scroll I will never understand though. |
|
27 December 2018, 21:57 | #5 | |
Registered User
Join Date: Dec 2013
Location: Auckland
Posts: 3,542
|
Quote:
But there's likely ways to optimise what I did. The modulus thing is interesting.. no, the buffer sizes aren't powers of 2, and I'd increase ram consumption a fair bit to make them power of 2 unfortunately (from memory the width of the bitmap is 384, the height is 320). |
|
27 December 2018, 22:57 | #6 |
Warhasneverbeensomuchfun
Join Date: Jun 2001
Location: Rio de Janeiro / Brazil
Age: 41
Posts: 3,450
|
If you blit everything at the end of the frame and do no CPU operations inbetween you are wasting CPU cycles.
I usually try to blit between CPU operations. Execute AI code for the object, then blit, then move to next one, excute AI code, etc. From what people told me, and I also have seen, when you ask the blitter to blit something, the program continue its flow up until it finds another blitter request, regardless if the Blitter is still doing its stuff or not. If the next blitter request happens while the blitter is still busy, program flow will stop and wait until blitter is available. So if you just blit a lot of stuff at the end of the frame, CPU will be hanging waiting until all the blitter stuff is done. I am pretty sure Blitter experts can use this to have very optimized use of the blitter. But it seems to be a good idea to have instructions between each blit you do. |
27 December 2018, 23:25 | #7 |
Registered User
Join Date: Oct 2017
Location: Sunderland, England
Posts: 2,702
|
I'm no BB coder but I guess some concepts are the same.
Reading Earok's routines there mine differ slightly in asm but you might be able to put them in BB. It's best to keep a third pristine copy of the screen in memory with no sprites for a fast rebuild of the screen, with this whenever you plot a sprite you simply store its offset from the top of the screen then when it comes to redrawing you just restore the screen pointer, add the offset then copy the tile block from the pristine screen. |
27 December 2018, 23:57 | #8 |
Registered User
Join Date: Dec 2013
Location: Auckland
Posts: 3,542
|
Hope this all makes sense
@shatterhand To be clear, I'm not blitting everything at the end of the frame. I blit the dirty tiles first, and then the do the enemy logic (which includes both blitting and movement etc). Since the blits are interleaved, it means that the CPU doesn't have to wait for each of the bitplanes to be blitted before triggering the blit of the next blitplane. But I haven't experimented with blitnasty to see if that helps things along, to be honest I'm not sure if it's necessary or how to properly use it. @mcgeezer having a triple buffer has been my standard in the past (or using dual playfield where you can just simply clear the bobs on the top layer), but I haven't needed it with this - I simply need to blit the tile twice (front and back buffer) rather than three times with the third buffer, and when I redraw I simply redraw the original tile rather than blit from the third buffer. |
28 December 2018, 00:48 | #9 |
Banana
Join Date: Jul 2016
Location: Darmstadt
Posts: 1,214
|
Both approaches are equivalent - blitting from an unmodified source, be it a bitmap or tile. As long as you have a quick way to determine which tile is used per coordinate.
I was thinking further about the mod. It's one of my favourite instructions but boy is it slow. It occurred to me that you could fit a look up table in 300 or so bytes (320 mod anything > 1 would fit in a .byte), using the y position as the array index. Perhaps not worth it for one mod per frame but if you're doing it a lot, maybe something to think about. |
28 December 2018, 01:07 | #10 | |
Registered User
Join Date: Dec 2013
Location: Auckland
Posts: 3,542
|
Quote:
I'm using mod a fair bit for different things, and I didn't realise how slow it was, so I'll definitely look at something like that on the next pass. Cheers. I guess something like this could work output = lookuptable(input & $1ff) Which would work for any sized input, so long as the table held the results for 512 values. The approaches are a bit different in that you're saving memory (two image buffers instead of three) and blitting (when you're scrolling, you only need to blit the new tile to two buffers instead of three). But there's advantages to the three buffer approach too. |
|
28 December 2018, 02:07 | #11 | |
Warhasneverbeensomuchfun
Join Date: Jun 2001
Location: Rio de Janeiro / Brazil
Age: 41
Posts: 3,450
|
Quote:
|
|
28 December 2018, 13:37 | #12 |
Registered User
Join Date: Nov 2016
Location: France
Posts: 854
|
why did you choose to zoom in on the amiga version? The screen is bigger on the other versions. This is for technical or aesthetic reasons?Thank you for this game, the animation of the character is good
|
28 December 2018, 20:34 | #13 |
Registered User
Join Date: Dec 2013
Location: Auckland
Posts: 3,542
|
Hi @Aladin, yes the resolution has been cut for performance reasons.
The original game was something like 640x480. While the AGA Amiga is perfectly capable of that resolution, it'd be slower due to how much would need to be blitted during scrolling and gameplay, also I'd need to burn up much more chipram for the buffers. On top of that, to get the vertical resolution, I'd need to put it in interlace mode. Yuck. To be honest, I think the game is actually better in the smaller screen display. There's not as much stuff going on at once, the action is much more tightly compressed around the player (rather than constantly having vehicles coming at you from far away from the player) |
22 February 2019, 11:53 | #14 | |
Registered User
Join Date: Oct 2008
Location: Finland
Posts: 643
|
Quote:
As a first step in creating something similar I'm trying to just achieve a copper split at a specific position (rolling screen). I'm doing something wrong I guess, the screen is garbled. This is what it looks like before "scrolling": And this is what it looks like when I have scrolled a little (wrappedCameraY > 0): Any idea what I have missed or done wrong earok? Relevant code: Code:
BitMap 0,320,240,5 InitCopList 0,44,208,$15,8,32,0 ; y=44, lores, smoothscroll, 5 bitplanes, 8 CreateDisplay 0 DisplayPalette 0,0 Use Bitmap 0 wrappedCameraY=0 splitoffset=0 splitline=0 Repeat VWait 1 DisplayBitMap 0,0,0,wrappedCameraY splitline = 240 - wrappedCameraY CopperReset 0, 0 ; Get the pointer to the front buffer bitmap *b.bitmap = Addr Bitmap(0) *b\_xclip = $7FFF ; Are we displaying over the bottom edge If splitline < 208 ; Asume x-position at 0 for now splitoffset = 0 ; Wait until the bottom edge CopperWait 0, splitline + 44 ; Reset the display to the top of the bitmap ; .. Same as in earoks post above Else ; If we're not displaying over the bottom edge, just wipe out our copper commands ; ... Same as in earoks post above EndIf If RawStatus(#KEY_DOWN) wrappedCameraY+1 If wrappedCameraY>#SCR_H Then wrappedCameraY=0 EndIf Until RawStatus($45)=-1 Last edited by MickGyver; 22 February 2019 at 12:03. |
|
22 February 2019, 12:00 | #15 | |
Registered User
Join Date: Dec 2013
Location: Auckland
Posts: 3,542
|
Quote:
|
|
22 February 2019, 12:07 | #16 | |
Registered User
Join Date: Oct 2008
Location: Finland
Posts: 643
|
Quote:
EDIT: Hopefully I will figure it out before you put any time into it. |
|
22 February 2019, 12:18 | #17 |
Registered User
Join Date: Dec 2013
Location: Auckland
Posts: 3,542
|
Looking at the screenshots, it seems the copper commands extend past line 255 without properly being handled. But looking at the code, it seems everything is done before line 255. So I'm not quite certain. When I'm next at my laptop, I'll take a look if you haven't solved it already.
|
22 February 2019, 13:45 | #18 | |
Registered User
Join Date: Oct 2008
Location: Finland
Posts: 643
|
Quote:
Code:
CopperWait 0, 208 + 44 CopperEnd (By the way if you want, you can answer me in the "Tilemap Scrolling Demos" thread, I don't want to hijack your Kiwi's Tale dev thread) |
|
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
Thread Tools | |
Similar Threads | ||||
Thread | Thread Starter | Forum | Replies | Last Post |
The Kiwi's Tale - Full length platformer for CD32 | earok | project.Amiga Game Factory | 82 | 17 April 2019 23:26 |
Stickies - Sticky notes | BippyM | support.WinUAE | 1 | 06 August 2013 22:57 |
Work In Progress notes - WinUAE (v1.0) | Carlos Ace | News | 11 | 09 May 2005 11:08 |
|
|