2 Step 1: Scene, Systems, and Input Handling
sadpmpk edited this page 2023-01-20 22:31:16 -05:00

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);
}