## 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.