17 October 2019, 22:07 | #1 |
Registered User
Join Date: Oct 2017
Location: Sunderland, England
Posts: 2,702
|
Game Programming Framework
So after a fair bit of thought on what to do next, I've long set my heart on doing some Game programming tutorial streams for the Amiga.
With that said, looking back I made many many mistakes when coding Rygar... things that were just bad habits or because I was under a time constraint to finish the game for the competition. So what I'd like to do is come up with a set of assembler functions that facilitate making game coding easier, while the implementation will be assembler I'll try and comform to high level languages like C (although I've never written any C before). So here goes... I've documented (but not written) a couple of functions to set the tone, the idea being writing the actual function will be the assembler tutorial and the hope is that a picture of how to build a game will slowly come across to following...with better games being made for the Amiga. Here are the off the cuff high level functions (just a brain dump)...I have more to add. Code:
[+] rawHandle = amgLoadAndUnpackRncAsset(*name, bufsize, memtype) [+] assetHandle = amgParseAsset(rawHandle) [+] cameraHandle = amgCreateScreenBuffer(XSize, YSize, BitPlanes) Allocate screen memory, Creates Bitplane Handles [+] copperHandle = amgCreateInitalCopperList(cameraHandle,paletteHandle) Build a default copper list from a camera handle. [+] spriteSheetHandle = amgGenerateSpriteSheet(assetHandle, *Buffer, SpriteCutSize, MaxCuts, bool Mask) Build a list of pointers to the sprites and their masks [+] spriteHandle = amgCreateSprite(spriteSheetHandle) [+] result = amgSetSpriteHandler(spriteHandle, *Address, State) [+] result = amgSetPlayerSprite(spriteHandle *Address) [+] result = amgDestroySprite(spriteHandle) [+] spriteAnimHandle = amgCreateSpriteAnimation(spriteHandle, spriteAnimationType) [+] result = amgAppendSpriteAnimationFrame(spriteAnimHandle,*Handler, Frame, Speed) [+] result = amgSetSpriteAnimation(spriteAnimHandle, bool [loop|term]) [+] SpriteNumber = amgCastSprite(spriteHandle,initXpos,initYpos,State) [+] result = amgSetSpriteStatus(spriteHandle, bool State) [+] result = amgSetSpriteAnimationSpeed(spriteAnimHandle) [+] tileHandle = amgSetTileSheetAttributes = (assetHandle, sizeX, size Y) [+] result = amgSetTilePlatform(tileId,tileHandle,spriteHandle,maskId) [+] result = amgSetTileObstacle(tileId,tileHandle,spriteHandle,maskId) [+] result = amgSetTileCeiling(tileId,tileHandle,spriteHandle,maskId) [+] mapHandle = amgCreateTileMap(rawHandle,xSize,ySize,size) [+] canvasHandle = amgCreateCanvas(mapHandle, tileHandle) [+] result = amgSetCanvasLimits(canvasHandle,x1,y1,x2,y2) [+] result = amgSetCanvasPlayerLimits(canvasHandle,spriteHandle,x1,y1,x2,y2) [+] amgBlitTileToCamera(canvasHandle,cameraHandle, sourceTile, destTile) [+] copperList = amgDrawCurrentCameraPosition(canvasHandle,cameraHandle) [+] copperListHandle = amgSetCanvasMode(canvasHandle,mode) Mode can be: 0 = Static 1 = Horizontal Bi-Directional 2 = Horizontal Right 3 = Horizontal Left 4 = Vertical Bi-Directional 5 = Vertical Down 6 = Vertical Up 7 = 8 Way 10 = Dual Playfield Static 11 = Dual Playfield Horizontal Bi-Directional 12 = Dual Playfield Horizontal Right 13 = Dual Playfield Horizontal Left 14 = Dual Playfield Vertical Bi-Directional 15 = Dual Playfield Vertical Down 16 = Dual Playfield Vertical Up 17 = Dual Playfield 8 Way [+] amgSetCanvasCameraPosition(canvasHandle,cameraHandle,x1,y1,x2,y2) ; Can use this to scroll around And here's the first two functions documented: Code:
NAME amgLoadAndUnpackRncAsset - Load and unpack file SYNOPSIS rawHandle = amgLoadAndUnpackRncAsset(*name, bufsize, memtype) d0 a0 d0 d1 FUNCTION The file 'name' should be a ProPacked file using method 1 or 2. Memory of the required type and bufsize in bytes is allocated using AmigaDos malloc(), If the supplied bufsize is 0 then the packed file header is read to find the number of first before loading and unpacking the file. The memtype argument can either by MEMF_CHIP to allocate chip ram, MEMF_FAST to allocate Fast Ram or MEMF_ANY to allocate any available ram type, however should fast ram be available as well as chip then fast will be used as a priority. INPUTS name - (a0) pointer to null-terminated file name of asset to load. bufsize - (d0) buffer size in bytes to allocate memtype - (d1) MEMF_CHIP | MEMF_FAST | MEMF_ANY RESULTS rawHandle - (d0) handle code for the loaded asset ERRORS error - (d0) failure to load and unpack the asset will result in any of the following errors. -1 - File not found -2 - Unable to allocate ram type -3 - Unpack failed -4 - Max handles reached Code:
NAME amgParseAsset - Parse and store the information found in a loaded asset SYNOPSIS assetHandle = amgParseAsset(rawHandle) d0 d0 FUNCTION The handle supplied should be the result from calling the 'amgLoadAndUnpackRncAsset' function. The supported assets are Protracker Music Modules, 8SVX Sound samples and uncompressed Amiga ILBM image files. Upon successful execution of the function a new handle is returned with the requested information parsed based on the found asset type. See the AMG handles structure for more information. INPUTS handle (d0) - A handle number that has previously been returned from 'amgLoadAndUnpackRncAsset'. RESULTS assetHandle (d0) - Asset handle number. ERRORS error - (d0) failure to parse information found with the associated handle. -1 - Supplied handle not found -2 - No supported Interchangable File Format header could be found (FORM) -3 - Failed to locate Bitmap Header (BMHD) -4 - Failed to locate Body Header (BODY) -5 - Failed to locate Colour Map header (CMAP) -6 - Asset is compressed -7 - Failed to locate Sample header (8SVX) -8 - Failed to locate Module header (MOD) SEE ALSO Code:
Struct handles STATUS X byte (-1 - invlaid, 0 - valid) * AmgLoadAndUnpackRncAsset() ADDRESS X long Pointer in ram to asset * amgLoadAndUnpackRncAsset() TYPE X byte (-1 - undefined, * amgParseAsset() 0 - IFF Image, 1 - Music Module, 2 - Sound Sample ) IMGIFFSIZE X word Size in bytes of entire * amgParseAsset() IMGBODYSIZE X long Size in bytes of body * amgParseAsset() IMGCMAPSIZE X long Size in bytes of colour map * amgParseAsset() IMGBODYPTR X long Pointer to body data origin * amgParseAsset() IMGCMAPPTR X long Pointer to cmap data origin * amgParseAsset() IMGCMAPTYPE X byte Type of colour map 0=12bit/1=24bit * amgParseAsset() IMGWIDTH X word Size in pixels of image width * amgParseAsset() IMGDEPTH X word Size in pixels of image depth * amgParseAsset() IMGPLANESIZE X byte Number of bitplanes in the image * amgParseAsset() IMGBYTEWIDTH X byte Number of bytes in width of the image * amgParseAsset() IMGMODULO X byte Image modulo * amgParseAsset() 8SXIFFSIZE X word Size in bytes of entire asset * amgParseAsset() 8SXBODYSIZE X long Size in bytes of body data * amgParseAsset() 8SXPERIOD X word Sample period * amgSetAssetData() result = amgSetAssetData(handle, offset, data) d0 d0 d1 d2 result = amgGetAssetData(handle, offset) d0 d0 d1 Index to handle data *ptr = amgIndexToHandle(handle) a0 So the hope is that I can build on this. As I have a strong Unix/Shell/Python background I tend to operate everything on file handles and OOP so I'm hoping to take those principles into this. This project might not go very far... but then again, it might really be useful. It's certainly something I can take at a much slower pace than coding something like Rygar. Ideas and comments as usual are really welcome. Geezer |
17 October 2019, 22:42 | #2 |
Registered User
Join Date: Dec 2016
Location: England
Posts: 87
|
Awesome!
You probably don't remember but I asked you about this on Facebook few weeks ago, so glad it looks like it's going ahead! |
17 October 2019, 23:15 | #3 |
AmigaMan
Join Date: Oct 2012
Location: Castro Urdiales/Spain
Posts: 760
|
great! I tick this thread to favourites!
|
17 October 2019, 23:23 | #4 |
Registered User
Join Date: Dec 2016
Location: England
Posts: 87
|
I really doubt a man of your skill will have much issues mate, if I'm honest.
C can be made very asm like, indeed that was what it was designed for, as an almost 1 to 1 correlation with asm. As an example I've decided to code Scoopex's tutorials into C, so I'm not just copying and pasting. Here's tut4. You can most probably guess the code this produces, as it's virtually identical to Photon's source. Code:
#include <exec/types.h> #define VPOSR 0xdff004 #define VHPOSR 0xdff006 #define INTENAR 0xdff01c #define INTENA 0xdff09a #define COLOR00 0xdff180 #define CIAPRAA 0xbfe001 int main() { volatile UBYTE *vhposr = (UBYTE *)VHPOSR; volatile UBYTE *vposr_lo = (UBYTE *)VPOSR + 1; // point to low byte UWORD *color00 = (UWORD *)COLOR00; UWORD *intena = (UWORD *)INTENA; // line starting position UBYTE yPos = 0xac; // line direction BYTE yDir = 1; // save interrupts UWORD oldInt = *(UWORD *)INTENAR; // disable all interrupts *intena = 0x7fff; // do main loop until mouse pressed while(*(volatile UBYTE *)CIAPRAA & 64) { // wait for start of frame if((*vposr_lo & 1) == 0 && *vhposr == 0x2c) { *color00 = 0; // bg black // update line y position yPos += yDir; // bounce line if(yPos > 0xf0 || yPos < 0x40) { yDir = -yDir; } // do literally nothing until we reach yPos while(*vhposr != yPos) {} *color00 = 0xfff; // bg white // now do nothing until we're not on yPos while(*vhposr == yPos) {} *color00 = 0x116; // bg blue } } // restore interrupts *intena = oldInt | 0xc000; return 0; } Code:
#include <proto/exec.h> #include <proto/dos.h> #include <proto/graphics.h> #include <exec/execbase.h> #include <hardware/cia.h> #include <hardware/custom.h> #include <graphics/gfxbase.h> extern struct GfxBase *GfxBase; extern struct Custom custom; extern struct CIA ciaa; struct View *OldView; static UWORD oldInt; static UWORD oldDMA; __chip UWORD copperlist[] = { 0x0106, 0x0000, // AGA - BPLCON3 0x01fc, 0x0000, // AGA - Slow FMODE 0x0180, 0x0f00, // Colour00 = red 0x8001, 0xff00, // wait until VPos = 0x80 0x0180, 0x0fff, // Colour00 = white 0xd001, 0xff00, // wait until VPos 0xd0 0x0180, 0x000f, // Colour00 = blue 0xffff, 0xfffe // end list }; int main() { // Save original view OldView = GfxBase->ActiView; // Set clean view LoadView(NULL); WaitTOF(); WaitTOF(); // Save interrupts and DMA oldInt = custom.intenar; oldDMA = custom.dmaconr; // disable all interrupts custom.intena = 0x7fff; // disable all DMA custom.dmacon = 0x7fff; // set cl1 to our copper custom.cop1lc = (ULONG)copperlist; // enable selected DMA custom.dmacon = 0x8280; // No Blitter or sprites, only copper // loop until mouse clicked while(ciaa.ciapra & CIAF_GAMEPORT0) { // do nothing } // restore interrupts and DMA custom.dmacon = oldDMA | 0x8000; custom.intena = oldInt | 0x8000; // restore orginal copper custom.cop1lc = (ULONG)GfxBase->copinit; // restore old view LoadView(OldView); WaitTOF(); WaitTOF(); return 0; } Last edited by Spec-Chum; 17 October 2019 at 23:41. |
20 October 2019, 23:57 | #5 |
Registered User
Join Date: Oct 2017
Location: Sunderland, England
Posts: 2,702
|
So I've implemeted a couple of these functions now and added another few.
The function added is amgUnloadAsset() - it's pretty simple. Code:
NAME amgUnloadAsset - Release handle and ram held by an asset SYNOPSIS amgUnloadAsset(assetHandle) d0 FUNCTION The specified handle is marked with a status of unused within its structure. The address and length pointers of the handle are left in tact, these are subsequently used to deallocate the ram of the asset using LVOFreeMem(). INPUTS handle - (d0) A previously allocated handle using amgLoadAndUnpackRncAsset(). RESULTS NULL ERRORS As I've implemented the functions I can post them here. We have six files in total main.asm - this has the main calling program. loader.asm/loader.dat - this is what does the load header, allocate ram, read packed file, unpack file. handle.asm/handle.dat - this contains the code for managing handles unpack.asm - RNC unpacker The main program looks like this, it simply reads the header of an RNC packed file, reads the unpacked size from the header, allocates that amount of ram, loads and unpacks the whole file to that allocated ram, allocates a handle for that asset that has been loaded....then... deallocates the handle and frees ram. This is pretty much a key aspect into managing assets in an Amiga game. Code:
ExecBase: equ 4 cseg basereg data,a4 near a4 CODE_BASE: bra MAIN CNOP 0,4 include i/custom.i ; Custom Register definitions include i/cia.i ; CIA Hardware definitions include 'exec/types.i' include 'exec/exec.i' include 'exec/exec_lib.i' include 'libraries/dos.i' include 'libraries/dos_lib.i' include 'hardware/blit.i' include 'hardware/dmabits.i' include s/unpack.asm include s/loader.asm include s/handle.asm MAIN: ; Reserve a4 and a5 for global pointers. lea data,a4 lea CHIPBASE,a5 ; Open the DOS library move.l ExecBase,a6 ; handle[d0] = OpenLibrary=(version[d0],libname[a1]) lea DOSNAME(a4),a1 moveq #0,d0 jsr _LVOOpenLibrary(a6) move.l d0,DOSBASE(a4) ; save DOSBASE for file reading lea asset_lower(a4),a0 ; rawHandle = amgLoadAndUnpackRncAsset(*name[a0], memtype[d1]) moveq #0,d0 moveq #MEMF_CHIP,d1 bsr amgLoadAndUnpackRncAsset ; d0 has allocated handle number ; now lets unload the asset from memory. bsr amgUnloadAsset ; amgUnloadUnpackedAsset(handle[d0]) move.l ExecBase,a6 ; Close the DOS library. move.l DOSBASE(a4),a1 jsr _LVOCloseLibrary(a6) ; End program moveq #0,d0 rts ; Data section here. dseg dats: include s/loader.dat include s/handle.dat DOSBASE: dc.l 0 DOSNAME: dc.b 'dos.library',0 even asset_lower: dc.b "dh1:Rygar/ry_lower.rnc",0 ; Packed RNC file to load. even data: loader.asm looks like this... Code:
mode_old =1005 mode_new =1006 ; rawHandle = amgLoadAndUnpackRncAsset(*name, memtype) ; d0 a0 d1 amgLoadAndUnpackRncAsset: movem.l d1-d7/a0-a1,-(a7) ; Save Registers move.l d1,d5 ; Save Requested Memory Type move.l a0,d6 ; Save filename pointer (we'll be needing it twice) ; Open the file for reading move.l a0,d1 ; handle[d0] = LVOOpenFile(filename[d1],mode[d2]) move.l DOSBASE(a4),a6 move.l #mode_old,d2 jsr _LVOOpen(a6) tst.l d0 bmi .open_error ; Read the RNC header file move.l d0,d4 ; Save handle for LVOClose lea amgRncHeaderBuffer(a4),a0 ; Buffer to read RNC header bytes move.l a0,d2 ; bytes[d0] = LVORead(handle[d1],buffer[d2],size[d3]) moveq #20,d3 jsr _LVORead(a6) tst.l d0 ; Was there an error? bmi .header_error cmp.l d0,d3 ; Did we read the correct number of bytes? bne .read_error ; Close the file move.l d4,d1 ; result = LVOClose(handle[d1]) move.l DOSBASE(a4),a6 jsr _LVOClose(a6) ; Check the RNC header lea amgRncHeaderBuffer(a4),a0 ; Get pointer to the read header move.l (a0),d0 lsr.l #8,d0 cmp.l #"RNC",d0 ; Is it RNC? bne .rnc_error ; Allocate ram based on RNC header move.l 4(a0),d0 ; address[d0] = LVOAllocMem(buffsize[d0],MEM_TYPE[d1]) move.l d5,d1 move.l ExecBase,a6 jsr _LVOAllocMem(a6) tst.l d0 bmi.s .alloc_error move.l d0,d4 ; Save the allocated buffer origin ; Open the file again and read it. move.l d6,d1 ; handle[d0] = LVOOpenFile(filename[d1],mode[d2]) move.l DOSBASE(a4),a6 move.l #mode_old,d2 jsr _LVOOpen(a6) tst.l d0 bmi.s .open_error move.l d0,d5 ; Save handle for read and close. ; Read the entire file move.l d4,d2 ; bytes[d0] = LVORead(handle[d1],buffer[d2],size[d3]) move.l #$ffffff,d3 jsr _LVORead(a6) tst.l d0 bmi.s .read_error ; Close the file move.l d5,d1 ; result = LVOClose(handle[d1]) move.l DOSBASE(a4),a6 jsr _LVOClose(a6) ; Allocate a new handle here for the asset that was just read. bsr amgAllocateHandle ; assetHandle[d0], handleOrigin[a0] = amgAllocateHandle() tst.l d0 bmi.s .handle_error move.l d0,d1 ; Save handle move.l d2,a1 move.l d4,HANDLE_STRUCT_ADDRESS(a0) ; Save the address of the asset in ram in the handle move.l 4(a1),HANDLE_STRUCT_LENGTH(a0) ; Save the length of the asset in ram in the handle move.l a1,a0 ; UnpackSize[d0] = Unpack[source[a0],dest[a0] bsr Unpack ; Verify the unpack size against what was in the header lea amgHandlesStruct(a4),a0 lsl.w #4,d1 cmp.l HANDLE_STRUCT_LENGTH(a0,d1),d0 bne.s .unpack_error move.l d1,d0 bra .exit ; All done. .open_error: moveq #ERROR_HANDLE_FILE_OPEN,d0 bra.s .exit .header_error: moveq #ERROR_HANDLE_HEADER_NOT_FOUND,d0 bra.s .exit .alloc_error: moveq #ERROR_HANDLE_ALLOCATE_FAIL,d0 bra.s .exit .read_error: moveq #ERROR_HANDLE_FILE_READ,d0 bra.s .exit .rnc_error: moveq #ERROR_HANDLE_RNC,d0 bra.s .exit .handle_error: moveq #ERROR_HANDLE_GENERIC,d0 bra.s .exit .unpack_error: moveq #ERROR_HANDLE_UNPACK,d0 bra.s .exit nop .exit: movem.l (a7)+,d1-d7/a0-a1 rts ; d0=handle number amgUnloadAsset: ; Now lets release the handle and the memory we just allocated. lsr.w #4,d0 bsr amgReleaseHandle ; handleOrigin[a0] = amgReleaseHandle(assetHandle[d0]) move.l HANDLE_STRUCT_ADDRESS(a0),a1 move.l HANDLE_STRUCT_LENGTH(a0),d1 move.l ExecBase,a6 jsr _LVOFreeMem(a6) rts Code:
CNOP 0,4 amgRncHeaderBuffer: ds.w 20 CNOP 0,4 handle.asm looks like this... Code:
maxHandles: equ 20 ; Error return codes for handles. ERROR_HANDLE_FILE_OPEN: equ -1 ERROR_HANDLE_HEADER_NOT_FOUND: equ -2 ERROR_HANDLE_ALLOCATE_FAIL: equ -3 ERROR_HANDLE_FILE_READ: equ -4 ERROR_HANDLE_RNC: equ -5 ERROR_HANDLE_GENERIC: equ -6 ERROR_HANDLE_UNPACK: equ -7 ; Handle structure indexes HANDLE_STRUCT_STATUS: equ 0 HANDLE_STRUCT_TYPE: equ 1 HANDLE_STRUCT_ADDRESS: equ 2 HANDLE_STRUCT_LENGTH: equ 6 ; OUTPUTS: ; Returns new handle number in d0 ; Returns pointer to handle structure in a0 amgAllocateHandle: move.l d7,-(a7) lea amgHandleAllocs(a4),a0 moveq #0,d0 moveq #maxHandles-1,d7 .loop: tst.w (a0)+ bmi.s .found addq.w #1,d0 dbf d7,.loop moveq #-1,d0 bra.s .exit .found: subq.w #2,a0 move.w d0,(a0) .exit: move.l d0,d7 lsl.w #4,d7 lea amgHandlesStruct(a4),a0 add.l d7,a0 move.b d0,(a0) move.l (a7)+,d7 rts ; Frees up a handle previously allocated with amgAllocateHandle() ; INPUT [d0] = handle to release amgReleaseHandle: lea amgHandleAllocs(a4),a0 move.w #-1,(a0,d0*2) lea amgHandlesStruct(a4),a0 lsl.w #4,d0 add.l d0,a0 move.w #-1,(a0) rts Code:
amgHandleCurrent: dc.w 0 ; Current handle pointer amgHandleAllocs: rept maxHandles dc.w -1 endr amgHandlesStruct: rept maxHandles dc.b -1 ; Status -1/0 dc.b -1 ; Type (-1 = unallocated) dc.l 0 ; Address dc.l 0 ; Length dc.b "HANDLE" ; Padding. endr Also included in the archive I have uploaded is the RNC unpack code. And a link to the source code is here. For those who want to follow along I will do a video showing setting up the tool chain and debugging the source here, feel free though to give it a try yourself if you want to race ahead. http://109.228.4.199/downloads/Tutorial%201.zip |
16 December 2021, 04:26 | #6 |
Registered User
Join Date: Jun 2020
Location: Australia
Posts: 18
|
[QUOTE=mcgeezer;1352868]
Code:
; d0=handle number amgUnloadAsset: ; Now lets release the handle and the memory we just allocated. lsr.w #4,d0 bsr amgReleaseHandle ; handleOrigin[a0] = amgReleaseHandle(assetHandle[d0]) move.l HANDLE_STRUCT_ADDRESS(a0),a1 move.l HANDLE_STRUCT_LENGTH(a0),d1 move.l ExecBase,a6 jsr _LVOFreeMem(a6) rts |
16 December 2021, 04:29 | #7 |
Moderator
Join Date: Jan 2002
Location: Chicago, IL
Posts: 3,375
|
Thanx for refreshing this thread, it's the first time I have seen it.
|
16 December 2021, 04:41 | #8 |
Registered User
Join Date: Jun 2020
Location: Australia
Posts: 18
|
Good thing that I bumped it then.
I just couldn't get this to free the right amount of RAM ( obviously too small a value in d0 ) until I moved the proper length of the asset into d0. |
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
Thread Tools | |
Similar Threads | ||||
Thread | Thread Starter | Forum | Replies | Last Post |
C# Framework | bburtonpa | Coders. General | 16 | 21 June 2020 13:27 |
API2 modern programming framework! | AMIGASYSTEM | support.Apps | 1 | 21 February 2019 17:05 |
Haujobb Amiga Framework released | stainy | Coders. Releases | 6 | 03 February 2019 18:02 |
Non-game programming - what does that leave me with? | TenLeftFingers | Coders. General | 56 | 03 February 2016 01:59 |
Amiga 3D game programming | Steve | Coders. General | 37 | 17 June 2013 22:15 |
|
|