diff --git a/README.md b/README.md new file mode 100644 index 0000000..a4ee5ef --- /dev/null +++ b/README.md @@ -0,0 +1,158 @@ + +## A high-level object-oriented Gameboy Advance sprite engine library + +That's a mouthful - let's break that down: + +#### High-level object-oriented + +The GBA is an older piece of hardware that's missing an OS. While that is great (`while(1) {}`), it also means there's little software library support. GBA programming boils down to manipulating **memory-mapped IO** pointers. And that's not a lot of fun. + +Instead of writing + +```C +vu16* paddle = ((volatile tile_block *)0x06000000)[0][1]; +for(int i = 0; i < 4 * sizeof(tile_4bpp) / 2; i++) { + paddle[i] = 0x2222; +} +``` + +Wouldn't it be a bit more readable if one could write + +```C +auto paddle = SpriteBuilder() + .withData(paddleTile, sizeof(paddleTile)) + .withLocation(10, 10) + .buildPtr(); +``` + +To leverage C++11's abilities combined with a clear object-oriented approach using abstraction to hide the hexadecimal addresses? That's the objective of this engine. + +**Speed is NOT my primary concern**! I'm not a C(++) expert and I value clean readable code above speedy algorithms. This is just a concept. That said, feel free to fork or provide patches for sloppy work (that will be present). Thank you! + +#### GBA sprite engine + +That means `MODE0`. + +#### library + +It's compiled as a static library for your convenience. Simply link with the library and include the header path and you're all set. Take a look at the demo's `CMake` files if you're interested. + +## Engine features + +A portion of [ToncLib](https://www.coranac.com/man/tonclib/main.htm) has been used as a low-level GBA accessor. If you know what you're doing, you can safely use those, headers are in ``. + +BIOS methods and Sin/Cos lookup tables are also compiled. + +### Implementing your own GBA Game + +#### Scenes + +Scenes contain sprites and backgrounds. You can transition between scenes with `engine->setScene(scene)` or `engine->transitionToScene(scene, effect)`. The main program bootstraps your first scene - from there on it's each scene's responsibility to load another one. + +This is the layout of a main function: + +```C +int main() { + std::shared_ptr engine(new GBAEngine()); + + auto startScene = new SampleStartScene(engine); + engine->setScene(startScene); + + while (true) { + engine->update(); + } + + return 0; +} +``` + +That's it! + +To create your own scene, subclass `Scene` and implement: + +1. `std::vector sprites()`: the sprites to load into VRAM +2. `std::vector backgrounds()`: your (multilayered) backgrounds +3. `void load()`: one-time scene loading (create your objects here) +4. `void tick(u16 keys)`: gets called each engine `update()` +5. set `foregroundPalette` and `backgroundPalette` in your load. + +Loading up a scene usually involves creating some sprites with the builder. Don't forget to set the palettes like this: `std::unique_ptr(new ForegroundPaletteManager(sharedPal, sizeof(sharedPal)));` + +The `sprites()` method gets periodically called to check whether something has been added or deleted and updates the VRAM and OAM accordingly. **You don't need to manage anything yourself!** Take a look at demo 3. + +A simple fade out scene effect is implemented in demo 1, that converges the palette colors of both palettes to white. It's easy to **create your own effects** by subclassing `SceneEffect`. + +#### Backgrounds + +Scrollable backgrounds are present: + +Call `scroll()` in your scene update. + +Creating a background: + +```C + bg = std::unique_ptr(new Background(1, background_data, sizeof(background_data), map, sizeof(map))); + bg->useMapScreenBlock(16); +``` + +Backgrounds work a bit different in VRAM compared to sprites. There are only 4 backgrounds available, and background #4 is taken by the font. Parameter 1 identifies your background used for prioritizing. Remember to use a screen block different than your map data. + +#### Sprites + +Conjuring sprites on the screen is a matter of exposing them to the sprites vector in your scene. Create them in your load and set them as a `std::unique_ptr` member variable in your scene so they get cleaned up automatically. + +Creating sprites is easy with the `SpriteBuilder`. Specify what kind of sprite you want to make as a template argument (`` or ``) and speficy your data `.with...()`. Done? Call `build()` to get a copy or `buildPtr()` to get a copy wrapped in a unique pointer. + +**Affine sprites can transform** using for example `rotate(angle)` - check out demo 1 or 3 for that. + +**Sprite animation is built-in**! Just feed your sprite data to the builder and use `.withAnimated(amountOfFrames, frameDelay)`. Remember to position each frame in one column in the image itself (vertically). + +Useful sprite methods: + +* `animate()`, `animateToFrame(x)` or `stopAnimating()` +* `flipVertically(bool)` or `flipHorizontally(bool)` +* `setVelocity(dx, dy)` (auto-updates) or `moveTo(x, y)` +* `setWithinBounds(bool)` automatically keeps your sprite within the GBA resolution bounds +* `collidesWith(otherSprite)` or `isOffScreen()` +* Various getters like `getWith()` etc + +#### Sound + +The engine supports **sounds** and **background music** using GBA's Active Sound channel A and B. It's a simple implementation meaning no mixing in either channels (but A and B are mixed). + +Call `engine->enqueueMusic(data, sizeof(data));` as a repeating music or `enqueueSound()` as a one-timer. If some sound is already playing, it will **not be played**. Background music auto-repeats when done. See demo 1. + +#### Text + +![default font](https://github.com/wgroeneveld/gba-sprite-engine/blob/master/engine/src/background/text.png?raw=true) + +There's a **default font embedded into the engine** using the `TextStream::instance()` static instance. It takes up background #4 and claims the last background palette bank so watch out with that! + +Useful text manipulation: + +* `setText(txt, row, col)` +* `<< text` or `<< int` etc: append to text stream for debugging purposes. +* `setTextColor(COLOR)`: changes default color palette (white). + +#### Error logging + +The text stream is also used if something goes wrong, there's a macro `failure_gba(WHOOPS)` that prints file, line, method and "exception" message to the text stream background. + +### Unit Testing GBA games + +The engine comes with (some) [Google Test](https://github.com/google/googletest) test cases to show you how separate classes can effectively be unit tested. I had to stub out some ARM-specific ToncLib includes, that's why the `add_definitions(-DCODE_COMPILED_AS_PART_OF_TEST)` CMake statement is there. Gtest compiles library source with `g++` and **not with the cross-compiler**! + +### Compiling everything + +The project has been developed with CLion. The `.idea` dir is there for you to get started. + +As such, `CMake` was an easy choice. Use the following commands to build everything, including the demos: + +1. `mkdir cmake-build-debug && cd cmake-build-debug` +2. `cmake ./../` + +Things you might need to change in `CMakeLists.txt` files: + +1. I'm assuming your GBA cross compiler is in your `$PATH`. If it's not, add an absolute path to `SET(CMAKE_C_COMPILER arm-none-eabi-gcc)` etc. +2. The Google Test Library should be compiled from source in some directory. I have mine hardcoded: `SET(GTEST_LIBRARY "/Users/jefklak/CLionProjects/googletest-release-1.8.0/googletest")` so adjust accordinly or exclude the test subproject. + diff --git a/img/bounds.gif b/img/bounds.gif new file mode 100644 index 0000000..30ea45c Binary files /dev/null and b/img/bounds.gif differ diff --git a/img/fade.gif b/img/fade.gif new file mode 100644 index 0000000..b8ccd3a Binary files /dev/null and b/img/fade.gif differ diff --git a/img/rotate.gif b/img/rotate.gif new file mode 100644 index 0000000..1e03ad6 Binary files /dev/null and b/img/rotate.gif differ diff --git a/img/scroll.gif b/img/scroll.gif new file mode 100644 index 0000000..1461d35 Binary files /dev/null and b/img/scroll.gif differ