835 lines
19 KiB
C
835 lines
19 KiB
C
#include "GameEngine.h"
|
|
#include "AssetManager.h"
|
|
#include "BitmapFont.h"
|
|
#include "ObjectStore.h"
|
|
#include "Renderer.h"
|
|
#include "SDL.h"
|
|
#include "SDL_error.h"
|
|
#include "SDL_events.h"
|
|
#include "SDL_hints.h"
|
|
#include "SDL_image.h"
|
|
#include "SDL_keycode.h"
|
|
#include "SDL_mixer.h"
|
|
#include "SDL_pixels.h"
|
|
#include "SDL_platform.h"
|
|
#include "SDL_rect.h"
|
|
#include "SDL_render.h"
|
|
#include "SDL_surface.h"
|
|
#include "SDL_system.h"
|
|
#include "SDL_timer.h"
|
|
#include "SDL_ttf.h"
|
|
#include "SDL_video.h"
|
|
#include "Window.h"
|
|
#include "aliases.h"
|
|
#include "constants.h"
|
|
#include "reusable_ids.h"
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if DEBUG_ENGINE
|
|
#include <stdio.h>
|
|
#endif /* DEBUG_ENGINE */
|
|
|
|
INTERNAL struct game_engine {
|
|
bool running;
|
|
f32 delta;
|
|
u64 last_time;
|
|
Window window;
|
|
SDL_Event event;
|
|
ObjectStore *object_store;
|
|
AssetManager *asset_manager;
|
|
} GameEngine;
|
|
|
|
bool init_engine(const char *game_name, WindowSize window_size,
|
|
SDL_Color window_background_color, bool full_screen,
|
|
u8 audio_tracks) {
|
|
bool initialisation_complete = true;
|
|
const char *current_platform = SDL_GetPlatform();
|
|
|
|
if (strcmp(current_platform, "Linux") == 0) {
|
|
SDL_SetHint(SDL_HINT_VIDEODRIVER, "wayland,x11");
|
|
}
|
|
|
|
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
|
#if DEBUG_ENGINE
|
|
printf("Failed to initialise SDL. Error: %s\n", SDL_GetError());
|
|
#endif /* DEBUG_ENGINE */
|
|
|
|
return false;
|
|
}
|
|
|
|
u32 img_flags = IMG_INIT_PNG;
|
|
if (strcmp(current_platform, "Android") != 0) {
|
|
// Apparently, Android devices don't support TIFF images, so we only enable
|
|
// TIFF support when we are not running Android.
|
|
//
|
|
// TODO (Abdelrahman): Check if iOS devices support TIFF images.
|
|
img_flags |= IMG_INIT_TIF;
|
|
}
|
|
|
|
if ((IMG_Init(img_flags) & img_flags) != img_flags) {
|
|
#if DEBUG_ENGINE
|
|
printf("Failed to initialise SDL_image. Error: %s\n", IMG_GetError());
|
|
#endif /* DEBUG_ENGINE */
|
|
initialisation_complete = false;
|
|
}
|
|
|
|
if (TTF_Init() < 0) {
|
|
#if DEBUG_ENGINE
|
|
printf("Failed to initialise SDL_ttf. Error: %s\n", TTF_GetError());
|
|
#endif /* DEBUG_ENGINE */
|
|
initialisation_complete = false;
|
|
}
|
|
|
|
// We're only interested in initialising SDL_mixer with WAVE files, so no
|
|
// need to pass any flags
|
|
if (Mix_Init(0) != 0) {
|
|
#if DEBUG_ENGINE
|
|
printf("Failed to initialise SDL_mixer. Error: %s\n", Mix_GetError());
|
|
#endif /* DEBUG_ENGINE */
|
|
initialisation_complete = false;
|
|
}
|
|
|
|
if (Mix_OpenAudio(48000, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
|
|
#if DEBUG_ENGINE
|
|
printf("Failed to open audio device. Error: %s\n", Mix_GetError());
|
|
#endif /* DEBUG_ENGINE */
|
|
initialisation_complete = false;
|
|
}
|
|
|
|
if (audio_tracks > 0) {
|
|
if (Mix_AllocateChannels(audio_tracks) != audio_tracks) {
|
|
#if DEBUG_ENGINE
|
|
printf("Failed to allocate the required tracks.\n");
|
|
#endif /* DEBUG_ENGINE */
|
|
initialisation_complete = false;
|
|
}
|
|
}
|
|
|
|
if (!init_window(&(GameEngine.window), game_name, window_size,
|
|
window_background_color, full_screen)) {
|
|
#if DEBUG_ENGINE
|
|
printf("Failed to initialise window\n");
|
|
#endif /* DEBUG_ENGINE */
|
|
|
|
initialisation_complete = false;
|
|
}
|
|
|
|
GameEngine.object_store = create_object_store();
|
|
if (!(GameEngine.object_store)) {
|
|
initialisation_complete = false;
|
|
}
|
|
|
|
GameEngine.asset_manager = create_asset_manager();
|
|
if (!(GameEngine.asset_manager)) {
|
|
initialisation_complete = false;
|
|
}
|
|
|
|
if (!initialisation_complete) {
|
|
close_engine();
|
|
|
|
return false;
|
|
}
|
|
|
|
GameEngine.running = true;
|
|
GameEngine.delta = 0.0f;
|
|
GameEngine.last_time = SDL_GetTicks64();
|
|
|
|
#if DEBUG_ENGINE
|
|
GameEngine.delta = 1.0f / 60.0f;
|
|
#endif /* DEBUG_ENGINE */
|
|
|
|
#if DEBUG_ENGINE
|
|
printf("Game initialised successfully\n");
|
|
#endif /* DEBUG_ENGINE */
|
|
|
|
return true;
|
|
}
|
|
|
|
bool game_running() { return GameEngine.running; }
|
|
|
|
void stop_game() { GameEngine.running = false; }
|
|
|
|
void close_engine() {
|
|
destroy_asset_manager(&(GameEngine.asset_manager));
|
|
|
|
destroy_object_store(&(GameEngine.object_store));
|
|
|
|
destroy_window(&(GameEngine.window));
|
|
|
|
GameEngine.last_time = 0.0f;
|
|
|
|
GameEngine.delta = 0.0f;
|
|
|
|
GameEngine.running = false;
|
|
|
|
Mix_CloseAudio();
|
|
|
|
Mix_Quit();
|
|
|
|
TTF_Quit();
|
|
|
|
IMG_Quit();
|
|
|
|
SDL_Quit();
|
|
|
|
#if DEBUG_ENGINE
|
|
printf("Game closed successfully\n");
|
|
#endif /* DEBUG_ENGINE */
|
|
}
|
|
|
|
i64 get_time() { return SDL_GetTicks64(); }
|
|
|
|
ObjectID add_object(void *object, EventHandlerPtr event_handler,
|
|
UpdateFuncPtr update_func, RenderFuncPtr render_func) {
|
|
if (!(GameEngine.running) || !(GameEngine.object_store)) {
|
|
return -1;
|
|
}
|
|
|
|
return add_object_to_store(GameEngine.object_store, object, event_handler,
|
|
update_func, render_func);
|
|
}
|
|
|
|
#ifdef __ANDROID__
|
|
const char *get_android_internal_storage(void) {
|
|
return SDL_AndroidGetInternalStoragePath();
|
|
}
|
|
#endif // __ANDROID__
|
|
|
|
void remove_object(ObjectID *id) {
|
|
if (!(GameEngine.running) || !(GameEngine.object_store)) {
|
|
return;
|
|
}
|
|
|
|
remove_object_from_store(GameEngine.object_store, id);
|
|
}
|
|
|
|
AssetID load_asset_from_file(const char *path, AssetType type) {
|
|
return type == ASSET_TYPE_IMAGE
|
|
? load_asset_to_manager(GameEngine.asset_manager, path, type,
|
|
GameEngine.window.renderer, NULL)
|
|
: load_asset_to_manager(GameEngine.asset_manager, path, type, NULL,
|
|
NULL);
|
|
}
|
|
|
|
void unload_asset(AssetID *id) {
|
|
if (*id < 0) {
|
|
return;
|
|
}
|
|
|
|
remove_asset_from_manager(GameEngine.asset_manager, id);
|
|
}
|
|
|
|
#ifdef __ANDROID__
|
|
BitmapFont *load_bitmap_font(const char *filepath, u64 tile_width,
|
|
u64 tile_height) {
|
|
return create_bitmap_font(GameEngine.asset_manager,
|
|
GameEngine.window.renderer, filepath, tile_width,
|
|
tile_height);
|
|
}
|
|
|
|
void unload_bitmap_font(BitmapFont **font) {
|
|
destroy_bitmap_font(GameEngine.asset_manager, font);
|
|
}
|
|
#endif /* ifdef __ANDROID__ */
|
|
|
|
WindowSize get_window_defined_size() { return GameEngine.window.defined_size; }
|
|
|
|
WindowSize get_window_real_size() { return GameEngine.window.real_size; }
|
|
|
|
WindowDrawingArea get_window_drawing_area() {
|
|
return GameEngine.window.drawing_area;
|
|
}
|
|
|
|
void draw_rectangle(RenderRect rect, SDL_Color color) {
|
|
draw_rect(GameEngine.window.renderer, color, rect.x, rect.y, rect.width,
|
|
rect.height);
|
|
}
|
|
|
|
void fill_rectangle(RenderRect rect, SDL_Color color) {
|
|
fill_rect(GameEngine.window.renderer, color, rect.x, rect.y, rect.width,
|
|
rect.height);
|
|
}
|
|
|
|
void clip_texture_render_rects_to_drawing_area(RenderRect *src,
|
|
RenderRect *dest) {
|
|
if (!dest) {
|
|
return;
|
|
}
|
|
|
|
WindowDrawingArea area = get_window_drawing_area();
|
|
|
|
// If the rectangle is completely out of bounds, set its width and height to 0
|
|
if (dest->x > area.x + area.width || dest->x + dest->width < area.x ||
|
|
dest->y > area.y + area.height || dest->y + dest->height < area.y) {
|
|
if (src) {
|
|
src->width = 0.0f;
|
|
src->height = 0.0f;
|
|
}
|
|
|
|
dest->width = 0.0f;
|
|
dest->height = 0.0f;
|
|
|
|
return;
|
|
}
|
|
|
|
f32 x_offset = 0.0f;
|
|
f32 y_offset = 0.0f;
|
|
f32 x_clip = 0.0f;
|
|
f32 y_clip = 0.0f;
|
|
|
|
// If the rectangle is partially out of the horizontal bounds of the drawing
|
|
// area, calculate how much of its width will be clipped
|
|
if (dest->x < area.x) {
|
|
// When an object goes out of the left border of the screen, we need to both
|
|
// clip and offset the source and destination rectangles to ensure that the
|
|
// object is rendered properly
|
|
x_clip = x_offset = area.x - dest->x;
|
|
} else if (dest->x + dest->width > area.x + area.width) {
|
|
x_clip = (dest->x + dest->width) - (area.x + area.width);
|
|
}
|
|
|
|
// If the rectangle is partially out of the vertical bounds of the drawing
|
|
// area, calculate how much of its height will be clipped
|
|
if (dest->y < area.y) {
|
|
// When an object goes out of the top border of the screen, we need to both
|
|
// clip and offset the source and destination rectangles to ensure that the
|
|
// object is rendered properly
|
|
y_clip = y_offset = area.y - dest->y;
|
|
} else if (dest->y + dest->height > area.y + area.height) {
|
|
y_clip = (dest->y + dest->height) - (area.y + area.height);
|
|
}
|
|
|
|
if (src) {
|
|
// Offset and clipping of source rectangle needs to be calculated based on
|
|
// the ratio of the x_clip and y_clip variables to the width and height of
|
|
// the destination rectangle respectively. This is because the source
|
|
// rectangle's width and height might be different from the destination's
|
|
src->x += ((x_offset / dest->width) * src->width);
|
|
src->y += ((y_offset / dest->height) * src->height);
|
|
src->width *= 1 - (x_clip / dest->width);
|
|
src->height *= 1 - (y_clip / dest->height);
|
|
}
|
|
|
|
dest->x += x_offset;
|
|
dest->y += y_offset;
|
|
dest->width -= x_clip;
|
|
dest->height -= y_clip;
|
|
}
|
|
|
|
RenderRect get_texture_asset_rect(AssetID id) {
|
|
RenderRect rect = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
SDL_Texture *texture = get_texture(GameEngine.asset_manager, id);
|
|
|
|
if (texture) {
|
|
i32 w = 0;
|
|
i32 h = 0;
|
|
if (SDL_QueryTexture(texture, NULL, NULL, &w, &h) == 0) {
|
|
rect.width = w;
|
|
rect.height = h;
|
|
}
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
void render_texture_asset(AssetID id, const RenderRect *src,
|
|
const RenderRect *dest) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
SDL_Texture *texture = get_texture(GameEngine.asset_manager, id);
|
|
|
|
if (!texture) {
|
|
return;
|
|
}
|
|
|
|
render_texture(GameEngine.window.renderer, texture, src, dest);
|
|
}
|
|
|
|
void modulate_texture_color(AssetID id, SDL_Color color) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
SDL_Texture *texture = get_texture(GameEngine.asset_manager, id);
|
|
|
|
if (!texture) {
|
|
return;
|
|
}
|
|
|
|
set_texture_color_modulation(texture, color);
|
|
}
|
|
|
|
void modulate_texture_alpha(AssetID id, u8 alpha) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
SDL_Texture *texture = get_texture(GameEngine.asset_manager, id);
|
|
|
|
if (!texture) {
|
|
return;
|
|
}
|
|
|
|
set_texture_alpha_modulation(texture, alpha);
|
|
}
|
|
|
|
TextSize get_text_size_from_font_asset(AssetID id, const char *text) {
|
|
TextSize output = {};
|
|
|
|
if (id < 0) {
|
|
return output;
|
|
}
|
|
|
|
TTF_Font *font = get_font(GameEngine.asset_manager, id);
|
|
if (!font) {
|
|
return output;
|
|
}
|
|
|
|
TTF_SizeUTF8(font, text, &(output.width), &(output.height));
|
|
|
|
return output;
|
|
}
|
|
|
|
void set_font_asset_size(AssetID id, u32 size) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
TTF_Font *font = get_font(GameEngine.asset_manager, id);
|
|
if (!font) {
|
|
return;
|
|
}
|
|
|
|
TTF_SetFontSize(font, size);
|
|
}
|
|
|
|
i32 set_font_direction(AssetID id, FontDirection direction) {
|
|
if (id < 0) {
|
|
return -2;
|
|
}
|
|
|
|
TTF_Font *font = get_font(GameEngine.asset_manager, id);
|
|
if (!font) {
|
|
return -2;
|
|
}
|
|
|
|
return TTF_SetFontDirection(font, (TTF_Direction)direction);
|
|
}
|
|
|
|
i32 set_font_script_name(AssetID id, const char *script) {
|
|
if (id < 0) {
|
|
return -2;
|
|
}
|
|
|
|
TTF_Font *font = get_font(GameEngine.asset_manager, id);
|
|
if (!font) {
|
|
return -2;
|
|
}
|
|
|
|
return TTF_SetFontScriptName(font, script);
|
|
}
|
|
|
|
void render_text_with_font_asset(AssetID id, const char *text,
|
|
const RenderRect *src, RenderRect dest,
|
|
SDL_Color color) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
TTF_Font *font = get_font(GameEngine.asset_manager, id);
|
|
if (!font) {
|
|
return;
|
|
}
|
|
|
|
SDL_Surface *surface = TTF_RenderUTF8_Solid(font, text, color);
|
|
if (!surface) {
|
|
return;
|
|
}
|
|
|
|
render_surface(GameEngine.window.renderer, surface, src, &dest);
|
|
|
|
SDL_FreeSurface(surface);
|
|
surface = NULL;
|
|
}
|
|
|
|
#ifdef __ANDROID__
|
|
void render_text_with_bitmap_font(BitmapFont *font, const char *text,
|
|
u64 font_size, u64 start_index,
|
|
RenderRect dest, SDL_Color color) {
|
|
render_bitmap_font_text(GameEngine.asset_manager, GameEngine.window.renderer,
|
|
font, font_size, text, start_index, dest, color);
|
|
}
|
|
#endif /* ifdef __ANDROID__ */
|
|
|
|
i8 play_audio_asset(AssetID id, i16 loops, bool fade_in,
|
|
u32 fade_length_in_ms) {
|
|
if (id < 0) {
|
|
return -1;
|
|
}
|
|
|
|
Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk) {
|
|
return -1;
|
|
}
|
|
|
|
if (fade_in) {
|
|
return Mix_FadeInChannel(-1, chunk, loops, fade_length_in_ms);
|
|
} else {
|
|
return Mix_PlayChannel(-1, chunk, loops);
|
|
}
|
|
}
|
|
|
|
void pause_audio_asset_on_track(AssetID id, u8 track) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk || chunk != Mix_GetChunk(track)) {
|
|
return;
|
|
}
|
|
|
|
Mix_Pause(track);
|
|
}
|
|
|
|
void resume_audio_asset_on_track(AssetID id, u8 track) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk || chunk != Mix_GetChunk(track)) {
|
|
return;
|
|
}
|
|
|
|
Mix_Resume(track);
|
|
}
|
|
|
|
void stop_audio_asset_on_track(AssetID id, u8 track) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk || chunk != Mix_GetChunk(track)) {
|
|
return;
|
|
}
|
|
|
|
Mix_HaltChannel(track);
|
|
}
|
|
|
|
void fade_out_audio_asset_on_track(AssetID id, u8 track,
|
|
u32 fade_length_in_ms) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk || chunk != Mix_GetChunk(track)) {
|
|
return;
|
|
}
|
|
|
|
Mix_FadeOutChannel(track, fade_length_in_ms);
|
|
}
|
|
|
|
void pause_all_instances_of_audio_asset(AssetID id) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk) {
|
|
return;
|
|
}
|
|
|
|
u64 channel_count = Mix_AllocateChannels(-1);
|
|
|
|
for (size_t i = 0; i < channel_count; ++i) {
|
|
if (chunk == Mix_GetChunk(i)) {
|
|
Mix_Pause(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void resume_all_instances_of_audio_asset(AssetID id) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk) {
|
|
return;
|
|
}
|
|
|
|
u64 channel_count = Mix_AllocateChannels(-1);
|
|
|
|
for (size_t i = 0; i < channel_count; ++i) {
|
|
if (chunk == Mix_GetChunk(i)) {
|
|
Mix_Resume(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void stop_all_instances_of_audio_asset(AssetID id) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk) {
|
|
return;
|
|
}
|
|
|
|
u64 channel_count = Mix_AllocateChannels(-1);
|
|
|
|
for (size_t i = 0; i < channel_count; ++i) {
|
|
if (chunk == Mix_GetChunk(i)) {
|
|
Mix_HaltChannel(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void fade_out_all_instances_of_audio_asset(AssetID id, u32 fade_length_in_ms) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk) {
|
|
return;
|
|
}
|
|
|
|
u64 channel_count = Mix_AllocateChannels(-1);
|
|
|
|
for (size_t i = 0; i < channel_count; ++i) {
|
|
if (chunk == Mix_GetChunk(i)) {
|
|
Mix_FadeOutChannel(i, fade_length_in_ms);
|
|
}
|
|
}
|
|
}
|
|
|
|
i8 get_audio_asset_volume(AssetID id) {
|
|
if (id < 0) {
|
|
return -1;
|
|
}
|
|
|
|
const Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk) {
|
|
return -1;
|
|
}
|
|
|
|
return chunk->volume;
|
|
}
|
|
|
|
void set_audio_asset_volume(AssetID id, i8 volume) {
|
|
if (id < 0) {
|
|
return;
|
|
}
|
|
|
|
Mix_Chunk *chunk = get_audio_chunk(GameEngine.asset_manager, id);
|
|
if (!chunk) {
|
|
return;
|
|
}
|
|
|
|
Mix_VolumeChunk(chunk, volume);
|
|
}
|
|
|
|
void play_music_asset(AssetID id, i16 loops, bool fade_in,
|
|
u32 fade_length_in_ms) {
|
|
if (id < 0 || is_music_asset_playing(id)) {
|
|
return;
|
|
}
|
|
|
|
Mix_Music *music = get_music(GameEngine.asset_manager, id);
|
|
if (!music) {
|
|
return;
|
|
}
|
|
|
|
if (fade_in) {
|
|
Mix_FadeInMusic(music, loops, fade_length_in_ms);
|
|
} else {
|
|
Mix_PlayMusic(music, loops);
|
|
}
|
|
}
|
|
|
|
void pause_music_asset(AssetID id) {
|
|
if (id < 0 || !is_music_asset_playing(id)) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Music *music = get_music(GameEngine.asset_manager, id);
|
|
if (!music) {
|
|
return;
|
|
}
|
|
|
|
Mix_PauseMusic();
|
|
}
|
|
|
|
void resume_music_asset(AssetID id) {
|
|
if (id < 0 || !is_music_asset_playing(id)) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Music *music = get_music(GameEngine.asset_manager, id);
|
|
if (!music) {
|
|
return;
|
|
}
|
|
|
|
Mix_ResumeMusic();
|
|
}
|
|
|
|
void stop_music_asset(AssetID id) {
|
|
if (id < 0 || !is_music_asset_playing(id)) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Music *music = get_music(GameEngine.asset_manager, id);
|
|
if (!music) {
|
|
return;
|
|
}
|
|
|
|
Mix_HaltMusic();
|
|
}
|
|
|
|
void fade_out_music_asset(AssetID id, u32 fade_length_in_ms) {
|
|
if (id < 0 || !is_music_asset_playing(id)) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Music *music = get_music(GameEngine.asset_manager, id);
|
|
if (!music) {
|
|
return;
|
|
}
|
|
|
|
Mix_FadeOutMusic(fade_length_in_ms);
|
|
}
|
|
|
|
i8 get_music_asset_volume(AssetID id) {
|
|
if (id < 0) {
|
|
return -1;
|
|
}
|
|
|
|
Mix_Music *music = get_music(GameEngine.asset_manager, id);
|
|
if (!music) {
|
|
return -1;
|
|
}
|
|
|
|
return Mix_GetMusicVolume(music);
|
|
}
|
|
|
|
void set_music_asset_volume(AssetID id, i8 volume) {
|
|
if (id < 0 || !is_music_asset_playing(id)) {
|
|
return;
|
|
}
|
|
|
|
const Mix_Music *music = get_music(GameEngine.asset_manager, id);
|
|
if (!music) {
|
|
return;
|
|
}
|
|
|
|
Mix_VolumeMusic(volume);
|
|
}
|
|
|
|
bool is_music_object_playing(const Mix_Music *music) {
|
|
if (!Mix_PlayingMusic()) {
|
|
return false;
|
|
}
|
|
|
|
// The Mix_GetMusic* functions used here all return the result for the
|
|
// currently playing music when passed a NULL pointer. We use that to compare
|
|
// the data of the Mix_Music object passed to the function to the data of the
|
|
// currently playing music.
|
|
//
|
|
// TODO (Abdelrahman): This is a bit of a hack. Figure out if there is
|
|
// a better alternative to check if a Mix_Music object is currently playing.
|
|
return Mix_GetMusicType(music) == Mix_GetMusicType(NULL) &&
|
|
Mix_GetMusicTitle(music) == Mix_GetMusicTitle(NULL) &&
|
|
Mix_GetMusicArtistTag(music) == Mix_GetMusicArtistTag(NULL) &&
|
|
Mix_GetMusicAlbumTag(music) == Mix_GetMusicAlbumTag(NULL);
|
|
}
|
|
|
|
bool is_music_asset_playing(AssetID id) {
|
|
if (id < 0) {
|
|
return false;
|
|
}
|
|
|
|
const Mix_Music *music = get_music(GameEngine.asset_manager, id);
|
|
if (!music) {
|
|
return false;
|
|
}
|
|
|
|
return is_music_object_playing(music);
|
|
}
|
|
|
|
void pause_playing_music() { Mix_PauseMusic(); }
|
|
|
|
void resume_playing_music() { Mix_ResumeMusic(); }
|
|
|
|
void stop_playing_music() { Mix_HaltMusic(); }
|
|
|
|
void fade_out_playing_music(u32 fade_length_in_ms) {
|
|
Mix_FadeOutMusic(fade_length_in_ms);
|
|
}
|
|
|
|
void set_playing_music_volume(i8 volume) { Mix_VolumeMusic(volume); }
|
|
|
|
bool is_music_playing() { return Mix_PlayingMusic(); }
|
|
|
|
void handle_events() {
|
|
if (!(GameEngine.running)) {
|
|
return;
|
|
}
|
|
|
|
while (SDL_PollEvent(&(GameEngine.event))) {
|
|
switch (GameEngine.event.type) {
|
|
case SDL_QUIT:
|
|
GameEngine.running = false;
|
|
break;
|
|
}
|
|
|
|
if (!GameEngine.object_store) {
|
|
break;
|
|
}
|
|
|
|
handle_all_objects_events(GameEngine.object_store, &(GameEngine.event));
|
|
}
|
|
}
|
|
|
|
void update() {
|
|
if (!(GameEngine.running) || !(GameEngine.object_store)) {
|
|
return;
|
|
}
|
|
|
|
u64 current_time = SDL_GetTicks64();
|
|
|
|
#ifndef DEBUG_ENGINE
|
|
// Get the delta time in seconds since the SDL_GetTicks64() returns the
|
|
// time in milliseconds
|
|
GameEngine.delta = (current_time - GameEngine.last_time) / 1000.0f;
|
|
#endif /* !DEBUG_ENGINE */
|
|
|
|
update_all_objects(GameEngine.object_store, GameEngine.delta);
|
|
|
|
GameEngine.last_time = current_time;
|
|
}
|
|
|
|
void render() {
|
|
if (!(GameEngine.running)) {
|
|
return;
|
|
}
|
|
|
|
clear_window(&(GameEngine.window));
|
|
|
|
if (GameEngine.object_store) {
|
|
render_all_objects(GameEngine.object_store);
|
|
}
|
|
|
|
swap_render_buffers(&(GameEngine.window));
|
|
}
|