This commit is contained in:
wgroeneveld 2018-08-13 16:50:24 +02:00
parent e457021953
commit 31664bb47b
5 changed files with 158 additions and 0 deletions

158
README.md Normal file
View File

@ -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<Sprite>()
.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 `<libgba-sprite-engine/gba>`.
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<GBAEngine> 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<Sprite *> sprites()`: the sprites to load into VRAM
2. `std::vector<Background *> 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<ForegroundPaletteManager>(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<Background>(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 (`<Sprite>` or `<AffineSprite>`) 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.

BIN
img/bounds.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
img/fade.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
img/rotate.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

BIN
img/scroll.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB