-
Notifications
You must be signed in to change notification settings - Fork 7
Architecture Overview
creative-engine is designed to be a comprehensive library for developing games and applications for the Odroid-Go platform. It is also designed to be portable to other platforms, including Mac/Linux using libsdl2.
The MacOS/Linux (host) build is intended to use CLion IDE, which allows for full featured debugging of game logic.
Instead of rendering to the Odroid Go display, the host version uses libsdl2 to open a window in the same format as the device's LCD.
The Display class renders to libsdl2 if on host, or renders via SPI bus on device.
The Display class manages two BBitmaps: one that is being rendered to the physical display and one that the game logic is rendering to, preparing the next frame's graphics.
Similarly, the Controls class does the right thing on device and using libsdl2, for reading the buttons and joystick.
In theory, if the game runs on the host, it should flash and run on the target.
The lower level rendering is done using BBitmaps. BBitmap is a class that encapsulates all things related to bitmaps: palette, pixels, width, height, and so on. It also incorporates functions to render subrectangles from another bitmap, draw lines, plot pixels, circles, and so on.
A BBitmap may refer to pixels that are in SPI (MEMF_SLOW) or on board memory (RAM).
There are 4 (or more) use cases for BBitmap:
- For the two page flip/double buffer screen images
- For sprite sheets
- For full screen or a single image
- A dynamically altered and generated image
Bitmap resources are compiled into Resources.bin by rcomp, and included as one big binary into the executable. The BResourceManager class provides a LoadBitmap() method to load a Bitmap from FLASH into a BBitmap. BResourceManager loads BBitmaps into a "SLOT" you specify. You might #define BKG_SLOT 0 and #define PLAYER_SLOT 1 and load the background and player bitmaps into those slots. The Sprite system uses the slot number to determine which of many possible bitmaps loaded to render the sprite image from.
resourceManager.LoadBitmap() takes two arguments: slot number and a flag to indicate whether the bitmap is IMAGE_ENTIRE, or a grid of various combinations of 8, 16, and 32 widths and heights. See the header file for specifics.
A typical case might be to load PLAYER_BMP (defined in Resources.h, generated by rcomp) into PLAYER_SLOT with IMAGE_32x32 flag. Image #0 is upper left 32x32, image #1 is next one to the right, image #4 is 1st one on second row - assuming a 128 wide sprite sheet.
You can resourceManager.GetBitmap(SLOT) to retrieve a BBitmap, and resourceManager.BitmapWidth(SLOT) to get the width of the grid for the bitmap (32 in the player bitmap example), and resourceManager.BitmapHeight(SLOT) to get the height.
The resource manager can also cache bitmaps so you don't have to load them over and over each game level. The player bitmap, for example, may never change no matter the level.
You can manually release a slot with resourceManager.ReleaseBitmap(slot), or release them all (except cached ones) by calling resourceManager.ReleaseBitmaps() (plural).
You should create a GResources.h for your game. It would #define the slot numbers and #define constants for the individual frames on your sprite sheets. It will also include Resources.h, which is generated by rcomp.
Inherit from BGameEngine to make your GGameEngine class. Your GGameEngine will track things like level, lives remaining, score, and so on.
BGameEngine provides methods for adding BSprites and Processes, and pausing and resuming the game.
BGameEngine provides methods you can override in your GGameEngine to:
- PreRender() before anything is done.
- PositionCamera() to position the viewport, worldX,worldY coordinates, or whatever.
- GameLoop() to render background, render sprites, and run processes for game logic (player, enemy AI).
- PostRender() after everything is done.
You will call GameLoop() every 30th of a second (or slower if you have a lower frame rate) in your app_main().
You will inherit from BPlayfield to create classes that render the background of your game. You may have one, but it is likely you will have several. For example, one for splash screen, one for high scores, one for level 1, one for level 2, etc., and one for game over. BGameEngine will call your playfield's Animate() method and then its Render() method. Use them as you see fit.
There are currently two base Sprite classes: BSprite and BAnimSprite. BSprite is a basic sprite that defines a world X,Y position, vector velocities (for physics), width, height, image to display, and flags (flipped, flopped, more detailed below).
BAnimSprite inherits from BSprite and provides a rich animation script interpreter. This allows for high level logic to StartAnimation(player_walking_animation) and not have to deal with changing the BSprite's image to display in game logic.
You will inherit from either of these classes if you want to add member variables or other functions.
Sprites must be added to the GameEngine's sprite list in order to be processed. The list is priority sorted (BSprite inherits from BNodePri). This allows you to control rendering so enemy bullets appear on top of enemies (otherwise they would hit the player and be invisible/behind enemy).
BGameEngine renders the list automatically in GameLoop().
BSprite flags include a bit for move, animate, render, and check. If SFLAG_MOVE is set, the sprite's vx will be added to x and vy added to y each frame. If SFLAG_ANIMATE is set, the sprite's Animate() function will be called each frame. If SFLAG_RENDER is set, the sprite will be rendered, otherwise it will not appear. If SFLAG_CHECK is set, the sprite is checked for collisions against other sprites (see below).
There are additional flags that affect how the sprite is rendered. If SFLAG_FLIP is set, the sprite is rendered right to left instead of left to right (flipped). If SFLAG_FLOP is set, the sprite is rendered bottom to top instead of top to bottom (flopped). A sprite may be flipped and flopped.
Miscellaneous other flags are SFLAG_CLIPPED (set if sprite is off the screen, check this in a Process perhaps), SFLAG_ANCHOR (sprite's Y is the bottom left corner instead of upper left), SFLAG_SORTX (sort sprite in sprite list by x coordinate), SFLAG_SORT_Y (sort sprite in sprite list by y coordinate), and SFLAG_SORTPRI (sort sprite by priority).
BSprite has a member variable "type" that contains a bit mask of the "kind" of thing the sprite is. Predefined values are STYPE_DEFAULT, STYPE_PLAYER, STYPE_PBULLET (player bullet), STYPE_ENEMY, STYPE_EBULLET (enemy bullet)... You may define your own custom types by using bits STYPE_USER_BIT and up.
There are two members that further control collision detection. cMask is STYPE_* bits or'ed together of the types of sprites to compare this one against. The cType member is a similar mask of bits SET to indicate which collisions actually occurred. You can override the Collide() method to augment this behavior (is called upon collision).
This is a BSprite that provides additional automatic animation capability. You will define one or more ANIMSCRIPT (animation script) arrays. For a platform game, you might have a PLAYER_STAND script, a PLAYER_WALK_RIGHT script a PLAYER_WALK_LEFT script, and a PLAYER_HIT script. In your GPlayerProcess, you will read the joystick and if no bits set, call the player sprite's StartAnimation(PLAYER_STAND). When the joystick right is pressed, call StartAnimation(PLAYER_WALK_RIGHT). And so on. You will only call StartAnimation() when state changes, not each time in the loop.
An animation script is defined using macros specified in the BAnimSprite.h
- ANULL(nFrames) - sets image to null for nFrames, make sprite invisible
- ABITMAP(bm)- sets the sprite's bitmap slot #
- ASTEP(nFrames, image) - shows the specified image (on the bitmap/slot) for nFrames
- ASTEP1(image) - same as ASTEP(1, image), shorthand, smaller code
- AFLIP(nFrames, image) - displays image from bitmap slot FLIPPED for nFrames
- AFLIP1(image) - same as AFLIP(1, image)
- AFLOP(nFrames, image) - displays image from bitmap slot FLOPPED for nFrames
- AFLOP1(image) - same as AFLOP(1, image)
- AFLIPFLOP(nFrames, image) - display image flipped and flopped for nFrames
- AFLIPFLOP1(image) - same as AFLIPFLOP(1, image)
- ALABEL - sets loop point in script
- ALOOP - branch to ALABEL
- AEND - end script, clear SFLAG_ANIM in sprite
Note that game logic (BProcess) can check for SFLAG_ANIM to be cleared do know when the animation is done.
You will want to inherit from BProcess to implement your game logic. A Process has its RunBefore() called before Sprites are rendered and RunAfter() called after Sprites are rendered. Return EFalse from either to have the Process removed from the list and destructed.
There's a good chance a BProcess will implement a state machine, where a state might be: running, standing, walking, jumping, falling, etc. You want to do different logic in RunBefore/RunAfter depending on the state. Perhaps a switch() statement.
You likely will have a GPlayerProcess that reads the controls and positions the player BSprite.
You may have a Process for each bullet and each enemy and each enemy bullet.
There is not a 1:1 correspondence between number of sprites and number of processes. You can have a single process control many sprites, or sprites that are not controlled at all (like a torch flickering on a wall, just animates and never moves), or a process that does not control sprites at all.