HandmadeHero/project/engine/engine.cpp

1022 lines
29 KiB
C++
Raw Normal View History

2023-09-23 18:03:33 -07:00
//#include "win32.h"
2023-09-26 14:26:35 -07:00
#include "engine.hpp"
#include "engine_to_platform_api.hpp"
2023-09-26 14:26:35 -07:00
#include "handmade.hpp"
2023-09-15 18:35:27 -07:00
NS_ENGINE_BEGIN
#define pressed( btn ) (btn.ended_down && btn.half_transitions > 0)
2023-09-28 10:44:43 -07:00
// Used to determine if analog input is at move threshold
constexpr f32 analog__move_threshold = 0.5f;
struct EngineActions
{
b32 move_up;
b32 move_down;
b32 move_left;
b32 move_right;
2023-09-28 10:44:43 -07:00
b32 loop_mode_engine;
b32 loop_mode_game;
2023-09-28 10:44:43 -07:00
b32 raise_volume;
b32 lower_volume;
b32 raise_tone_hz;
b32 lower_tone_hz;
2023-09-28 10:44:43 -07:00
b32 toggle_wave_tone;
2023-09-28 10:44:43 -07:00
2023-09-28 12:41:30 -07:00
#if Build_Development
b32 pause_renderer;
2023-09-30 12:40:27 -07:00
2023-10-01 17:17:14 -07:00
b32 load_auto_snapshot;
2023-09-30 12:40:27 -07:00
b32 set_snapshot_slot_1;
b32 set_snapshot_slot_2;
b32 set_snapshot_slot_3;
b32 set_snapshot_slot_4;
2023-10-01 17:17:14 -07:00
b32 force_null_access_violation;
2023-09-28 12:41:30 -07:00
#endif
2023-09-28 10:44:43 -07:00
};
2023-09-18 17:16:40 -07:00
struct EngineState
{
2023-10-01 17:17:14 -07:00
f32 auto_snapshot_interval;
f32 auto_snapshot_timer;
s32 wave_tone_hz;
s32 tone_volume;
s32 x_offset;
s32 y_offset;
2023-09-28 10:44:43 -07:00
b32 renderer_paused;
2023-09-28 10:44:43 -07:00
f32 sample_wave_sine_time;
b32 sample_wave_switch;
2023-09-28 10:44:43 -07:00
hh::Memory game_memory;
2023-09-18 17:16:40 -07:00
};
2023-09-16 15:41:07 -07:00
2023-09-30 17:26:00 -07:00
#include "test_samples.cpp"
2023-09-15 18:35:27 -07:00
2023-09-28 10:44:43 -07:00
internal void
render_player( OffscreenBuffer* buffer, s32 pos_x, s32 pos_y )
2023-09-17 18:20:11 -07:00
{
u8* end_of_buffer = rcast(u8*, buffer->memory)
- buffer->bytes_per_pixel * buffer->width
+ buffer->pitch * buffer->height;
2023-09-28 10:44:43 -07:00
s32 top = pos_y;
s32 bottom = pos_y + 10;
u32 color = 0xFFFFFFFF;
for ( s32 coord_x = pos_x; coord_x < (pos_x+ 10); ++ coord_x )
{
u8*
pixel_byte = rcast(u8*, buffer->memory);
pixel_byte += coord_x * buffer->bytes_per_pixel;
pixel_byte += top * buffer->pitch;
2023-09-17 18:20:11 -07:00
2023-09-28 10:44:43 -07:00
for ( s32 coord_y = top; coord_y < bottom; ++ coord_y )
{
if ( pixel_byte < buffer->memory || pixel_byte >= end_of_buffer )
2023-09-28 10:44:43 -07:00
continue;
s32* pixel = rcast(s32*, pixel_byte);
*pixel = color;
pixel_byte += buffer->pitch;
2023-09-28 10:44:43 -07:00
}
}
2023-09-18 17:16:40 -07:00
}
2023-09-30 12:40:27 -07:00
#if Build_Development
using SnapshotFn = void ( Memory* memory, platform::ModuleAPI* platform_api );
internal
2023-09-30 12:40:27 -07:00
void load_engine_snapshot( Memory* memory, platform::ModuleAPI* platform_api )
{
2023-09-30 12:40:27 -07:00
platform_api->memory_copy( memory->persistent, memory->total_size()
, memory->snapshots[ memory->active_snapshot_slot ].memory );
}
internal
2023-09-30 12:40:27 -07:00
void load_game_snapshot( Memory* memory, platform::ModuleAPI* platform_api )
{
2023-09-30 12:40:27 -07:00
s32 slot = memory->active_snapshot_slot;
EngineState* state = rcast( EngineState*, memory->persistent );
void* persistent_slot = memory->snapshots[ slot ].memory;
void* transient_slot = rcast( Byte*, memory->snapshots[ slot ].memory ) + state->game_memory.persistent_size;
platform_api->memory_copy( state->game_memory.persistent, state->game_memory.persistent_size, persistent_slot );
platform_api->memory_copy( state->game_memory.transient, state->game_memory.transient_size, transient_slot );
}
internal
2023-09-30 12:40:27 -07:00
void take_engine_snapshot( Memory* memory, platform::ModuleAPI* platform_api )
{
2023-09-30 12:40:27 -07:00
platform_api->memory_copy( memory->snapshots[ memory->active_snapshot_slot ].memory, memory->total_size(), memory->persistent );
2023-10-01 17:17:14 -07:00
memory->snapshots[ memory->active_snapshot_slot ].age = platform_api->get_wall_clock();
}
2023-09-28 10:44:43 -07:00
internal
2023-09-30 12:40:27 -07:00
void take_game_snapshot( Memory* memory, platform::ModuleAPI* platform_api )
{
2023-09-30 12:40:27 -07:00
s32 slot = memory->active_snapshot_slot;
EngineState* state = rcast( EngineState*, memory->persistent );
void* persistent_slot = memory->snapshots[ slot ].memory;
void* transient_slot = rcast( Byte*, memory->snapshots[ slot ].memory ) + state->game_memory.persistent_size;
platform_api->memory_copy( persistent_slot, state->game_memory.persistent_size, state->game_memory.persistent );
platform_api->memory_copy( transient_slot, state->game_memory.transient_size, state->game_memory.transient );
2023-10-01 17:17:14 -07:00
memory->snapshots[ memory->active_snapshot_slot ].age = platform_api->get_wall_clock();
}
internal
void begin_recording_input( Memory* memory, InputState* input, platform::ModuleAPI* platform_api )
2023-09-18 17:16:40 -07:00
{
2023-09-30 12:40:27 -07:00
Str file_name = str_ascii("test_input_");
2023-09-29 12:58:18 -07:00
StrPath file_path = {};
file_path.concat( platform_api->path_scratch, file_name );
2023-09-30 12:40:27 -07:00
snprintf( file_path.ptr, file_path.len, "%s%d.hm_replay", file_name.ptr, memory->active_snapshot_slot );
2023-09-29 12:58:18 -07:00
2023-09-30 12:40:27 -07:00
memory->active_input_replay_file.path = file_path;
2023-09-30 17:26:00 -07:00
platform_api->file_delete( memory->active_input_replay_file.path );
2023-09-30 12:40:27 -07:00
memory->replay_mode = ReplayMode_Record;
2023-09-28 10:44:43 -07:00
}
2023-09-17 18:20:11 -07:00
2023-09-28 10:44:43 -07:00
internal
void end_recording_input( Memory* memory, InputState* input, platform::ModuleAPI* platform_api )
2023-09-28 10:44:43 -07:00
{
2023-09-30 12:40:27 -07:00
memory->replay_mode = ReplayMode_Off;
platform_api->file_close( & memory->active_input_replay_file );
2023-09-28 10:44:43 -07:00
}
2023-09-17 18:20:11 -07:00
2023-09-28 10:44:43 -07:00
internal
void begin_playback_input( Memory* memory, InputState* input, platform::ModuleAPI* platform_api )
2023-09-28 10:44:43 -07:00
{
2023-09-30 12:40:27 -07:00
Str file_name = str_ascii("test_input_");
2023-09-29 12:58:18 -07:00
StrPath file_path = {};
file_path.concat( platform_api->path_scratch, file_name );
2023-09-30 12:40:27 -07:00
snprintf( file_path.ptr, file_path.len, "%s%d.hm_replay", file_name.ptr, memory->active_snapshot_slot );
// TODO(Ed - From Casey): Recording system still seems to take too long
// on record start - find out what Windows is doing and if
// we can speed up / defer some of that processing.
2023-09-29 12:58:18 -07:00
if ( platform_api->file_check_exists( file_path ) )
2023-09-28 10:44:43 -07:00
{
2023-09-30 12:40:27 -07:00
memory->active_input_replay_file.path = file_path;
memory->replay_mode = ReplayMode_Playback;
2023-09-28 10:44:43 -07:00
}
2023-09-30 12:40:27 -07:00
else
2023-09-28 10:44:43 -07:00
{
2023-09-30 12:40:27 -07:00
// TODO(Ed) : Logging
2023-09-28 10:44:43 -07:00
}
}
internal
void end_playback_input( Memory* memory, InputState* input, platform::ModuleAPI* platform_api )
2023-09-28 10:44:43 -07:00
{
2023-09-30 12:40:27 -07:00
memory->replay_mode = ReplayMode_Off;
platform_api->file_close( & memory->active_input_replay_file );
2023-09-28 10:44:43 -07:00
}
2023-09-21 23:16:40 -07:00
2023-09-28 10:44:43 -07:00
InputStateSnapshot input_state_snapshot( InputState* input )
{
InputStateSnapshot snapshot = {};
for ( s32 idx = 0; idx < array_count( snapshot.controllers ); ++ idx )
2023-09-26 14:26:35 -07:00
{
ControllerState* controller = & input->controllers[idx];
2023-09-28 10:44:43 -07:00
if ( controller == nullptr )
continue;
if ( controller->ds_pad )
snapshot.controllers[idx].ds_pad = *controller->ds_pad;
2023-09-28 10:44:43 -07:00
if ( controller->xpad )
snapshot.controllers[idx].xpad = *controller->xpad;
2023-09-18 17:16:40 -07:00
if ( controller->keyboard )
2023-09-20 11:43:55 -07:00
{
snapshot.controllers[idx].keyboard = *controller->keyboard;
2023-09-20 11:43:55 -07:00
}
2023-09-28 10:44:43 -07:00
if ( controller->mouse )
snapshot.controllers[idx].mouse = *controller->mouse;
2023-09-26 14:26:35 -07:00
}
2023-09-28 10:44:43 -07:00
return snapshot;
2023-09-26 14:26:35 -07:00
}
2023-09-28 10:44:43 -07:00
internal
void record_input( Memory* memory, InputState* input, platform::ModuleAPI* platform_api )
2023-09-26 14:26:35 -07:00
{
2023-09-28 10:44:43 -07:00
InputStateSnapshot snapshot = input_state_snapshot( input );
2023-09-30 12:40:27 -07:00
if ( platform_api->file_write_stream( & memory->active_input_replay_file, sizeof(snapshot), &snapshot ) == 0 )
2023-09-28 10:44:43 -07:00
{
// TODO(Ed) : Logging
}
2023-09-26 14:26:35 -07:00
}
2023-09-28 10:44:43 -07:00
internal
void play_input( SnapshotFn* load_snapshot, Memory* memory, InputState* input, platform::ModuleAPI* platform_api )
2023-09-26 14:26:35 -07:00
{
2023-09-28 10:44:43 -07:00
InputStateSnapshot new_input;
2023-09-30 12:40:27 -07:00
if ( platform_api->file_read_stream( & memory->active_input_replay_file, sizeof(InputStateSnapshot), & new_input ) == 0 )
2023-09-28 10:44:43 -07:00
{
2023-09-30 12:40:27 -07:00
load_snapshot( memory, platform_api );
platform_api->file_rewind( & memory->active_input_replay_file );
2023-09-28 10:44:43 -07:00
return;
}
for ( s32 idx = 0; idx < array_count( new_input.controllers ); ++ idx )
2023-09-28 10:44:43 -07:00
{
ControllerState* controller = & input->controllers[idx];
2023-09-28 10:44:43 -07:00
if ( controller == nullptr )
continue;
if ( controller->ds_pad )
*controller->ds_pad = new_input.controllers[idx].ds_pad;
2023-09-28 10:44:43 -07:00
if ( controller->xpad )
*controller->xpad = new_input.controllers[idx].xpad;
2023-09-28 10:44:43 -07:00
if ( controller->keyboard )
2023-09-28 10:44:43 -07:00
{
*controller->keyboard = new_input.controllers[idx].keyboard;
2023-09-28 10:44:43 -07:00
}
2023-09-17 18:20:11 -07:00
if ( controller->mouse )
*controller->mouse = new_input.controllers[idx].mouse;
}
}
void process_loop_mode( SnapshotFn* take_snapshot, SnapshotFn* load_snapshot
, Memory* memory, EngineState* state, InputState* input, platform::ModuleAPI* platform_api )
{
2023-09-30 12:40:27 -07:00
if ( memory->replay_mode == ReplayMode_Off )
{
2023-09-30 12:40:27 -07:00
take_snapshot( memory, platform_api );
begin_recording_input( memory, input, platform_api );
}
2023-09-30 12:40:27 -07:00
else if ( memory->replay_mode == ReplayMode_Playback )
{
end_playback_input( memory, input, platform_api );
2023-09-30 12:40:27 -07:00
load_snapshot( memory, platform_api );
}
2023-09-30 12:40:27 -07:00
else if ( memory->replay_mode == ReplayMode_Record )
{
end_recording_input( memory, input, platform_api );
2023-09-30 12:40:27 -07:00
load_snapshot( memory, platform_api );
begin_playback_input( memory, input, platform_api );
2023-09-28 10:44:43 -07:00
}
}
2023-09-30 12:40:27 -07:00
// Build_Development
#endif
2023-09-28 10:44:43 -07:00
internal
void input_poll_engine_actions( InputState* input, EngineActions* actions )
{
ControllerState* controller = & input->controllers[0];
KeyboardState* keyboard = controller->keyboard;
2023-09-22 12:13:18 -07:00
// actions->move_right |= keyboard->D.EndedDown;
// actions->move_left |= keyboard->A.EndedDown;
// actions->move_up |= keyboard->W.EndedDown;
// actions->move_down |= keyboard->S.EndedDown;
2023-09-22 12:13:18 -07:00
actions->raise_volume |= keyboard->up.ended_down;
actions->lower_volume |= keyboard->down.ended_down;
2023-09-22 12:13:18 -07:00
actions->raise_tone_hz |= keyboard->right.ended_down;
actions->lower_tone_hz |= keyboard->left.ended_down;
2023-09-22 12:13:18 -07:00
2023-09-28 12:41:30 -07:00
#if Build_Development
actions->pause_renderer |= pressed( keyboard->pause );
2023-09-30 12:40:27 -07:00
actions->set_snapshot_slot_1 |= pressed( keyboard->_1 ) && keyboard->right_alt.ended_down;
actions->set_snapshot_slot_2 |= pressed( keyboard->_2 ) && keyboard->right_alt.ended_down;
actions->set_snapshot_slot_3 |= pressed( keyboard->_3 ) && keyboard->right_alt.ended_down;
actions->set_snapshot_slot_4 |= pressed( keyboard->_4 ) && keyboard->right_alt.ended_down;
2023-09-28 12:41:30 -07:00
#endif
2023-09-17 18:20:11 -07:00
2023-09-28 10:44:43 -07:00
actions->toggle_wave_tone |= pressed( keyboard->Q );
2023-09-24 19:37:05 -07:00
2023-10-01 17:17:14 -07:00
actions->loop_mode_game |= pressed( keyboard->L ) && ! keyboard->right_shift.ended_down && ! keyboard->right_alt.ended_down;
actions->loop_mode_engine |= pressed( keyboard->L ) && keyboard->right_shift.ended_down;
MousesState* mouse = controller->mouse;
actions->move_right = (mouse->horizontal_wheel.end > 0.f) * 20;
actions->move_left = (mouse->horizontal_wheel.end < 0.f) * 20;
actions->move_up = (mouse->vertical_wheel.end > 0.f) * 10;
actions->move_down = (mouse->vertical_wheel.end < 0.f) * 10;
2023-09-30 12:40:27 -07:00
2023-10-01 17:17:14 -07:00
actions->load_auto_snapshot |= pressed( keyboard->L ) && keyboard->right_alt.ended_down;
2023-09-28 10:44:43 -07:00
}
2023-09-22 12:13:18 -07:00
2023-09-28 10:44:43 -07:00
internal
void input_poll_player_actions( InputState* input, hh::PlayerActions* actions )
2023-09-28 10:44:43 -07:00
{
ControllerState* controller = & input->controllers[0];
2023-09-24 19:37:05 -07:00
if ( controller->ds_pad )
2023-09-17 18:20:11 -07:00
{
DualsensePadState* pad = controller->ds_pad;
2023-09-22 12:13:18 -07:00
actions->jump |= pressed( pad->cross );
2023-09-22 12:13:18 -07:00
actions->player_x_move_analog += pad->stick.left.X.end;
actions->player_y_move_analog += pad->stick.left.Y.end;
2023-09-17 18:20:11 -07:00
}
if ( controller->xpad )
2023-09-17 18:20:11 -07:00
{
XInputPadState* pad = controller->xpad;
2023-09-22 12:13:18 -07:00
2023-09-28 10:44:43 -07:00
actions->jump |= pressed( pad->A );
2023-09-22 12:13:18 -07:00
actions->player_x_move_analog += pad->stick.left.X.end;
actions->player_y_move_analog += pad->stick.left.Y.end;
2023-09-17 18:20:11 -07:00
}
2023-09-28 10:44:43 -07:00
if ( controller->keyboard )
2023-09-20 21:26:23 -07:00
{
KeyboardState* keyboard = controller->keyboard;
actions->jump |= pressed( keyboard->space );
2023-09-28 10:44:43 -07:00
actions->player_x_move_digital += keyboard->D.ended_down - keyboard->A.ended_down;
actions->player_y_move_digital += keyboard->W.ended_down - keyboard->S.ended_down;
}
if ( controller->mouse )
{
MousesState* mouse = controller->mouse;
2023-09-28 10:44:43 -07:00
}
}
2023-09-30 17:26:00 -07:00
internal void
output_sound( EngineState* state, AudioBuffer* sound_buffer, GetSoundSampleValueFn* get_sample_value )
{
s16* sample_out = sound_buffer->samples;
for ( s32 sample_index = 0; sample_index < sound_buffer->num_samples; ++ sample_index )
{
s16 sample_value = get_sample_value( state, sound_buffer );
sound_buffer->running_sample_index++;
// char ms_timing_debug[256] {};
// wsprintfA( ms_timing_debug, "sample_value: %d\n", sample_value );
// d ms_timing_debug );
*sample_out = sample_value;
++ sample_out;
*sample_out = sample_value;
++ sample_out;
}
}
2023-10-06 10:06:40 -07:00
inline
s32 floor_f32_to_s32( f32 value )
{
// TODO : Casey wants to use an intrinsic
return scast(s32, floorf( value ));
}
2023-10-04 10:55:15 -07:00
inline
2023-09-30 17:26:00 -07:00
s32 round_f32_to_s32( f32 value )
{
// TODO(Ed) : Casey wants to use an intrinsic
return scast(s32, value + 0.5f);
}
2023-10-04 10:55:15 -07:00
inline
s32 truncate_f32_to_s32( f32 value )
{
return scast(s32, value);
}
2023-09-30 17:26:00 -07:00
internal
void draw_rectangle( OffscreenBuffer* buffer
, f32 min_x, f32 min_y
, f32 max_x, f32 max_y
, f32 red, f32 green, f32 blue )
{
s32 min_x_32 = round_f32_to_s32( min_x );
s32 min_y_32 = round_f32_to_s32( min_y );
s32 max_x_32 = round_f32_to_s32( max_x );
s32 max_y_32 = round_f32_to_s32( max_y );
s32 buffer_width = buffer->width;
s32 buffer_height = buffer->height;
if ( min_x_32 < 0 )
min_x_32 = 0;
if ( min_y_32 < 0 )
min_y_32 = 0;
if ( max_x_32 > buffer_width )
max_x_32 = buffer_width;
if ( max_y_32 > buffer_height )
max_y_32 = buffer_height;
2023-10-01 17:17:14 -07:00
s32 red_32 = round_f32_to_s32( 255.f * red );
s32 green_32 = round_f32_to_s32( 255.f * green );
s32 blue_32 = round_f32_to_s32( 255.f * blue );
s32 color =
(scast(s32, red_32) << 16)
| (scast(s32, green_32) << 8)
| (scast(s32, blue_32) << 0);
2023-09-30 17:26:00 -07:00
// Start with the pixel on the top left corner of the rectangle
u8* row = rcast(u8*, buffer->memory )
+ min_x_32 * buffer->bytes_per_pixel
+ min_y_32 * buffer->pitch;
for ( s32 y = min_y_32; y < max_y_32; ++ y )
{
s32* pixel_32 = rcast(s32*, row);
for ( s32 x = min_x_32; x < max_x_32; ++ x )
{
*pixel_32 = color;
pixel_32++;
}
row += buffer->pitch;
}
}
2023-09-28 10:44:43 -07:00
Engine_API
void on_module_reload( Memory* memory, platform::ModuleAPI* platfom_api )
{
}
Engine_API
void startup( Memory* memory, platform::ModuleAPI* platform_api )
{
2023-09-30 12:40:27 -07:00
#if Build_Development
memory->active_snapshot_slot = 1;
memory->replay_mode = ReplayMode_Off;
memory->active_input_replay_file = {};
memory->engine_loop_active = false;
memory->game_loop_active = false;
#endif
2023-10-01 17:17:14 -07:00
#if 0
for ( s32 slot = 0; slot < memory->Num_Snapshot_Slots; ++ slot )
{
// TODO(Ed) : Specify default file paths for saving slots ?
}
2023-10-01 17:17:14 -07:00
#endif
2023-09-22 12:13:18 -07:00
EngineState* state = rcast( EngineState*, memory->persistent );
assert( sizeof(EngineState) <= memory->persistent_size );
2023-09-22 12:13:18 -07:00
2023-10-06 10:06:40 -07:00
state->auto_snapshot_interval = 60.f;
2023-10-01 17:17:14 -07:00
state->tone_volume = 1000;
2023-09-22 12:13:18 -07:00
state->x_offset = 0;
state->y_offset = 0;
2023-09-24 19:37:05 -07:00
state->sample_wave_switch = false;
state->wave_tone_hz = 60;
state->sample_wave_sine_time = 0.f;
2023-09-22 12:13:18 -07:00
state->renderer_paused = false;
state->game_memory.persistent_size = memory->persistent_size / 2;
state->game_memory.persistent = rcast(Byte*, memory->persistent) + state->game_memory.persistent_size;
state->game_memory.transient_size = memory->transient_size / 2;
state->game_memory.transient = rcast(Byte*, memory->transient) + state->game_memory.transient_size;
2023-09-20 21:26:23 -07:00
2023-10-06 10:06:40 -07:00
hh::GameState* game_state = rcast( hh::GameState*, state->game_memory.persistent );
assert( sizeof(hh::GameState) <= state->game_memory.persistent_size );
game_state->tile_map_x = 0;
game_state->tile_map_y = 0;
2023-09-28 10:44:43 -07:00
2023-10-06 10:06:40 -07:00
hh::PlayerState* player = & game_state->player_state;
player->pos_x = 920;
player->pos_y = 466;
player->mid_jump = false;
player->jump_time = 0.f;
2023-09-28 10:44:43 -07:00
}
Engine_API
void shutdown( Memory* memory, platform::ModuleAPI* platform_api )
{
}
2023-10-04 10:55:15 -07:00
inline
2023-10-06 10:06:40 -07:00
CanonPosition get_cannonical_position( World* world, RawPosition raw_pos )
2023-10-04 10:55:15 -07:00
{
2023-10-06 10:06:40 -07:00
s32 tile_map_x = raw_pos.tile_map_x;
s32 tile_map_y = raw_pos.tile_map_y;
f32 pos_x = ( raw_pos.x - world->tile_upper_left_x );
f32 pos_y = ( raw_pos.y - world->tile_upper_left_y );
s32 tile_x = floor_f32_to_s32( pos_x / world->tile_width );
s32 tile_y = floor_f32_to_s32( pos_y / world->tile_height );
f32 tile_rel_x = pos_x - scast(f32, tile_x) * world->tile_width;
f32 tile_rel_y = pos_y - scast(f32, tile_y) * world->tile_height;
assert( tile_rel_x >= 0.f );
assert( tile_rel_y >= 0.f );
assert( tile_rel_x < world->tile_width );
assert( tile_rel_y < world->tile_height );
/*
The puprpose of this is to be able to detect if the point is outside of the tilemap,
and if so, roll the point over to an adjacent tilemap.
For example : If the point is at x = -1, then it is outside of the tilemap, and
should be rolled over to the tilemap to the left of the current tilemap.
*/
if ( tile_x < 0 )
{
tile_x += world->num_tiles_x;
-- tile_map_x;
}
if ( tile_y < 0 )
{
tile_y += world->num_tiles_y;
-- tile_map_y;
}
if ( tile_x >= world->num_tiles_x )
{
tile_x -= world->num_tiles_x;
++ tile_map_x;
}
if ( tile_y >= world->num_tiles_y )
{
tile_y -= world->num_tiles_y;
++ tile_map_y;
}
return { tile_rel_x, tile_rel_y, tile_map_x, tile_map_y, tile_x, tile_y };
}
inline
u32 tilemap_tile_value( TileMap* tile_map, World* world, s32 x, s32 y )
{
assert( tile_map != nullptr );
assert( world != nullptr );
assert( x >= 0 && x < scast(s32, world->num_tiles_x) );
assert( y >= 0 && y < scast(s32, world->num_tiles_y) );
return tile_map->tiles[ (y * world->num_tiles_x) + x ];
2023-10-04 10:55:15 -07:00
}
inline
2023-10-06 10:06:40 -07:00
b32 tilemap_is_point_empty( TileMap* tile_map, World* world, s32 tile_x, s32 tile_y )
2023-10-04 10:55:15 -07:00
{
2023-10-06 10:06:40 -07:00
assert( tile_map != nullptr );
assert( world != nullptr );
// Assume space is occupied if there is bad data
if ( tile_map == nullptr )
return false;
2023-10-04 10:55:15 -07:00
b32 is_empty = false;
2023-10-06 10:06:40 -07:00
if ( tile_x >= 0 && tile_x < world->num_tiles_x
&& tile_y >= 0 && tile_y < world->num_tiles_y )
2023-10-04 10:55:15 -07:00
{
2023-10-06 10:06:40 -07:00
u32 tile_value = tilemap_tile_value( tile_map, world, tile_x, tile_y );
2023-10-04 10:55:15 -07:00
is_empty = tile_value == 0;
}
return is_empty;
}
inline
2023-10-06 10:06:40 -07:00
TileMap* world_get_tilemap( World* world, s32 tile_map_x, s32 tile_map_y )
2023-10-04 10:55:15 -07:00
{
2023-10-06 10:06:40 -07:00
assert( tile_map_x >= 0 && tile_map_x < world->tilemaps_num_x );
assert( tile_map_y >= 0 && tile_map_y < world->tilemaps_num_y );
return & world->tile_maps[ (tile_map_y * world->tilemaps_num_x) + tile_map_x ];
2023-10-04 10:55:15 -07:00
}
internal
2023-10-06 10:06:40 -07:00
b32 world_is_point_empty( World* world, RawPosition raw_pos )
2023-10-04 10:55:15 -07:00
{
2023-10-06 10:06:40 -07:00
assert( world != nullptr );
b32 is_empty = false;
CanonPosition position = get_cannonical_position( world, raw_pos );
TileMap* tile_map = world_get_tilemap( world, position.tile_map_x, position.tile_map_y );
is_empty = tilemap_is_point_empty( tile_map, world, position.tile_x, position.tile_y );
return is_empty;
2023-10-04 10:55:15 -07:00
}
2023-09-28 10:44:43 -07:00
Engine_API
2023-10-04 10:55:15 -07:00
void update_and_render( f32 delta_time, InputState* input, OffscreenBuffer* back_buffer
, Memory* memory, platform::ModuleAPI* platform_api, ThreadContext* thread )
2023-09-28 10:44:43 -07:00
{
EngineState* state = rcast( EngineState*, memory->persistent );
assert( sizeof(EngineState) <= memory->persistent_size );
2023-09-28 10:44:43 -07:00
2023-10-01 17:17:14 -07:00
// Engine auto_snapshot
{
state->auto_snapshot_timer += delta_time;
if ( state->auto_snapshot_timer >= state->auto_snapshot_interval )
{
state->auto_snapshot_timer = 0.f;
s32 current_slot = memory->active_snapshot_slot;
memory->active_snapshot_slot = 0;
take_engine_snapshot( memory, platform_api );
memory->active_snapshot_slot = current_slot;
state->auto_snapshot_timer = 0.f;
}
}
ControllerState* controller = & input->controllers[0];
2023-09-28 10:44:43 -07:00
EngineActions engine_actions {};
hh::PlayerActions player_actions {};
2023-09-28 10:44:43 -07:00
input_poll_engine_actions( input, & engine_actions );
2023-10-01 17:17:14 -07:00
if ( engine_actions.load_auto_snapshot )
{
s32 current_slot = memory->active_snapshot_slot;
memory->active_snapshot_slot = 0;
load_engine_snapshot( memory, platform_api );
memory->active_snapshot_slot = current_slot;
}
2023-09-30 12:40:27 -07:00
#if Build_Development
// Ease of use: Allow user to press L key without shift if engine loop recording is active.
engine_actions.loop_mode_engine |= engine_actions.loop_mode_game && memory->engine_loop_active;
if ( engine_actions.loop_mode_engine && ! memory->game_loop_active )
{
process_loop_mode( & take_engine_snapshot, & load_engine_snapshot, memory, state, input, platform_api );
2023-09-30 12:40:27 -07:00
memory->engine_loop_active = memory->replay_mode > ReplayMode_Off;
}
// Input recording and playback for engine state
if ( memory->engine_loop_active )
{
2023-09-30 12:40:27 -07:00
if ( memory->replay_mode == ReplayMode_Record )
{
record_input( memory, input, platform_api );
}
2023-09-30 12:40:27 -07:00
if ( memory->replay_mode == ReplayMode_Playback )
{
play_input( & load_engine_snapshot, memory, input, platform_api );
}
}
2023-09-30 12:40:27 -07:00
#endif
// Process Engine Actions
2023-09-21 23:16:40 -07:00
{
state->x_offset += 3 * engine_actions.move_right;
state->x_offset -= 3 * engine_actions.move_left;
state->y_offset += 3 * engine_actions.move_down;
state->y_offset -= 3 * engine_actions.move_up;
2023-09-28 10:44:43 -07:00
if ( engine_actions.raise_volume )
{
state->tone_volume += 10;
2023-09-28 10:44:43 -07:00
}
if ( engine_actions.lower_volume )
{
state->tone_volume -= 10;
if ( state->tone_volume <= 0 )
state->tone_volume = 0;
2023-09-28 10:44:43 -07:00
}
if ( engine_actions.raise_tone_hz )
{
state->wave_tone_hz += 1;
2023-09-28 10:44:43 -07:00
}
if ( engine_actions.lower_tone_hz )
{
state->wave_tone_hz -= 1;
if ( state->wave_tone_hz <= 0 )
state->wave_tone_hz = 1;
2023-09-28 10:44:43 -07:00
}
if ( engine_actions.toggle_wave_tone )
{
state->sample_wave_switch ^= true;
2023-09-28 10:44:43 -07:00
}
if ( engine_actions.loop_mode_game && ! memory->engine_loop_active )
2023-09-28 10:44:43 -07:00
{
process_loop_mode( & take_game_snapshot, & load_game_snapshot, memory, state, input, platform_api );
2023-09-30 12:40:27 -07:00
memory->game_loop_active = memory->replay_mode > ReplayMode_Off;
2023-09-28 10:44:43 -07:00
}
2023-09-28 12:41:30 -07:00
#if Build_Development
2023-09-28 10:44:43 -07:00
if ( engine_actions.pause_renderer )
{
if ( state->renderer_paused )
2023-09-28 10:44:43 -07:00
{
platform_api->debug_set_pause_rendering(false);
state->renderer_paused = false;
2023-09-28 10:44:43 -07:00
}
else
{
platform_api->debug_set_pause_rendering(true);
state->renderer_paused = true;
2023-09-28 10:44:43 -07:00
}
}
2023-09-30 12:40:27 -07:00
if ( ! memory->game_loop_active )
{
2023-10-01 17:17:14 -07:00
if ( engine_actions.set_snapshot_slot_1 ) memory->active_snapshot_slot = 1;
if ( engine_actions.set_snapshot_slot_2 ) memory->active_snapshot_slot = 2;
2023-09-30 12:40:27 -07:00
}
2023-09-28 12:41:30 -07:00
#endif
2023-09-21 23:16:40 -07:00
}
2023-09-20 21:26:23 -07:00
2023-09-30 12:40:27 -07:00
#if Build_Development
if ( ! memory->engine_loop_active )
2023-09-21 23:16:40 -07:00
{
// Input recording and playback for game state
2023-09-30 12:40:27 -07:00
if ( memory->replay_mode == ReplayMode_Record )
{
record_input( memory, input, platform_api );
}
2023-09-30 12:40:27 -07:00
if ( memory->replay_mode == ReplayMode_Playback )
{
play_input( & load_game_snapshot, memory, input, platform_api );
}
2023-09-21 23:16:40 -07:00
}
2023-09-30 12:40:27 -07:00
#endif
2023-09-20 21:26:23 -07:00
2023-10-06 10:06:40 -07:00
hh::GameState* game_state = rcast( hh::GameState*, state->game_memory.persistent );
hh::PlayerState* player = & game_state->player_state;
2023-10-04 10:55:15 -07:00
f32 x_offset_f = scast(f32, state->x_offset);
f32 y_offset_f = scast(f32, state->y_offset);
constexpr s32 tile_map_num_x = 16;
constexpr s32 tile_map_num_y = 9;
2023-10-06 10:06:40 -07:00
// tiles_XY
2023-10-04 10:55:15 -07:00
u32 tiles_00 [tile_map_num_y][tile_map_num_x] = {
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
2023-10-06 10:06:40 -07:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
2023-10-04 10:55:15 -07:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 },
};
2023-10-06 10:06:40 -07:00
u32 tiles_10 [tile_map_num_y][tile_map_num_x] = {
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
2023-10-04 10:55:15 -07:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
2023-10-06 10:06:40 -07:00
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
2023-10-04 10:55:15 -07:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
2023-10-06 10:06:40 -07:00
{ 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
2023-10-04 10:55:15 -07:00
};
2023-10-06 10:06:40 -07:00
u32 tiles_01 [tile_map_num_y][tile_map_num_x] = {
{ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 },
2023-10-04 10:55:15 -07:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
2023-10-06 10:06:40 -07:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
2023-10-04 10:55:15 -07:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
};
2023-10-06 10:06:40 -07:00
u32 tiles_11 [tile_map_num_y][tile_map_num_x] = {
{ 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
2023-10-04 10:55:15 -07:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
2023-10-06 10:06:40 -07:00
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
2023-10-04 10:55:15 -07:00
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
};
TileMap tile_maps[2][2] {};
2023-10-06 10:06:40 -07:00
tile_maps[0][0].tiles = rcast(u32*, tiles_00);
tile_maps[0][1].tiles = rcast(u32*, tiles_10);
tile_maps[1][0].tiles = rcast(u32*, tiles_01);
tile_maps[1][1].tiles = rcast(u32*, tiles_11);
2023-10-04 10:55:15 -07:00
2023-10-06 10:06:40 -07:00
World world;
world.num_tiles_x = tile_map_num_x;
world.num_tiles_y = tile_map_num_y;
2023-10-04 10:55:15 -07:00
2023-10-06 10:06:40 -07:00
f32 scale = 85;
2023-10-04 10:55:15 -07:00
2023-10-06 10:06:40 -07:00
world.tile_width = scale;
world.tile_height = scale * 1.05f;
world.tile_upper_left_x = -(world.tile_width * 0.5f);
world.tile_upper_left_y = -(world.tile_height * 0.5f);
world.tilemaps_num_x = 2;
world.tilemaps_num_y = 2;
2023-10-04 10:55:15 -07:00
2023-10-06 10:06:40 -07:00
world.tile_maps = rcast(TileMap*, tile_maps);
TileMap* current_tile_map = world_get_tilemap( & world, game_state->tile_map_x, game_state->tile_map_y );
assert( current_tile_map != nullptr );
player->width = world.tile_width * 0.75f;
player->height = world.tile_height;
f32 player_half_width = player->width / 2.f;
f32 player_quarter_height = player->height / 4.f;
2023-10-04 10:55:15 -07:00
2023-09-28 10:44:43 -07:00
input_poll_player_actions( input, & player_actions );
2023-09-24 19:37:05 -07:00
{
2023-10-01 17:17:14 -07:00
f32 move_speed = 200.f;
2023-10-04 10:55:15 -07:00
f32 new_player_pos_x = player->pos_x;
f32 new_player_pos_y = player->pos_y;
if ( player_actions.player_x_move_analog || player_actions.player_y_move_analog )
{
new_player_pos_x += scast(f32, player_actions.player_x_move_analog * delta_time * move_speed);
new_player_pos_y -= scast(f32, player_actions.player_y_move_analog * delta_time * move_speed);
}
else
{
new_player_pos_x += scast(f32, player_actions.player_x_move_digital) * delta_time * move_speed;
new_player_pos_y -= scast(f32, player_actions.player_y_move_digital) * delta_time * move_speed;
}
new_player_pos_y += sinf( player->jump_time * TAU ) * 200.f * delta_time;
2023-10-06 10:06:40 -07:00
b32 valid_new_pos = true;
{
RawPosition test_pos = { new_player_pos_x - player_half_width, new_player_pos_y - player_quarter_height, game_state->tile_map_x, game_state->tile_map_y };
valid_new_pos &= world_is_point_empty( & world, test_pos );
test_pos.x = new_player_pos_x + player_half_width;
valid_new_pos &= world_is_point_empty( & world, test_pos );
test_pos.x = new_player_pos_x - player_half_width;
test_pos.y = new_player_pos_y;
valid_new_pos &= world_is_point_empty( & world, test_pos );
test_pos.x = new_player_pos_x + player_half_width;
valid_new_pos &= world_is_point_empty( & world, test_pos );
}
2023-10-04 10:55:15 -07:00
if ( valid_new_pos )
{
2023-10-06 10:06:40 -07:00
RawPosition raw_pos = { new_player_pos_x, new_player_pos_y, game_state->tile_map_x, game_state->tile_map_y };
CanonPosition canon_pos = get_cannonical_position( & world, raw_pos);
game_state->tile_map_x = canon_pos.tile_map_x;
game_state->tile_map_y = canon_pos.tile_map_y;
// current_tile_map = world_get_tilemap( & world, game_state->tile_map_x, game_state->tile_map_y );
player->pos_x = world.tile_upper_left_x + world.tile_width * scast(f32, canon_pos.tile_x) + canon_pos.x;
player->pos_y = world.tile_upper_left_y + world.tile_height * scast(f32, canon_pos.tile_y) + canon_pos.y;
2023-10-04 10:55:15 -07:00
}
2023-10-01 17:17:14 -07:00
2023-10-04 10:55:15 -07:00
// player_tile_x
2023-09-28 10:44:43 -07:00
if ( player->jump_time > 0.f )
2023-09-24 19:37:05 -07:00
{
2023-10-01 17:17:14 -07:00
player->jump_time -= delta_time;
2023-09-24 19:37:05 -07:00
}
else
{
player->jump_time = 0.f;
2023-09-30 12:40:27 -07:00
player->mid_jump = false;
2023-09-28 10:44:43 -07:00
}
if ( ! player->mid_jump && player_actions.jump )
2023-09-28 10:44:43 -07:00
{
player->jump_time = 1.f;
2023-09-30 12:40:27 -07:00
player->mid_jump = true;
2023-09-24 19:37:05 -07:00
}
2023-09-20 21:26:23 -07:00
}
2023-09-28 10:44:43 -07:00
2023-09-30 17:26:00 -07:00
draw_rectangle( back_buffer
, 0.f, 0.f
, scast(f32, back_buffer->width), scast(f32, back_buffer->height)
2023-10-01 17:17:14 -07:00
, 1.f, 0.24f, 0.24f );
2023-10-04 10:55:15 -07:00
// Draw tilemap
2023-10-01 17:17:14 -07:00
for ( s32 row = 0; row < 9; ++ row )
{
for ( s32 col = 0; col < 16; ++ col )
{
2023-10-06 10:06:40 -07:00
u32 tileID = tilemap_tile_value( current_tile_map, & world, col, row );
2023-10-01 17:17:14 -07:00
f32 grey[3] = { 0.15f, 0.15f, 0.15f };
if ( tileID == 1 )
{
grey[0] = 0.22f;
grey[1] = 0.22f;
grey[2] = 0.22f;
}
2023-10-06 10:06:40 -07:00
f32 min_x = world.tile_upper_left_x + scast(f32, col) * world.tile_width;
f32 min_y = world.tile_upper_left_y + scast(f32, row) * world.tile_height;
f32 max_x = min_x + world.tile_width;
f32 max_y = min_y + world.tile_height;
2023-10-01 17:17:14 -07:00
draw_rectangle( back_buffer
, min_x, min_y
, max_x, max_y
, grey[0], grey[1], grey[2] );
}
}
// Player
f32 player_red = 0.3f;
f32 player_green = 0.3f;
f32 player_blue = 0.3f;
draw_rectangle( back_buffer
2023-10-06 10:06:40 -07:00
, player->pos_x - player_half_width, player->pos_y - player->height
, player->pos_x + player_half_width, player->pos_y
2023-10-01 17:17:14 -07:00
, player_red, player_green, player_blue );
// Auto-Snapshot percent bar
if (1)
{
f32 snapshot_percent_x = ((state->auto_snapshot_timer / state->auto_snapshot_interval)) * (f32)back_buffer->width / 4.f;
draw_rectangle( back_buffer
, 0.f, 0.f
, snapshot_percent_x, 10.f
2023-10-04 10:55:15 -07:00
, 0.f, 0.15f, 0.35f );
2023-10-01 17:17:14 -07:00
}
2023-09-30 12:40:27 -07:00
#if Build_Development
if ( memory->replay_mode == ReplayMode_Record )
2023-10-01 17:17:14 -07:00
{
draw_rectangle( back_buffer
, scast(f32, player->pos_x) + 50.f, scast(f32, player->pos_y) - 50.f
, scast(f32, player->pos_x) + 10.f, scast(f32, player->pos_y) + 40.f
, 1.f, 1.f, 1.f );
}
2023-09-30 12:40:27 -07:00
#endif
2023-10-01 17:17:14 -07:00
// Change above to use draw rectangle
2023-10-04 10:55:15 -07:00
if ( 0 )
{
draw_rectangle( back_buffer
, (f32)input->controllers[0].mouse->X.end, (f32)input->controllers[0].mouse->Y.end
, (f32)input->controllers[0].mouse->X.end + 10.f, (f32)input->controllers[0].mouse->Y.end + 10.f
, 1.f, 1.f, 0.f );
}
2023-09-28 10:44:43 -07:00
// Mouse buttons test
2023-10-01 17:17:14 -07:00
#if 0
{
if ( input->controllers[0].mouse->left.ended_down == true )
render_player( back_buffer, 5, 5 );
if ( input->controllers[0].mouse->middle.ended_down == true )
render_player( back_buffer, 5, 20 );
if ( input->controllers[0].mouse->right.ended_down == true )
render_player( back_buffer, 5, 35 );
}
2023-10-01 17:17:14 -07:00
#endif
2023-09-24 19:37:05 -07:00
}
2023-09-26 14:26:35 -07:00
Engine_API
2023-09-30 17:26:00 -07:00
void update_audio( f32 delta_time, AudioBuffer* audio_buffer, Memory* memory, platform::ModuleAPI* platform_api, ThreadContext* thread )
2023-09-24 19:37:05 -07:00
{
EngineState* state = rcast( EngineState*, memory->persistent );
2023-09-24 19:37:05 -07:00
do_once_start
do_once_end
2023-09-17 18:20:11 -07:00
2023-09-16 15:41:07 -07:00
// TODO(Ed) : Allow sample offsets here for more robust platform options
if ( ! state->sample_wave_switch )
2023-09-24 19:37:05 -07:00
output_sound( state, audio_buffer, sine_wave_sample_value );
2023-09-17 18:20:11 -07:00
else
2023-09-24 19:37:05 -07:00
output_sound( state, audio_buffer, square_wave_sample_value );
2023-09-15 18:35:27 -07:00
}
NS_ENGINE_END
2023-09-28 10:44:43 -07:00
#undef pressed