video.mp4
This game was created with implementation of AK-mOS(mini RTOS) and Oled GUI(Graphic user interface). With the purpose to test, learn and pratice the use of Event-driven-based with tasks.
The development of AK-mOS and Oled GUI are being updated, which will be released in the future.
See AK Base Kit for more information
The kit is pretty cool, which has 3 buttons, 1 buzzer and 1 Oled 1.3" display. Those are enough for game development like any other kit such as Arduboy,...
Not only that, the kit but also has RS485, NRF24L01+, and External Flash 32MB suitable for application prototypes in embedded systems or uses such as: wired communication, wireless communication, data logger storage applications, multiple application loader,...
To be honest im not a gaming guy. So to think the game i like and make it as we have been playing on retro game console like this, i cant remember.
But at least i like playing FPS game(Valorant, CSGO,...). The idea is making a running game but make it FPS :))). I see the guy made his Evasion game and i want to make it too.
We have 3 button - UP, DOWN, MODE.
-
UP and DOWN are used to navigate, in menu screens is used to go up and down. In game screen, use to turn left and right.
-
MODE is used to select, hold MODE to exit from screens.
Turn left and turn right to evade the columns. Every step you have gone is your score. Game has 3 fixed speed modes but the way to calculate score is the same. So it is one chart for all modes.
Object | Description |
---|---|
Column | Move to you every step |
Star | Effect, make the game more realistic |
Player view | Not visible, can move to left or right |
-
A column has of course width and height, depth(distance from column to player view), and 1 object of GUI to draw rectangular which takes 3 params(width, height and fade(the farther the blurrier))
-
A star has only height(width = 1), depth and 1 obj of GUI to draw a dot.
-
Player has step width and step depth(these params are fixed). Every time player steps, the attributes of stars and columns are recalculated, that make the feel moving in 3D space.
The AK-mOS task is independent, it has its own message queue. Because its independant, the private stack is required for every task, with big task - big stack size. Any task that listens to message needs message queue to store message to process. Task with no message queue can normally send message to other tasks.
Some important aspects of task:
- Event Handling: Tasks are used to handle messages fired when an event occurs. Each task can be associated with a specific event and execute a series of actions when that event occurs.
- Synchronization: Tasks provide a synchronization mechanism for handling events. When an event occurs, the corresponding task is triggered and executed. Other tasks will wait until the current task completes before being triggered. This helps ensure that event handling actions are performed in a certain order and avoid conflicts.
- Control Flow Management: Tasks allow event flow management in event-driven applications. By using tasks, you can determine the order of execution of actions when different events occur.
- Logic Separation: Using tasks helps separate event handling logic, which makes the Source code clear and easy to read.
- Task hierarchy: Task level allows to arrange the priority order of processing task messages in the system queue.
- Avoid blocking fucntion: With application using GUI, the GUI need to be placed in seprated task. GUI every interval of time will refresh display, it will block other task if tasks are not prioritized and separated. This application places GUI at lowest priority.
This is list of all tasks in application:
Task ID | Task Handle | Task priority | Message queue size | Stack size |
---|---|---|---|---|
TASK_BTN_ID | task_btn | 0 | 0 | 100 |
TASK_BUZZER_ID | task_buzzer | 1 | 8 | 100 |
TASK_TIMER_ID | task_timer | 2 | 8 | 100 |
TASK_SCR_ID | task_scr | 3 | 16 | 250 |
TASK_GUI_ID | task_gui | 4 | 0 | 150 |
To understand this easier, i will describle from high priority to low. (Task with lower number get highest priority).
- NOTE: Task with same priority can also run in round-robin schem.
Task button has highest prio, in order to not missing any event from user.(I want it to turn left, but it ignore me!)
Task button using button driver, to init buttons, first it calls:
/* button init */
button_init(&btn_mode, 10, BUTTON_MODE_ID, io_button_mode_init, io_button_mode_read, btn_mode_callback);
button_init(&btn_up, 10, BUTTON_UP_ID, io_button_up_init, io_button_up_read, btn_up_callback);
button_init(&btn_down, 10, BUTTON_DOWN_ID, io_button_down_init, io_button_down_read, btn_down_callback);
button_enable(&btn_mode);
button_enable(&btn_up);
button_enable(&btn_down);
And then poll state of buttons at every 10ms:
void task_btn(void *p_arg)
{
while(1)
{
button_timer_polling(&btn_mode);
button_timer_polling(&btn_up);
button_timer_polling(&btn_down);
os_task_delay(10);
}
}
If any of these button has an event (PRESSED, REALEASED, LONG PRESSED), associated funtion will be invoked and send signal to Task screen(described later).
void btn_mode_callback(void* b) {
button_t* me_b = (button_t*)b;
switch (me_b->state) {
case BUTTON_SW_STATE_PRESSED: {
os_task_post_msg_pure(TASK_SCR_ID, AC_DISPLAY_BUTTON_MODE_PRESSED);
}
break;
case BUTTON_SW_STATE_LONG_PRESSED: {
os_task_post_msg_pure(TASK_SCR_ID, AC_DISPLAY_BUTTON_MODE_LONG_PRESSED);
}
break;
case BUTTON_SW_STATE_RELEASED: {
os_task_post_msg_pure(TASK_SCR_ID, AC_DISPLAY_BUTTON_MODE_RELEASED);
}
break;
default:
break;
}
}
Task buzzer does not run frequently, so it has prio high afer the highest.
Task buzzer using buzzer driver, to init buzzer, first it calls:
BUZZER_Init();
And then in task it looks like:
void task_buzzer(void *p_arg)
{
msg_t *msg;
int32_t sig = OS_CFG_DELAY_MAX;
while (1)
{
msg = os_task_wait_for_msg(sig);
if(msg != NULL)
{
sig = msg->sig;
os_msg_free(msg);
}
if(sig != OS_CFG_DELAY_MAX)
{
switch (sig)
{
case AC_BUZZER_PLAY_TONE_MOVE:
play_track(tones_USB_dis);
break;
case AC_BUZZER_PLAY_TONE_DEATH:
play_track(tones_startup);
break;
default:
break;
}
sig = OS_CFG_DELAY_MAX;
}
}
}
This task will wait indefinitely for signal to decided which tone to play. These signals come from Task screen when player turns left or right or when game is over.
Task timer runs more frequently than task buzzer, so it has prio after task buzzer
Task time has message queue to wait for signals. Signal that this task need is the interger number determine interval to send message to update screen.
void task_timer(void *p_arg)
{
msg_t *msg;
int32_t sig = OS_CFG_DELAY_MAX;
while (1)
{
msg = os_task_wait_for_msg(sig);
if(msg != NULL)
{
sig = msg->sig;
os_msg_free(msg);
}
if(sig != OS_CFG_DELAY_MAX)
{
os_task_post_msg_pure(TASK_SCR_ID, AC_DISPLAY_GAME_UPDATE);
}
}
}
Task screen manage all screens in this application including:
-
Start-up screen:
-
Menu game screen:
-
Chart game scree:
-
Setting game screen:
-
Main game screen:
Task screen takes message queue size of 16. These signals below are what this task receives and sends:
/* define signal */
enum {
AC_DISPLAY_BUTTON_MODE_PRESSED = 1,
AC_DISPLAY_BUTTON_MODE_LONG_PRESSED,
AC_DISPLAY_BUTTON_MODE_RELEASED,
AC_DISPLAY_BUTTON_UP_PRESSED,
AC_DISPLAY_BUTTON_UP_LONG_PRESSED,
AC_DISPLAY_BUTTON_UP_RELEASED,
AC_DISPLAY_BUTTON_DOWN_PRESSED,
AC_DISPLAY_BUTTON_DOWN_LONG_PRESSED,
AC_DISPLAY_BUTTON_DOWN_RELEASED,
AC_DISPLAY_GAME_UPDATE
};
/* define signal */
enum {
AC_BUZZER_PLAY_TONE_MOVE = 1,
AC_BUZZER_PLAY_TONE_DEATH
};
Signals start with AC_DISPLAY_BUTTON_... are sent from Task button.
AC_DISPLAY_GAME_UPDATE is sent from Task timer.
Signals start with AC_BUZZER_... are sent from this task to Task buzzer to play tones.
Task GUI using OLED GUI(is being developed) first call gui_init() to init oled display, obj of gui chain, ... And then call gui_run() to update objects and refresh display whenever it has a chance to run.
gui_init() is called before AK-mOS initialization to save task GUI's stack size.
Task GUI takes no message.
void task_gui(void *p_arg)
{
while(1)
{
gui_run();
}
}
For better understand, the sequence diagram is given below: