392 lines
11 KiB
C
392 lines
11 KiB
C
/**
|
|
game: this file implements the backend of a complete, actually playable
|
|
game, and is meant to be included and used by specific frontends (which
|
|
will handle each platform's hardware details and I/O).
|
|
|
|
TODO: more documentation
|
|
|
|
UNITS: There are various kinds of units used to ensure independence of the
|
|
game modules. Here is a summary:
|
|
|
|
- LCR_GameUnit: data type, abstract unit of the game (racing module). One map
|
|
square is LCR_GAME_UNITs long, a full angle is also LCR_GAME_UNITs.
|
|
- LCR_GAME_UNIT: Size of one game square and full angle in LCR_GameUnits.
|
|
Square height is only half of this.
|
|
- S3L_Unit: data type, small3dlib's unit. May change with renderer change.
|
|
- S3L_F: small3dlib's value representing 1.0.
|
|
- LCR_RENDERER_UNIT: for the renderer one map square is this many S3L_Units.
|
|
- TPE_Unit: tinyphysicsengine's unit. May change with phys. engine change.
|
|
- TPE_F: tinyphysicsengine's value representing value 1.0.
|
|
- LCR_PHYSICS_UNIT: for the phys. eng. one map square is this many TPE_Units.
|
|
|
|
COORDINATE SYSTEM AND ROTATIONS: The game itself (racing module) is
|
|
independent of rendering and physics libraries, but out of convenient adopts
|
|
their coordinate system (X right, Y up, Z forward) and rotations (Euler
|
|
angles, by Z, then by X, then Y).
|
|
*/
|
|
|
|
#ifndef _LCR_GAME_H
|
|
#define _LCR_GAME_H
|
|
|
|
#define LCR_KEY_UP 0x00
|
|
#define LCR_KEY_RIGHT 0x01
|
|
#define LCR_KEY_DOWN 0x02
|
|
#define LCR_KEY_LEFT 0x03
|
|
#define LCR_KEY_A 0x04 ///< confirm, restart race
|
|
#define LCR_KEY_B 0x05 ///< cancel, open menu
|
|
|
|
#define LCR_KEYS_TOTAL 6
|
|
|
|
/*
|
|
FOR FRONTENDS:
|
|
- Implement the below described functions according to their description.
|
|
- Implement the main program and game loop.
|
|
- Call the below described functions as described.
|
|
*/
|
|
|
|
/**
|
|
Implement this in your frontend. Returns 1 if given key is pressed or 0
|
|
otherwise.
|
|
*/
|
|
uint8_t LCR_keyPressed(uint8_t key);
|
|
|
|
/**
|
|
Implement this in your frontend. This function pauses program execution for
|
|
given amount of milliseconds and relieves the CPU usage. On platforms that
|
|
don't support this the function may simply do nothing.
|
|
*/
|
|
void LCR_sleep(uint16_t timeMs);
|
|
|
|
/**
|
|
Implement this in your frontend. This function draws a pixel of given color
|
|
to the screen back buffer (i.e. NOT directly to screen, back buffer shall
|
|
only be copied to front buffer once the LCR_gameStep function finishes all
|
|
rendering). This function should NOT check for out-of-screen coordinates, this
|
|
is handled by the game internals and out-of-screen pixels will never be drawn.
|
|
The color value depends on game settings but is normally an RGB565 value.
|
|
*/
|
|
void LCR_drawPixel(unsigned long index, uint16_t color);
|
|
|
|
/**
|
|
Implement this in your frontend. This function will be called to log what the
|
|
program is doing. If you want to ignore logging, simply make the function do
|
|
nothing.
|
|
*/
|
|
void LCR_log(const char *str);
|
|
|
|
/**
|
|
Call this function in your frontend at the start of the program.
|
|
*/
|
|
void LCR_gameInit(void);
|
|
|
|
/**
|
|
Call this function in your frontend right before program end.
|
|
*/
|
|
void LCR_gameEnd(void);
|
|
|
|
/**
|
|
Call this function in your frontend repeatedly inside the main loop, pass the
|
|
current time as the number of milliseconds since program start. This function
|
|
will perform the game step AND other things such as checking the input states,
|
|
rendering or sleeping (all using the above functions you should implement).
|
|
Returns 0 if program should end, otherwise 1.
|
|
*/
|
|
uint8_t LCR_gameStep(uint32_t timeMs);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
#define LCR_LOG0(s) ;
|
|
#define LCR_LOG1(s) ;
|
|
#define LCR_LOG2(s) ;
|
|
|
|
#if LCR_SETTING_LOG_LEVEL > 0
|
|
#undef LCR_LOG0
|
|
#define LCR_LOG0(s) LCR_log(s);
|
|
|
|
#if LCR_SETTING_LOG_LEVEL > 1
|
|
#undef LCR_LOG1
|
|
#define LCR_LOG1(s) LCR_log(s);
|
|
|
|
#if LCR_SETTING_LOG_LEVEL > 2
|
|
#undef LCR_LOG2
|
|
#define LCR_LOG2(s) LCR_log(s);
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
#define LCR_CONTROL_MODE_FREECAM 0x00
|
|
#define LCR_CONTROL_MODE_DRIVE 0x01
|
|
|
|
#define LCR_GAME_STATE_MENU 0x00
|
|
#define LCR_GAME_STATE_RUN_STARTING 0x01
|
|
#define LCR_GAME_STATE_RUN 0x02
|
|
#define LCR_GAME_STATE_RUN_FINISHED 0x03
|
|
|
|
struct
|
|
{
|
|
uint8_t state;
|
|
uint32_t stateStartTime;
|
|
|
|
uint32_t time;
|
|
uint32_t nextRenderFrameTime;
|
|
uint32_t nextRacingTickTime;
|
|
uint8_t controlMode;
|
|
uint8_t debugDraw;
|
|
uint8_t checkpointsTaken;
|
|
} LCR_game;
|
|
|
|
void LCR_drawPixelXYUnsafe(unsigned int x, unsigned int y,
|
|
uint16_t color);
|
|
|
|
/**
|
|
Internal pixel drawing function that checks for out-of-screen coordinates. Use
|
|
this if the pixel can potentially lie of screen (however if you know it won't,
|
|
use the normal unsafe function in sake of performance).
|
|
*/
|
|
static inline void LCR_drawPixelXYSafe(unsigned int x, unsigned int y,
|
|
uint_fast16_t color);
|
|
|
|
#include "racing.h"
|
|
#include "assets.h"
|
|
#include "renderer.h"
|
|
|
|
uint8_t LCR_keyStates[LCR_KEYS_TOTAL]; /**< Assures unchanging key states
|
|
during a single frame, holds
|
|
number of frames for which the key
|
|
has been continuously held. */
|
|
|
|
void LCR_drawPixelXYUnsafe(unsigned int x, unsigned int y,
|
|
uint16_t color)
|
|
{
|
|
#if LCR_SETTING_RESOLUTION_SUBDIVIDE == 1
|
|
LCR_drawPixel(y * LCR_SETTING_RESOLUTION_X + x,color);
|
|
#else
|
|
// TODO
|
|
#endif
|
|
}
|
|
|
|
static inline void LCR_drawPixelXYSafe(unsigned int x, unsigned int y,
|
|
uint_fast16_t color)
|
|
{
|
|
if (x < LCR_EFFECTIVE_RESOLUTION_X && y < LCR_EFFECTIVE_RESOLUTION_Y)
|
|
LCR_drawPixelXYUnsafe(x,y,color);
|
|
}
|
|
|
|
void LCR_gameSetState(uint8_t state)
|
|
{
|
|
LCR_LOG1("changing state");
|
|
LCR_game.state = state;
|
|
LCR_game.stateStartTime = LCR_game.time;
|
|
}
|
|
|
|
void LCR_gameResetRun(void)
|
|
{
|
|
LCR_LOG0("resetting run");
|
|
LCR_game.checkpointsTaken = 0;
|
|
LCR_mapReset();
|
|
LCR_rendererUnmarkCPs();
|
|
LCR_gameSetState(LCR_GAME_STATE_RUN_STARTING);
|
|
}
|
|
|
|
//void LCR_gameStartRun(const uint8_t *map)
|
|
void LCR_gameStartRun(const char *mapStr)
|
|
{
|
|
LCR_mapLoadFromStr(mapStr);
|
|
LCR_rendererLoadMap();
|
|
LCR_gameResetRun();
|
|
LCR_racingRestart();
|
|
}
|
|
|
|
void LCR_gameInit(void)
|
|
{
|
|
LCR_LOG0("initializing");
|
|
|
|
for (int i = 0; i < LCR_KEYS_TOTAL; ++i)
|
|
LCR_keyStates[i] = 0;
|
|
|
|
LCR_rendererInit();
|
|
LCR_racingInit();
|
|
|
|
LCR_game.nextRenderFrameTime = 0;
|
|
LCR_game.nextRacingTickTime = 0;
|
|
|
|
LCR_game.controlMode = LCR_CONTROL_MODE_FREECAM;
|
|
LCR_game.debugDraw = 0;
|
|
|
|
LCR_gameStartRun(LCR_maps[0]);
|
|
}
|
|
|
|
void LCR_gameEnd(void)
|
|
{
|
|
LCR_LOG0("ending");
|
|
}
|
|
|
|
uint8_t LCR_gameStep(uint32_t time)
|
|
{
|
|
LCR_LOG2("game step start");
|
|
|
|
LCR_game.time = time;
|
|
|
|
LCR_GameUnit carTransform[6];
|
|
|
|
for (int i = 0; i < LCR_KEYS_TOTAL; ++i)
|
|
LCR_keyStates[i] = LCR_keyPressed(i) ?
|
|
(LCR_keyStates[i] < 255 ? LCR_keyStates[i] + 1 : 255) : 0;
|
|
|
|
//S3L_logTransform3D(LCR_renderer.scene.camera.transform);
|
|
|
|
if ((LCR_racing.tick % 32) == 0)
|
|
{
|
|
printf("speed: %d\n",LCR_racingGetCarSpeedSigned());
|
|
|
|
}
|
|
|
|
uint32_t sleep = 0;
|
|
|
|
if (LCR_keyStates[LCR_KEY_B] == 1)
|
|
LCR_game.controlMode = LCR_game.controlMode == LCR_CONTROL_MODE_FREECAM ?
|
|
LCR_CONTROL_MODE_DRIVE : LCR_CONTROL_MODE_FREECAM;
|
|
else if (LCR_keyStates[LCR_KEY_B] == 30)
|
|
LCR_racingRestart();
|
|
else if (LCR_keyStates[LCR_KEY_B] == 60)
|
|
LCR_game.debugDraw = !LCR_game.debugDraw;
|
|
|
|
while (time >= LCR_game.nextRacingTickTime)
|
|
{
|
|
LCR_LOG2("gonna step racing engine");
|
|
unsigned int input = 0;
|
|
|
|
if (LCR_game.controlMode != LCR_CONTROL_MODE_FREECAM)
|
|
input =
|
|
(LCR_keyStates[LCR_KEY_UP] ? LCR_RACING_INPUT_FORW : 0) |
|
|
(LCR_keyStates[LCR_KEY_RIGHT] ? LCR_RACING_INPUT_RIGHT : 0) |
|
|
(LCR_keyStates[LCR_KEY_DOWN] ? LCR_RACING_INPUT_BACK : 0) |
|
|
(LCR_keyStates[LCR_KEY_LEFT] ? LCR_RACING_INPUT_LEFT : 0);
|
|
|
|
LCR_racingStep(input);
|
|
|
|
LCR_racingGetCarTransform(carTransform,carTransform + 3,0);
|
|
|
|
carTransform[0] = (carTransform[0] + (LCR_GAME_UNIT * LCR_MAP_SIZE_BLOCKS)
|
|
/ 2) / LCR_GAME_UNIT;
|
|
carTransform[1] = (carTransform[1] + (LCR_GAME_UNIT * LCR_MAP_SIZE_BLOCKS)
|
|
/ 4) / (LCR_GAME_UNIT / 2);
|
|
carTransform[2] = (carTransform[2] + (LCR_GAME_UNIT * LCR_MAP_SIZE_BLOCKS)
|
|
/ 2) / LCR_GAME_UNIT;
|
|
|
|
int blockIndex =
|
|
LCR_mapGetBlockAt(carTransform[0],carTransform[1],carTransform[2]);
|
|
|
|
if (blockIndex >= 0)
|
|
{
|
|
if (LCR_currentMap.blocks[blockIndex * LCR_BLOCK_SIZE] ==
|
|
LCR_BLOCK_CHECKPOINT_0)
|
|
{
|
|
LCR_LOG1("CP taken");
|
|
|
|
LCR_currentMap.blocks[blockIndex * LCR_BLOCK_SIZE] =
|
|
LCR_BLOCK_CHECKPOINT_1;
|
|
LCR_rendererMarkTakenCP(
|
|
carTransform[0],carTransform[1],carTransform[2]);
|
|
LCR_game.checkpointsTaken++;
|
|
}
|
|
else if (LCR_game.checkpointsTaken == LCR_currentMap.checkpointCount &&
|
|
LCR_currentMap.blocks[blockIndex * LCR_BLOCK_SIZE] == LCR_BLOCK_FINISH)
|
|
{
|
|
LCR_LOG1("finished");
|
|
|
|
// TODO
|
|
|
|
}
|
|
}
|
|
|
|
LCR_game.nextRacingTickTime += LCR_RACING_TICK_MS;
|
|
}
|
|
|
|
sleep = (3 * (LCR_game.nextRacingTickTime - time)) / 4;
|
|
|
|
if (time >= LCR_game.nextRenderFrameTime)
|
|
{
|
|
LCR_LOG2("gonna render next frame");
|
|
|
|
LCR_GameUnit physicsInterpolationParam = LCR_GAME_UNIT -
|
|
((LCR_game.nextRacingTickTime - time) * LCR_GAME_UNIT) /
|
|
LCR_RACING_TICK_MS;
|
|
|
|
LCR_racingGetCarTransform(carTransform,carTransform + 3,
|
|
physicsInterpolationParam);
|
|
|
|
LCR_rendererSetCarTransform(carTransform,carTransform + 3);
|
|
|
|
while (time >= LCR_game.nextRenderFrameTime)
|
|
LCR_game.nextRenderFrameTime += 1000 / LCR_SETTING_FPS;
|
|
|
|
LCR_GameUnit offsets[5];
|
|
|
|
for (int i = 0; i < 5; ++i)
|
|
offsets[i] = 0;
|
|
|
|
if (LCR_game.controlMode == LCR_CONTROL_MODE_FREECAM)
|
|
{
|
|
if (LCR_keyStates[LCR_KEY_A])
|
|
{
|
|
if (LCR_keyStates[LCR_KEY_UP])
|
|
offsets[4] = LCR_FREE_CAMERA_TURN_STEP;
|
|
else if (LCR_keyStates[LCR_KEY_DOWN])
|
|
offsets[4] -= LCR_FREE_CAMERA_TURN_STEP;
|
|
|
|
if (LCR_keyStates[LCR_KEY_RIGHT])
|
|
offsets[3] -= LCR_FREE_CAMERA_TURN_STEP;
|
|
else if (LCR_keyStates[LCR_KEY_LEFT])
|
|
offsets[3] = LCR_FREE_CAMERA_TURN_STEP;
|
|
}
|
|
else
|
|
{
|
|
if (LCR_keyStates[LCR_KEY_UP])
|
|
offsets[0] = LCR_FREE_CAMERA_STEP;
|
|
else if (LCR_keyStates[LCR_KEY_DOWN])
|
|
offsets[0] -= LCR_FREE_CAMERA_STEP;
|
|
|
|
if (LCR_keyStates[LCR_KEY_RIGHT])
|
|
offsets[1] = LCR_FREE_CAMERA_STEP;
|
|
else if (LCR_keyStates[LCR_KEY_LEFT])
|
|
offsets[1] -= LCR_FREE_CAMERA_STEP;
|
|
}
|
|
|
|
LCR_rendererMoveCamera(offsets,offsets + 3);
|
|
}
|
|
else
|
|
LCR_rendererCameraFollow();
|
|
|
|
#if LCR_ANIMATE_CAR
|
|
LCR_rendererSetWheelState(LCR_racingGetWheelRotation(),
|
|
LCR_racingGetWheelSteer() * 2);
|
|
#endif
|
|
|
|
LCR_rendererDraw();
|
|
|
|
LCR_rendererDrawText("abcdefghijklmnopqrstuvwxyz",10,20,0,2);
|
|
|
|
if (LCR_game.debugDraw)
|
|
{
|
|
LCR_GameUnit camTr[7];
|
|
LCR_rendererGetCameraTransform(camTr,camTr + 3,camTr + 6);
|
|
LCR_physicsDebugDraw(camTr,camTr + 3,camTr[6]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32_t tmp = (3 * (LCR_game.nextRenderFrameTime - time)) / 4;
|
|
sleep = tmp < sleep ? tmp : sleep;
|
|
}
|
|
|
|
if (sleep)
|
|
LCR_sleep(sleep);
|
|
|
|
LCR_LOG2("game step end");
|
|
|
|
return 1;
|
|
}
|
|
|
|
#endif // guard
|