diff --git a/.gitignore b/.gitignore index 9b844e0..fa1341a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /FONTBANK\#060000 /Art/Generated /fonts.lst +/catfight.lst diff --git a/GSCats.xcodeproj/project.pbxproj b/GSCats.xcodeproj/project.pbxproj index 8b161f9..d4b990b 100644 --- a/GSCats.xcodeproj/project.pbxproj +++ b/GSCats.xcodeproj/project.pbxproj @@ -44,7 +44,7 @@ 70E554C41F807ADB00F3C871 /* spritebank.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = spritebank.s; sourceTree = ""; }; 70E9D85F1F2BD95400555C19 /* equates.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = equates.s; sourceTree = ""; }; 70E9D8601F2BD95400555C19 /* graphics.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = graphics.s; sourceTree = ""; }; - 70E9D8611F2BD95400555C19 /* gscats.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = gscats.s; sourceTree = ""; }; + 70E9D8611F2BD95400555C19 /* catfight.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = catfight.s; sourceTree = ""; }; 70E9D8621F2BD95400555C19 /* macros.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = macros.s; sourceTree = ""; }; 70E9D8631F2BD95400555C19 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 70F011D023B91B2900C8873F /* dirt.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = dirt.s; sourceTree = ""; }; @@ -65,7 +65,7 @@ 701E708F2A69CE520030C35D /* loadingBar.s */, 701E70902A6A23800030C35D /* sharedGraphics.s */, 701E70922A6DC5910030C35D /* titleScreen.s */, - 70E9D8611F2BD95400555C19 /* gscats.s */, + 70E9D8611F2BD95400555C19 /* catfight.s */, 70A80FB01F43D7F200BD34C9 /* gamemanager.s */, 70E9D8601F2BD95400555C19 /* graphics.s */, 7099E3841F41022100182A82 /* gameobject.s */, diff --git a/Makefile b/Makefile index 33d1250..9e2e722 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ CL65=cl65 CAD=./cadius -VOLNAME=GSAPP +VOLNAME=CATFIGHT IMG=DiskImageParts EMU=/Applications/GSplus.app/Contents/MacOS/gsplus ADDR=0800 @@ -20,7 +20,7 @@ EXEC=$(PGM)\#06$(ADDR) SOUNDBANK=SOUNDBANK\#060000 FONTBANK=FONTBANK\#060000 TITLESCREEN=TITLE\#060000 -PGM=gscats +PGM=catfight MRSPRITE=../MrSprite/mrsprite GENART=Art/Generated diff --git a/ReadMe.md b/ReadMe.md index f46b174..762b1cd 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -9,6 +9,11 @@ This was written as an exercise for me to learn the fundamentals of Apple IIgs g If nothing else, I do think this project serves as a solid starting point for anyone interested in writing GS games. The basics are all here- an art pipeline with compiled sprites, a VBL-synced game loop, input handling, fonts, animation system, and sound. All the things you need on a GS, and all things that are hard on the GS. I'll describe these systems in more detail below. +## Running The Game + +The included 800k disk image should load fine in any emulator or on real hardware. It should run on any GS with 512k or more of RAM, and no acceleration is required. The game runs of ProDOS 8 and can in theory be copied to a hard drive, but at the moment the volume name is hardcoded in the loader. Sorry, it's lame, I know. The image boots Bitsy Bye, and the game is called CATFIGHT. Run that and enjoy spitting at your opponent all the live long day. + + ## Acknowledgements I'm writing this game now because I'm learning all the things I wished I could know when I was a teenager and playing all the amazing GS games. Games like Rastan, Task Force, Alien Mind, The Immortal, and Sword of Sodan that did things that people said the GS shouldn't be capable of. I was never able to acquire the knowledge of how these things were done back then, but thanks to an amazing modern retro community, I can! In that spirit, thanks to: @@ -56,7 +61,7 @@ Fonts on the GS are best rendered in a similar way to sprites- as compiled entit ## The Terrain Engine -Rendering fast on the GS is all about compiling your pixel writes. This usually means sprite compiling, which is where you use an offline tool to convert your sprite art into code that renders those sprites directly, as explained above. For the terrain engine in Cat Fight, this means taking a 2D height map and compiling it into a series of stack push instructions to render as quickly as possible. It's similar to sprite compiling, but this has to be done on the fly. The way I did this was by limiting the terrain to a single colour. This means I only need four bit patterns to represent all possible terrain pixel patterns (since the GS has 4 pixels per word). We can store four bit patterns in four registers that can all be pushed- the accumulator, index X, index Y, and the Direct Page register. This means the terrain compiler generates a huge block of PHA, PHX, PHY, and PHD instructions to render the terrain. We're not done though, because the terrain needs to be deformable to make it a true artillery style game. To do this, sections of the terrain can be recompiled as needed to modify it. Compiling the entire map would be slow (and is only done once) but recompiling small "dirty rectangles" is so fast you don't notice it. Furthermore, the terrain has to scroll, which means we render an arbitrary 320 pixel wide section of it. To do this, the compiled terrain code is "clipped" by inserting a column of RTS instructions at the left edge of the virtual screen's location. Each row of terrain is rendered by jumping to the right edge entry point, then bouncing back at the left edge off the RTS. When the terrain scrolls, that column of RTS instructions is replaced with the terrain data that was there. The column of terrain we would overwrite to do this is saved in a little mini stack. To save and restore this data very quickly, I use the amazing GS trick of relocating the GS stack. I can push and pop this data to save/restore it very quickly into a little buffer than I hide in an unused area of bank $E0. +Rendering fast on the GS is all about compiling your pixel writes. This usually means sprite compiling, which is where you use an offline tool to convert your sprite art into code that renders those sprites directly, as explained above. For the terrain engine in Cat Fight, this means taking a 2D height map and compiling it into a series of stack push instructions to render as quickly as possible. It's similar to sprite compiling, but this has to be done on the fly. The way I did this was by limiting the terrain to a single colour. This means I only need four bit patterns to represent all possible terrain pixel patterns (since the GS has 4 pixels per word). We can store four bit patterns in four registers that can all be pushed- the accumulator, index X, index Y, and the Direct Page register. This means the terrain compiler generates a huge block of PHA, PHX, PHY, and PHD instructions to render the terrain. We're not done though, because the terrain needs to be deformable to make it a true artillery style game. To do this, sections of the terrain can be recompiled as needed to modify it. Compiling the entire map would be slow (and is only done once) but recompiling small "dirty rectangles" is so fast you don't notice it. Furthermore, the terrain has to scroll, which means we render an arbitrary 320 pixel wide section of it. To do this, the compiled terrain code is "clipped" by inserting a column of JMP instructions at the left edge of the virtual screen's location. Each row of terrain is rendered by jumping to the right edge entry point, then bouncing back at the left edge off the return JMP. When the terrain scrolls, that column of JMP instructions is replaced with the terrain data that was there. The column of terrain we overwrite to do this is saved in a little mini stack. To save and restore this data very quickly, I use the amazing GS trick of relocating the GS stack. I can push and pop this data to save/restore it very quickly into a little buffer than I hide in an unused area of bank $E0. All of this is a little mind-bending because stack-based rendering means you do everything backwards. Sprites are rendered from bottom right to top left, and my terrain is rendered right-to-left on each row, and bottom to top overall. diff --git a/catfight.2mg b/catfight.2mg new file mode 100644 index 0000000..5e0f2bb Binary files /dev/null and b/catfight.2mg differ diff --git a/gscats.s b/catfight.s similarity index 100% rename from gscats.s rename to catfight.s diff --git a/loader.s b/loader.s index bab4897..ece264e 100644 --- a/loader.s +++ b/loader.s @@ -434,15 +434,15 @@ fileOpenTitle: .byte 0 ; Padding codePath: - pstring "/GSAPP/CODEBANK" + pstring "/CATFIGHT/CODEBANK" spritePath: - pstring "/GSAPP/SPRITEBANK" + pstring "/CATFIGHT/SPRITEBANK" soundPath: - pstring "/GSAPP/SOUNDBANK" + pstring "/CATFIGHT/SOUNDBANK" fontPath: - pstring "/GSAPP/FONTBANK" + pstring "/CATFIGHT/FONTBANK" titlePath: - pstring "/GSAPP/TITLE" + pstring "/CATFIGHT/TITLE" .include "sharedGraphics.s" .include "loaderGraphics.s"