Now that the component and entity are basically implemented with a manager, the next step is to actually use them.
Scenes
In a game, there would be many Scenes, such as main game loop, main menu, pause screen, etc. Each Scene has its own logic, systems, key handling, and entities.
The lecture uses OO design approach to implementing Scenes. It introduces a BaseScene interface class where it would contains common Scene function and data. Subsequent Scenes will derived from the BaseScene to implement Scene-specific logic.
Unfortunately, once again, C does not have such convenience. So, I have to emulate it somewhat. The implementation will also have a base Scene struct. Subsequent implemented Scene will need to contain a Scene field and a Scene-specific data field.
The base Scene struct looks like this in scene.h
:
struct Scene
{
struct sc_map_64 action_map; // key -> actions
struct sc_array_systems systems;
system_func_t render_function;
action_func_t action_function;
EntityManager_t ent_manager;
SceneType_t scene_type;
void * scene_data;
bool paused;
bool has_ended;
};
Note that in the base Scene struct, it holds a pointer the scene-specific data.
As a starter, the LevelScene struct, which is where the gameplay takes place, is implemented as such in scene_impl.h
:
typedef struct LevelScene
{
Scene_t scene;
LevelSceneData_t data;
}LevelScene_t;
typedef struct LevelSceneData
{
Vector2 player_dir;
TileGrid_t tilemap;
bool jumped_pressed;
bool crouch_pressed;
}LevelSceneData_t;
As seen above, these data fields are specific to the LevelScene. The LevelSceneData is then set as the Scene scene_data
pointer during initialisation.
Systems
As mentioned before, systems are functions that describes the interactions of components and/or entities. Systems can vary from Scene to Scene. In this implementation, a system is a function pointer that takes in a pointer to a base Scene class and returns nothing. Using the Scene class, Scene-specific data can be accessed via the scene_data
pointer, while the entities and/or components can be access via the ent_manager
field.
typedef void(*system_func_t)(Scene_t *);
During the loop, the systems are executed in a linear order. Therefore, the order of the systems added to the Scene is important.
Input Handling
Actions
As per the lecture, Actions are introduced as interfaces to the physical controls used. The buttons/keys/motions of the physical inputs are translated into these Actions via a defined mapping that is registered during runtime.
Actions can perform different things for different Scenes. E.g. the Up action in a main menu would move the selection pointer upwards, while that may cause the player to jump in a gameplay scene.
Therefore, Scenes must also implement what Actions would do for that scene under action_function
field. The action function is also a function pointer:
typedef void(*action_func_t)(Scene_t *, ActionType_t, bool);
In the implementation, an action has two states: either pressed or released. This is the last input of the function pointer. A hold is just pressed sustained for multiple frames.
Having Actions implemented allows for replay system implementation as described in the lecture series. However, this is not considered at this early stage of implementation.
Key Handling
The game will be implemented with keyboard in mind, as per the original game.
Raylib provides functions to detect key presses and releases. One nice function is the GetKeyPressed
function which gives the keys pressed (not hold) for the frame. Internally, Raylib maintains a queue for the key pressed. However, it does not have a corresponding function to detect key releases.
To deal with this, a separate queue is used to maintain the key already pressed in order to check for releases. In the implementation, the keys that are pressed previously are checked for release. Then, it will check for any new key presses. A snippet for this logic can be seen in scene_test.c
struct sc_queue_32 key_buffer;
for (size_t i=0; i<sz; i++)
{
int button = sc_queue_del_first(&key_buffer);
ActionType_t action = sc_map_get_64(&scene.scene.action_map, button);
if (IsKeyReleased(button))
{
do_action(&scene.scene, action, false);
}
else
{
do_action(&scene.scene, action, true);
sc_queue_add_last(&key_buffer, button);
}
}
while(true)
{
int button = GetKeyPressed();
if (button == 0) break;
ActionType_t action = sc_map_get_64(&scene.scene.action_map, button);
if (!sc_map_found(&scene.scene.action_map)) continue;
do_action(&scene.scene, action, true);
sc_queue_add_last(&key_buffer, button);
}