Update readme

This commit is contained in:
Miloslav Číž 2020-10-09 11:32:57 +02:00
parent 95e12e5fc0
commit b6e77fbb14
3 changed files with 18 additions and 16 deletions

View File

@ -256,7 +256,9 @@ Main **game logic** is implemented in `game.h` file. All global identifiers of t
The game core only implements the back end independent of any platforms and libraries. This back end has an API a few functions that the front end has to implement, such as `SFG_setPixel` which should write a pixel to the platform's screen. Therefore if one wants to **port** the game to a new platform, it should be enough to implement these few simple functions and adjust game settings. The game core only implements the back end independent of any platforms and libraries. This back end has an API a few functions that the front end has to implement, such as `SFG_setPixel` which should write a pixel to the platform's screen. Therefore if one wants to **port** the game to a new platform, it should be enough to implement these few simple functions and adjust game settings.
The **rendering engine** -- raycastlib -- works on the principle of **ray casting** on a square grid and handles the rendering of the 3D environment (minus sprites). This library also handles player's **collisions** with the level. There is a copy of raycastlib in this repository but I maintain raycastlib as a separate project in a different repository, which you can see for more details about it. For us, the important functions interfacing with the engine are e.g. `SFG_floorHeightAt`, `SFG_ceilingHeightAt` (functions the engine uses to retirieve floor and ceiling height) and `SFG_pixelFunc` (function the engine uses to write pixels to the screen during rendering, which in turn uses each platform's specific `SFG_setPixel`). A deterministic **game loop** is used, meaning every main loop/simulation iteration has a constant time step, independently of actually achieved FPS. The FPS that is set determines both the target rendering FPS as well as the length of the simulation step: rendering at higher FPS than that of the simulation would bring no visual benefit as the draw function renders the game state as-is, without interpolating towards the next frame etc. Note that the game will behave slightly differently with different FPS values due to rounding errors, but should be okay as long as the FPS isn't extremely low (e.g. 2) or high (e.g. 1000). If the set FPS can't be reached, the game will appear slowed down, so set the FPS to a value that your platform can handle.
The **rendering engine** -- raycastlib -- works on the principle of **ray casting** on a square grid and handles the rendering of the 3D environment (minus sprites). This library also handles player's **collisions** with the level. There is a copy of raycastlib in this repository but I maintain raycastlib as a separate project in a different repository, which you can see for more details about it. For us, the important functions interfacing with the engine are e.g. `SFG_floorHeightAt`, `SFG_ceilingHeightAt` (functions the engine uses to retirieve floor and ceiling height) and `SFG_pixelFunc` (function the engine uses to write pixels to the screen during rendering, which in turn uses each platform's specific `SFG_setPixel`). It is documented in its own source code.
Only **integer arithmetic** is used, no floating point is needed. Integers are effectively used as fixed point numbers, having `RCL_UNITS_PER_SQUARE` (1024) fractions in a unit. I.e. what we would write as 1.0 in floating point we write as 1024, 0.5 becomes 512 etc. Only **integer arithmetic** is used, no floating point is needed. Integers are effectively used as fixed point numbers, having `RCL_UNITS_PER_SQUARE` (1024) fractions in a unit. I.e. what we would write as 1.0 in floating point we write as 1024, 0.5 becomes 512 etc.
@ -286,8 +288,6 @@ To increase **performance**, you can adjust some settings, see settings.h and se
**Levels** are stored in levels.h as structs that are manually editable, but also use a little bit of compression principles to not take up too much space, as each level is 64 x 64 squares, with each square having a floor heigh, ceiling heigh, floor texture, ceiling texture plus special properties (door, elevator etc.). There is a python script that allows to create levels in image editors such as GIMP. A level consists of a tile dictionary, recordind up to 64 tile types, the map, being a 2D array of values that combine an index pointing to the tile dictionary plus the special properties (doors, elevator, ...), a list of up to 128 level elements (monsters, items, door locks, ...), and other special records (floor/ceiling color, wall textures used in the level, player start position, ...). **Levels** are stored in levels.h as structs that are manually editable, but also use a little bit of compression principles to not take up too much space, as each level is 64 x 64 squares, with each square having a floor heigh, ceiling heigh, floor texture, ceiling texture plus special properties (door, elevator etc.). There is a python script that allows to create levels in image editors such as GIMP. A level consists of a tile dictionary, recordind up to 64 tile types, the map, being a 2D array of values that combine an index pointing to the tile dictionary plus the special properties (doors, elevator, ...), a list of up to 128 level elements (monsters, items, door locks, ...), and other special records (floor/ceiling color, wall textures used in the level, player start position, ...).
The game uses a deterministic **game loop**, meaning every main loop/simulation iteration has a constant time step, independently of actual rendering FPS. The time step is determined by the FPS setting. If this FPS can't be reached, the game will appear slowed down, so set the FPS to the value that your platform can handle. It is important to note that different settings, most imporantly the FPS, will affect the simulation, so that the game with different settings behaves slightly differenly and in the extreme cases (very low/high FPS) can become weird and buggy.
**Saving/loading** is an optional feature. If it is not present (frontend doesn't implement the API save/load functions), all levels are unlocked from the start and no state survives the game restart. If the feature is implemented, progress, settings and a saved position is preserved in permanent storage. What the permanent storage actually is depends on the front end implementation it can be a file, EEPROM, a cookie etc., the game doesn't care. Only a few bytes are required to be saved. Saving of game position is primitive: position can only be saved at the start of a level, allowing to store only a few values such as health and ammo. **Saving/loading** is an optional feature. If it is not present (frontend doesn't implement the API save/load functions), all levels are unlocked from the start and no state survives the game restart. If the feature is implemented, progress, settings and a saved position is preserved in permanent storage. What the permanent storage actually is depends on the front end implementation it can be a file, EEPROM, a cookie etc., the game doesn't care. Only a few bytes are required to be saved. Saving of game position is primitive: position can only be saved at the start of a level, allowing to store only a few values such as health and ammo.
Performance and small size are achieved by multiple **optimization** techniques. Macros are used a lot to move computation from run time to compile time and also reduce the binary size. E.g. the game resolution is a constant and can't change during gameplay, allowing the compiler to precompute many expression with resolution values in them. Powers of 2 are used whenever possible. Approximations such as taxicab distances are used. Some "accelerating" structures are also used, e.g. a 2D bit array for item collisions. Don't forget to compile the game with -O3. Performance and small size are achieved by multiple **optimization** techniques. Macros are used a lot to move computation from run time to compile time and also reduce the binary size. E.g. the game resolution is a constant and can't change during gameplay, allowing the compiler to precompute many expression with resolution values in them. Powers of 2 are used whenever possible. Approximations such as taxicab distances are used. Some "accelerating" structures are also used, e.g. a 2D bit array for item collisions. Don't forget to compile the game with -O3.

View File

@ -1,8 +1,6 @@
general: general:
- Refactor. - Refactor.
- Add option for advanced head bobbing which will also shear the camera along
with offsetting it.
- Port to OpenDingux. - Port to OpenDingux.
- Port to some fantasy console? - Port to some fantasy console?
- Rewrite python scripts to C (faster, less bloat). - Rewrite python scripts to C (faster, less bloat).
@ -13,9 +11,6 @@ general:
- compile on BSD and WinShit - compile on BSD and WinShit
- make SFML frontend - make SFML frontend
- add time slowdown constant - add time slowdown constant
- compile with different compilers (gcc, clang, musl, ...) and settings, make
a build script that creates multiple binaries (with different combinations of
compilers and settings)
- run on raspbery pi - run on raspbery pi
level ideas: level ideas:
@ -197,6 +192,11 @@ done:
to normalize the track? to normalize the track?
- Player is sometimes squeezed to death in door (appeared in level 9). - Player is sometimes squeezed to death in door (appeared in level 9).
Investigate, fix. Investigate, fix.
- Add option for advanced head bobbing which will also shear the camera along
with offsetting it.
- compile with different compilers (gcc, clang, musl, ...) and settings, make
a build script that creates multiple binaries (with different combinations of
compilers and settings)
scratched: scratched:
- option for disabling wall transparency, for performance? - option for disabling wall transparency, for performance?

16
game.h
View File

@ -181,7 +181,6 @@ void SFG_init();
#define SFG_PROGRAM_MEMORY const PROGMEM #define SFG_PROGRAM_MEMORY const PROGMEM
#define SFG_PROGRAM_MEMORY_U8(addr) pgm_read_byte(addr) #define SFG_PROGRAM_MEMORY_U8(addr) pgm_read_byte(addr)
// TODO
#else #else
#define SFG_PROGRAM_MEMORY static const #define SFG_PROGRAM_MEMORY static const
#define SFG_PROGRAM_MEMORY_U8(addr) ((uint8_t) (*(addr))) #define SFG_PROGRAM_MEMORY_U8(addr) ((uint8_t) (*(addr)))
@ -503,7 +502,7 @@ uint8_t SFG_getItemCollisionMapBit(uint8_t x, uint8_t y)
} }
#if SFG_DITHERED_SHADOW #if SFG_DITHERED_SHADOW
SFG_PROGRAM_MEMORY uint8_t SFG_ditheringPatterns[] = static const uint8_t SFG_ditheringPatterns[] =
{ {
0,0,0,0, 0,0,0,0,
0,0,0,0, 0,0,0,0,
@ -833,7 +832,7 @@ void SFG_recomputePLayerDirection()
#if SFG_BACKGROUND_BLUR != 0 #if SFG_BACKGROUND_BLUR != 0
uint8_t SFG_backgroundBlurIndex = 0; uint8_t SFG_backgroundBlurIndex = 0;
SFG_PROGRAM_MEMORY int8_t SFG_backgroundBlurOffsets[9] = static const int8_t SFG_backgroundBlurOffsets[8] =
{ {
0 * SFG_BACKGROUND_BLUR, 0 * SFG_BACKGROUND_BLUR,
16 * SFG_BACKGROUND_BLUR, 16 * SFG_BACKGROUND_BLUR,
@ -843,7 +842,6 @@ SFG_PROGRAM_MEMORY int8_t SFG_backgroundBlurOffsets[9] =
4 * SFG_BACKGROUND_BLUR, 4 * SFG_BACKGROUND_BLUR,
15 * SFG_BACKGROUND_BLUR, 15 * SFG_BACKGROUND_BLUR,
9 * SFG_BACKGROUND_BLUR, 9 * SFG_BACKGROUND_BLUR,
7 * SFG_BACKGROUND_BLUR
}; };
#endif #endif
@ -977,7 +975,9 @@ void SFG_pixelFunc(RCL_PixelInfo *pixel)
); );
#if SFG_BACKGROUND_BLUR != 0 #if SFG_BACKGROUND_BLUR != 0
SFG_backgroundBlurIndex = (SFG_backgroundBlurIndex + 1) % 0x07;
SFG_backgroundBlurIndex = (SFG_backgroundBlurIndex + 1) % 8;
// SFG_backgroundBlurIndex = (SFG_backgroundBlurIndex + 1) % 0x07;
#endif #endif
#else #else
color = 1; color = 1;
@ -4803,12 +4803,14 @@ uint8_t SFG_mainLoopBody()
if (SFG_game.antiSpam > 0) if (SFG_game.antiSpam > 0)
SFG_game.antiSpam--; SFG_game.antiSpam--;
// render noly once // render only once
SFG_draw(); SFG_draw();
} }
else else
{ {
SFG_sleepMs((SFG_game.frameTime + SFG_MS_PER_FRAME - timeNow) / 2); // wait, relieve CPU // wait, relieve CPU
SFG_sleepMs(RCL_max(1,
(3 * (SFG_game.frameTime + SFG_MS_PER_FRAME - timeNow)) / 4));
} }
return SFG_game.continues; return SFG_game.continues;