- add sound effects and background music
- add mute button - add walk and reverse adaptive sound effects to character - add more flexible error handling to PHP log of play time - set total memory to 200MB in web build
This commit is contained in:
parent
a090c52178
commit
ac095283f4
14
Makefile
14
Makefile
|
@ -117,8 +117,7 @@ $(addsuffix /Segment.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Segment.cpp
|
|||
$(addsuffix /Pixels.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Pixels.cpp Pixels.hpp Box.hpp extension.hpp Log.hpp math.hpp) | $(BUILD_DIRS)
|
||||
$(CXX) $(CXXFLAGS) $< -c -o $@
|
||||
|
||||
$(addsuffix /Audio.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Audio.cpp Audio.hpp Node.hpp Display.hpp Configuration.hpp Box.hpp filesystem.hpp extension.hpp \
|
||||
Animation.hpp) | $(BUILD_DIRS)
|
||||
$(addsuffix /Audio.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Audio.cpp Audio.hpp Node.hpp filesystem.hpp) | $(BUILD_DIRS)
|
||||
$(CXX) $(CXXFLAGS) $< -c -o $@
|
||||
|
||||
$(addsuffix /GLObject.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, GLObject.cpp GLObject.hpp Log.hpp) | $(BUILD_DIRS)
|
||||
|
@ -136,7 +135,7 @@ $(addsuffix /Attributes.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Attribute
|
|||
$(addsuffix /Model.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Model.cpp Model.hpp extension.hpp Attributes.hpp Texture.hpp Carousel.hpp) | $(BUILD_DIRS)
|
||||
$(CXX) $(CXXFLAGS) $< -c -o $@
|
||||
|
||||
$(addsuffix /Text.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Text.cpp Text.hpp Model.hpp Color.hpp) | $(BUILD_DIRS)
|
||||
$(addsuffix /Text.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Text.cpp Text.hpp Model.hpp Color.hpp Log.hpp) | $(BUILD_DIRS)
|
||||
$(CXX) $(CXXFLAGS) $< -c -o $@
|
||||
|
||||
$(addsuffix /Color.o, $(BUILD_DIRS)): $(addprefix $(SB_SRC_DIR)/, Color.cpp Color.hpp) | $(BUILD_DIRS)
|
||||
|
@ -199,7 +198,9 @@ EMSCRIPTENHOME = $(HOME)/ext/software/emsdk/upstream/emscripten
|
|||
EMSCRIPTEN_CFLAGS = -O0 -Wall -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS="['png', 'jpg']" -s USE_SDL_TTF=2 -s USE_SDL_MIXER=2 \
|
||||
--no-heap-copy -I $(SB_LIB_DIR) -I $(SB_SRC_DIR)
|
||||
EMSCRIPTEN_LFLAGS = -s MIN_WEBGL_VERSION=2 -s EXPORTED_FUNCTIONS="['_main', '_malloc']" \
|
||||
-s LLD_REPORT_UNDEFINED -s NO_DISABLE_EXCEPTION_CATCHING -s FULL_ES3=1 -s ALLOW_MEMORY_GROWTH=1 -lidbfs.js
|
||||
-s LLD_REPORT_UNDEFINED -s NO_DISABLE_EXCEPTION_CATCHING -s FULL_ES3=1 -lidbfs.js \
|
||||
-s TOTAL_MEMORY=200MB -s ALLOW_MEMORY_GROWTH=0
|
||||
# -s ALLOW_MEMORY_GROWTH=1
|
||||
EMSCRIPTEN_PRELOADS = --preload-file "BPmono.ttf"@/ --preload-file "config.json"@/ --preload-file "config_wasm.json"@/ --preload-file "resource/"@/"resource/" \
|
||||
--preload-file "src/shaders/"@/"src/shaders/"
|
||||
EMSCRIPTEN_GAME_CONFIGS = config.json config_wasm.json resource/levels.json
|
||||
|
@ -219,7 +220,8 @@ cakefoot_debug.html : CFLAGS = $(EMSCRIPTEN_CFLAGS) -g2
|
|||
cakefoot_debug.html : CXXFLAGS = $(CFLAGS) --std=c++17
|
||||
cakefoot_debug.html : $(WASM_OBJS) $(EMSCRIPTEN_GAME_CONFIGS)
|
||||
$(CREATE_FONT_SYMLINK)
|
||||
$(CXX) $(filter-out $(EMSCRIPTEN_GAME_CONFIGS), $^) $(EMSCRIPTEN_LFLAGS) $(EMSCRIPTEN_PRELOADS) --memoryprofiler --cpuprofiler -o cakefoot_debug.html
|
||||
$(CXX) $(filter-out $(EMSCRIPTEN_GAME_CONFIGS), $^) $(CXXFLAGS) $(EMSCRIPTEN_LFLAGS) $(EMSCRIPTEN_PRELOADS) --memoryprofiler --cpuprofiler \
|
||||
-o cakefoot_debug.html
|
||||
|
||||
#################
|
||||
# Android build #
|
||||
|
@ -284,7 +286,7 @@ $(ANDROID_BUILD_DIR)/app-debug.apk: $(ANDROID_BUILD_DIR) $(ANDROID_BUILD_DIR)/$(
|
|||
# Make all builds #
|
||||
###################
|
||||
|
||||
all : Cakefoot-linux_debug.x86_64 cakefoot.js
|
||||
all : Cakefoot-linux_debug.x86_64 cakefoot.js cakefoot_debug.html
|
||||
|
||||
#########################
|
||||
# Clean up object files #
|
||||
|
|
39
config.json
39
config.json
|
@ -113,11 +113,11 @@
|
|||
},
|
||||
{
|
||||
"name": "BUFFALO BEEF CAKE",
|
||||
"speed increment": 0.005,
|
||||
"speed increment": 0.01,
|
||||
"speed decrement": 0.0455,
|
||||
"max speed": 0.82,
|
||||
"max speed": 0.95,
|
||||
"min speed": -0.3125,
|
||||
"increment mod": 0.025,
|
||||
"increment mod": 0.08,
|
||||
"decrement mod": 0.125,
|
||||
"animation frames": ["resource/buffalo/cake1.png"]
|
||||
}
|
||||
|
@ -156,7 +156,11 @@
|
|||
"profile decrement text": "<",
|
||||
"profile decrement translation": [-0.65, -0.83],
|
||||
"profile increment text": ">",
|
||||
"profile increment translation": [0.65, -0.83]
|
||||
"profile increment translation": [0.65, -0.83],
|
||||
"volume on texture": "resource/vol.png",
|
||||
"volume off texture": "resource/vol_off.png",
|
||||
"volume translation": [-1.65, -0.85],
|
||||
"volume scale": 0.08
|
||||
},
|
||||
|
||||
"world": [
|
||||
|
@ -176,5 +180,30 @@
|
|||
"start": 18,
|
||||
"color": [0.23, 0.12, 0.18, 1.0]
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
"audio": {
|
||||
"files":
|
||||
{
|
||||
"restart": "resource/no.ogg",
|
||||
"teleport": "resource/grow_0.wav",
|
||||
"walk": "resource/bump_4.wav",
|
||||
"reverse": "resource/bump_5.wav",
|
||||
"main": "resource/azu main theme_amp.ogg",
|
||||
"menu": "resource/azu menu music_amp.ogg",
|
||||
"take": "resource/Coin_.wav",
|
||||
"checkpoint": "resource/arrive_0.wav"
|
||||
},
|
||||
"volume": {
|
||||
"restart": 0.5,
|
||||
"teleport": 0.7,
|
||||
"walk": 0.5,
|
||||
"reverse": 0.46,
|
||||
"main": 1.0,
|
||||
"menu": 1.0,
|
||||
"take": 0.5,
|
||||
"checkpoint": 0.5
|
||||
},
|
||||
"fade": 2.0
|
||||
}
|
||||
}
|
||||
|
|
2
lib/sb
2
lib/sb
|
@ -1 +1 @@
|
|||
Subproject commit 5046b4bcf1696296725ad4ab2bab7f589e97d7c8
|
||||
Subproject commit d575307b15024bcf7259f06126fd8d61adb455ac
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
179
src/Cakefoot.cpp
179
src/Cakefoot.cpp
|
@ -97,9 +97,44 @@ Cakefoot::Cakefoot()
|
|||
/* Load coin graphics */
|
||||
coin.load();
|
||||
|
||||
/* Load SFX and BGM */
|
||||
load_audio();
|
||||
|
||||
/* Load title screen and character graphics */
|
||||
character.load();
|
||||
load_level(0);
|
||||
|
||||
/* Switch volume on */
|
||||
button.at("volume").press();
|
||||
}
|
||||
|
||||
void Cakefoot::load_audio()
|
||||
{
|
||||
audio = {};
|
||||
for (const auto& [name, path] : configuration()("audio", "files").items())
|
||||
{
|
||||
audio[name] = sb::audio::Chunk(path.get<fs::path>());
|
||||
}
|
||||
|
||||
/* Set number of loops for looping audio */
|
||||
audio.at("restart").loop(5);
|
||||
audio.at("main").loop();
|
||||
audio.at("menu").loop();
|
||||
audio.at("walk").loop();
|
||||
audio.at("reverse").loop();
|
||||
|
||||
/* Set volumes */
|
||||
for (const auto& [name, volume] : configuration()("audio", "volume").items())
|
||||
{
|
||||
audio.at(name).volume(volume);
|
||||
}
|
||||
|
||||
/* Reserve two channels for walk sound effects so the channel volume manipulation won't affect other sounds. */
|
||||
int result = Mix_ReserveChannels(2);
|
||||
if (result != 2)
|
||||
{
|
||||
sb::Log::sdl_error("Unable to reserve audio channels in SDL mixer", sb::Log::WARN);
|
||||
}
|
||||
}
|
||||
|
||||
void Cakefoot::load_curves()
|
||||
|
@ -234,8 +269,23 @@ void Cakefoot::set_up_buttons()
|
|||
button.at("resume").on_state_change([&](bool state){
|
||||
unpaused_timer.on();
|
||||
run_timer.on();
|
||||
|
||||
/* Transition between menu theme and main theme */
|
||||
if (audio.at("menu").playing())
|
||||
{
|
||||
audio.at("menu").pause();
|
||||
}
|
||||
if (audio.at("main").paused())
|
||||
{
|
||||
audio.at("main").resume();
|
||||
}
|
||||
else if (audio.at("main").fading() || !audio.at("main").playing())
|
||||
{
|
||||
audio.at("main").play();
|
||||
}
|
||||
});
|
||||
button.at("reset").on_state_change([&](bool state){
|
||||
audio.at("main").stop();
|
||||
sb::Delegate::post(reset_command_name, false);
|
||||
});
|
||||
|
||||
|
@ -250,6 +300,65 @@ void Cakefoot::set_up_buttons()
|
|||
button.at("pause").on_state_change([&](bool state){
|
||||
unpaused_timer.off();
|
||||
run_timer.off();
|
||||
|
||||
std::cout << std::boolalpha << "menu playing: " << audio.at("menu").playing() << ", menu fading: " << audio.at("menu").fading() << std::endl;
|
||||
|
||||
/* Transition between main theme and menu theme */
|
||||
if (audio.at("main").playing())
|
||||
{
|
||||
audio.at("main").pause();
|
||||
}
|
||||
if (audio.at("menu").paused())
|
||||
{
|
||||
audio.at("menu").resume();
|
||||
}
|
||||
else if (audio.at("menu").fading() || !audio.at("menu").playing())
|
||||
{
|
||||
audio.at("menu").play();
|
||||
}
|
||||
|
||||
std::cout << std::boolalpha << "menu playing: " << audio.at("menu").playing() << ", menu fading: " << audio.at("menu").fading() << std::endl;
|
||||
|
||||
});
|
||||
|
||||
/* Set up volume button */
|
||||
bool original_state = button.at("volume").pressed();
|
||||
sb::Texture volume_off_texture {configuration()("button", "volume off texture")};
|
||||
volume_off_texture.load();
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
sb::Texture volume_on_texture {configuration()("button", "volume on texture")};
|
||||
volume_on_texture.load();
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
sb::Plane volume_plane;
|
||||
volume_plane.texture(volume_off_texture);
|
||||
volume_plane.texture(volume_on_texture);
|
||||
button.at("volume") = sb::Pad<>{volume_plane, configuration()("button", "volume translation"), configuration()("button", "volume scale"), 1.0f};
|
||||
button.at("volume").state(original_state);
|
||||
button.at("volume").on_state_change([&](bool state){
|
||||
/* BGM is paused to get around a bug in looping that causes looped audio to stop when looping on mute. */
|
||||
if (state)
|
||||
{
|
||||
Mix_Volume(-1, 128);
|
||||
// for (auto& [name, chunk] : audio)
|
||||
// {
|
||||
// chunk.channel_volume(MIX_MAX_VOLUME);
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& [name, chunk] : audio)
|
||||
{
|
||||
if (chunk.fading())
|
||||
{
|
||||
chunk.stop();
|
||||
chunk.play();
|
||||
}
|
||||
// chunk.channel_volume(0);
|
||||
}
|
||||
Mix_Volume(-1, 0);
|
||||
}
|
||||
});
|
||||
|
||||
/* Set up level select spinner */
|
||||
|
@ -417,6 +526,42 @@ void Cakefoot::load_level(int index)
|
|||
/* Wrap the index if it is out of range. */
|
||||
index = glm::mod(index, configuration()("levels").size());
|
||||
|
||||
/* Play menu theme on title screen and end screen. Play main theme on any other level. Cross fade between the two. */
|
||||
// float fade = configuration()("audio", "fade");
|
||||
if (index == 0 || static_cast<std::size_t>(index) == configuration()("levels").size() - 1)
|
||||
{
|
||||
/* If menu theme is already playing, let it continue to play. */
|
||||
// audio.at("menu").stop();
|
||||
if (!audio.at("menu").playing() || audio.at("menu").paused() || audio.at("menu").fading())
|
||||
{
|
||||
// audio.at("menu").play(fade);
|
||||
audio.at("menu").play();
|
||||
}
|
||||
|
||||
/* Cross fade main theme into menu theme */
|
||||
// if (audio.at("main").playing())
|
||||
// {
|
||||
// audio.at("main").stop(fade);
|
||||
audio.at("main").stop();
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
// audio.at("main").stop();
|
||||
if (audio.at("main").paused() || !audio.at("main").playing())
|
||||
{
|
||||
// audio.at("main").play(fade);
|
||||
audio.at("main").play();
|
||||
}
|
||||
|
||||
/* If the menu theme is playing, cross fade to the main theme. */
|
||||
// if (audio.at("menu").playing())
|
||||
// {
|
||||
// audio.at("menu").stop(fade);
|
||||
audio.at("menu").stop();
|
||||
// }
|
||||
}
|
||||
|
||||
/* Update indices and reset character. */
|
||||
level_index = index;
|
||||
curve_index = index;
|
||||
|
@ -770,6 +915,20 @@ void Cakefoot::respond(SDL_Event& event)
|
|||
}
|
||||
}
|
||||
|
||||
/* Collide with volume button */
|
||||
else if (button.at("volume").collide(mouse_ndc, view, projection))
|
||||
{
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN)
|
||||
{
|
||||
button.at("volume").press();
|
||||
button_pressed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hovering = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check pause menu buttons */
|
||||
else if (level_index > 0 && !unpaused_timer)
|
||||
{
|
||||
|
@ -846,6 +1005,7 @@ void Cakefoot::respond(SDL_Event& event)
|
|||
character.box_size(configuration()("character", "hitbox").get<float>());
|
||||
set_up_buttons();
|
||||
set_up_hud();
|
||||
load_audio();
|
||||
}
|
||||
|
||||
else if (sb::Delegate::compare(event, "window resize"))
|
||||
|
@ -936,9 +1096,10 @@ void Cakefoot::update(float timestamp)
|
|||
|
||||
/* Update character, along the curve, using the timer to determine movement since last frame, and update enemies. Check for collison
|
||||
* as enemies are updated. */
|
||||
character.update(curve(), unpaused_timer);
|
||||
character.update(curve(), unpaused_timer, !button.at("volume").pressed());
|
||||
if (character.at_end(curve()))
|
||||
{
|
||||
audio.at("teleport").play();
|
||||
load_level(level_index + 1);
|
||||
}
|
||||
else
|
||||
|
@ -950,6 +1111,7 @@ void Cakefoot::update(float timestamp)
|
|||
{
|
||||
if (character.relative(curve()) >= checkpoint["position"].get<float>() && character.checkpoint() < checkpoint["position"].get<float>())
|
||||
{
|
||||
audio.at("checkpoint").play();
|
||||
character.checkpoint(checkpoint["position"].get<float>());
|
||||
|
||||
/* Collect any previously taken coins */
|
||||
|
@ -976,13 +1138,15 @@ void Cakefoot::update(float timestamp)
|
|||
}
|
||||
else if (enemy->collide_coin(character.box(), clip_upper, clip_lower))
|
||||
{
|
||||
audio.at("take").play();
|
||||
enemy->take_coin();
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset level */
|
||||
if (enemy_collision)
|
||||
if (!character.resting() && enemy_collision)
|
||||
{
|
||||
audio.at("restart").play();
|
||||
character.spawn(curve());
|
||||
for (auto& enemy : enemies)
|
||||
{
|
||||
|
@ -1072,6 +1236,7 @@ void Cakefoot::update(float timestamp)
|
|||
character.draw(curve(), uniform["mvp"], view * rotation_matrix, projection, uniform["texture enabled"]);
|
||||
|
||||
/* Draw buttons. Don't include rotation matrix in view, so buttons will remain flat in the z-dimension. */
|
||||
button.at("volume").draw(uniform["mvp"], view, projection, uniform["texture enabled"]);
|
||||
if (level_index == 0)
|
||||
{
|
||||
button.at("start").draw(uniform["mvp"], view, projection, uniform["texture enabled"]);
|
||||
|
@ -1085,7 +1250,7 @@ void Cakefoot::update(float timestamp)
|
|||
/* Draw spinner labels */
|
||||
for (const std::string& name : {"level select", "profile"})
|
||||
{
|
||||
label.at(name).texture().bind();
|
||||
label.at(name).texture(0).bind();
|
||||
glm::mat4 label_transformation = projection * view * label.at(name).transformation();
|
||||
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label_transformation[0][0]);
|
||||
label.at(name).enable();
|
||||
|
@ -1115,7 +1280,7 @@ void Cakefoot::update(float timestamp)
|
|||
label.at("clock").content(clock.str());
|
||||
sb::Plane::position->bind("vertex_position", shader_program);
|
||||
glUniform1i(uniform["texture enabled"], true);
|
||||
label.at("clock").texture().bind();
|
||||
label.at("clock").texture(0).bind();
|
||||
glm::mat4 clock_transformation = projection * view * label.at("clock").transformation();
|
||||
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &clock_transformation[0][0]);
|
||||
label.at("clock").enable();
|
||||
|
@ -1127,7 +1292,7 @@ void Cakefoot::update(float timestamp)
|
|||
std::stringstream level_indicator;
|
||||
level_indicator << std::setw(2) << std::setfill('0') << level_index << "/" << std::setw(2) << _configuration("levels").size() - 2;
|
||||
label.at("level").content(level_indicator.str());
|
||||
label.at("level").texture().bind();
|
||||
label.at("level").texture(0).bind();
|
||||
glm::mat4 level_transformation = projection * view * label.at("level").transformation();
|
||||
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &level_transformation[0][0]);
|
||||
label.at("level").enable();
|
||||
|
@ -1143,12 +1308,12 @@ void Cakefoot::update(float timestamp)
|
|||
label.at("fps").content(padded);
|
||||
previous_frames_per_second = current_frames_per_second;
|
||||
}
|
||||
if (label.at("fps").texture().generated())
|
||||
if (label.at("fps").texture(0).generated())
|
||||
{
|
||||
/* Draw FPS indicator */
|
||||
sb::Plane::position->bind("vertex_position", shader_program);
|
||||
glUniform1i(uniform["texture enabled"], true);
|
||||
label.at("fps").texture().bind();
|
||||
label.at("fps").texture(0).bind();
|
||||
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label.at("fps").transformation()[0][0]);
|
||||
label.at("fps").enable();
|
||||
glDrawArrays(GL_TRIANGLES, 0, label.at("fps").attributes("position")->count());
|
||||
|
|
|
@ -94,7 +94,8 @@ private:
|
|||
{"level decrement", sb::Pad<>()},
|
||||
{"pause", sb::Pad<>()},
|
||||
{"profile increment", sb::Pad<>()},
|
||||
{"profile decrement", sb::Pad<>()}
|
||||
{"profile decrement", sb::Pad<>()},
|
||||
{"volume", sb::Pad<>()}
|
||||
};
|
||||
std::map<std::string, sb::Text> label = {
|
||||
{"fps", sb::Text(font())},
|
||||
|
@ -105,7 +106,6 @@ private:
|
|||
};
|
||||
sb::Sprite playing_field, checkpoint_on, checkpoint_off, coin {"resource/coin/coin-0.png", glm::vec2{12.0f / 486.0f}};
|
||||
sb::Timer on_timer, run_timer, unpaused_timer;
|
||||
Character character {_configuration};
|
||||
glm::vec3 camera_position {0.0f, 0.0f, 2.0f}, subject_position {0.0f, 0.0f, 0.0f};
|
||||
float zoom = 0.0f;
|
||||
glm::vec2 rotation = {0.0f, 0.0f};
|
||||
|
@ -113,6 +113,14 @@ private:
|
|||
std::vector<std::shared_ptr<Enemy>> enemies;
|
||||
glm::vec4 world_color {0.2f, 0.2f, 0.2f, 1.0f};
|
||||
std::map<std::string, std::shared_ptr<TTF_Font>> fonts;
|
||||
std::map<std::string, sb::audio::Chunk> audio;
|
||||
Character character {_configuration, audio};
|
||||
|
||||
/*!
|
||||
* Load sound effects and music into objects that can be used by the SDL mixer library. Use chunk objects for background music instead of
|
||||
* music objects so background music tracks can fade into each other.
|
||||
*/
|
||||
void load_audio();
|
||||
|
||||
/*!
|
||||
* Open configuration and load curve data into the object.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "Character.hpp"
|
||||
|
||||
Character::Character(const Configuration& configuration) : configuration(configuration) {}
|
||||
Character::Character(const Configuration& configuration, std::map<std::string, sb::audio::Chunk>& audio) :
|
||||
configuration(configuration), audio(audio) {}
|
||||
|
||||
void Character::profile(const std::string& name)
|
||||
{
|
||||
|
@ -61,6 +62,9 @@ void Character::spawn(const Curve& curve)
|
|||
position = curve[next_point_index];
|
||||
speed = 0.0f;
|
||||
accelerating = false;
|
||||
_resting = true;
|
||||
audio.at("walk").stop();
|
||||
audio.at("reverse").stop();
|
||||
}
|
||||
|
||||
void Character::checkpoint(float checkpoint)
|
||||
|
@ -78,68 +82,126 @@ bool Character::at_end(const Curve& curve) const
|
|||
return next_point_index > curve.length() - 1;
|
||||
}
|
||||
|
||||
bool Character::resting() const
|
||||
{
|
||||
return _resting;
|
||||
}
|
||||
|
||||
float Character::relative(const Curve& curve) const
|
||||
{
|
||||
return float(next_point_index) / curve.length();
|
||||
}
|
||||
|
||||
void Character::update(const Curve& curve, const sb::Timer& timer)
|
||||
void Character::update(const Curve& curve, const sb::Timer& timer, bool muted)
|
||||
{
|
||||
/* Adjust speed based on acceleration state and character profile. */
|
||||
if (accelerating)
|
||||
if (timer.frame() > 0.0f)
|
||||
{
|
||||
/* Apply delta time to the speed increase. */
|
||||
speed += timer.delta(profile()["speed increment"].get<float>()) + glm::abs(speed) * profile()["increment mod"].get<float>();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Apply delta time to the speed decrease. */
|
||||
speed -= timer.delta(profile()["speed decrement"].get<float>()) + glm::abs(speed) * profile()["decrement mod"].get<float>();
|
||||
}
|
||||
/* Adjust speed based on acceleration state and character profile. */
|
||||
if (accelerating)
|
||||
{
|
||||
_resting = false;
|
||||
|
||||
/* Clamp speed, applying delta time to the limits */
|
||||
speed = std::clamp(speed, timer.delta(profile()["min speed"].get<float>()), timer.delta(profile()["max speed"].get<float>()));
|
||||
|
||||
/* Move along unwrapped curve vertices */
|
||||
float distance_remaining = std::abs(speed), distance = 0.0f;
|
||||
glm::vec3 next_point, step;
|
||||
while (distance_remaining)
|
||||
{
|
||||
if (speed < 0.0f && relative(curve) <= checkpoint())
|
||||
{
|
||||
speed = 0.0f;
|
||||
break;
|
||||
}
|
||||
else if (speed > 0.0f && next_point_index > curve.length() - 1)
|
||||
{
|
||||
speed = 0.0f;
|
||||
break;
|
||||
}
|
||||
if (speed > 0.0f)
|
||||
{
|
||||
next_point = curve[next_point_index];
|
||||
/* Apply delta time to the speed increase. */
|
||||
speed += timer.delta(profile()["speed increment"].get<float>()) + glm::abs(speed) * profile()["increment mod"].get<float>();
|
||||
}
|
||||
else
|
||||
{
|
||||
next_point = curve[next_point_index - 1];
|
||||
/* Apply delta time to the speed decrease. */
|
||||
speed -= timer.delta(profile()["speed decrement"].get<float>()) + glm::abs(speed) * profile()["decrement mod"].get<float>();
|
||||
}
|
||||
distance = glm::distance(position, next_point);
|
||||
if (distance < distance_remaining)
|
||||
|
||||
/* Clamp speed, applying delta time to the limits */
|
||||
float max_speed = timer.delta(profile()["max speed"].get<float>());
|
||||
float min_speed = timer.delta(profile()["min speed"].get<float>());
|
||||
speed = std::clamp(speed, min_speed, max_speed);
|
||||
|
||||
/* Calculate volume based on speed relative to max speed */
|
||||
int volume;
|
||||
if (speed >= 0.0f)
|
||||
{
|
||||
distance_remaining -= distance;
|
||||
position = next_point;
|
||||
next_point_index += speed < 0.0f ? -1 : 1;
|
||||
/* Only play walking forward effect. */
|
||||
audio.at("reverse").stop();
|
||||
if (!audio.at("walk").playing())
|
||||
{
|
||||
audio.at("walk").play(0.0f, walk_channel);
|
||||
}
|
||||
|
||||
if (!muted)
|
||||
{
|
||||
/* Get louder closer to max speed using an exponential scale */
|
||||
volume = std::round(std::pow(speed / max_speed, 3.0f) * static_cast<float>(MIX_MAX_VOLUME));
|
||||
audio.at("walk").channel_volume(volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.at("walk").stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
step = glm::vec3{sb::Segment(position, next_point).step(distance_remaining), 0.0f};
|
||||
position += step;
|
||||
distance_remaining = 0;
|
||||
}
|
||||
}
|
||||
/* Only play walking backward effect. */
|
||||
audio.at("walk").stop();
|
||||
if (!audio.at("reverse").playing())
|
||||
{
|
||||
audio.at("reverse").play(0.0f, reverse_channel);
|
||||
}
|
||||
|
||||
/* Update collision box location. */
|
||||
_box.south(position);
|
||||
if (!muted)
|
||||
{
|
||||
/* Get louder closer to min speed using an exponential scale */
|
||||
volume = std::round(std::pow(speed / min_speed, 3.0f) * static_cast<float>(MIX_MAX_VOLUME));
|
||||
audio.at("reverse").channel_volume(volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.at("reverse").stop();
|
||||
}
|
||||
}
|
||||
|
||||
/* Move along unwrapped curve vertices */
|
||||
float distance_remaining = std::abs(speed), distance = 0.0f;
|
||||
glm::vec3 next_point, step;
|
||||
while (distance_remaining)
|
||||
{
|
||||
if (speed < 0.0f && (relative(curve) <= 0.0f || (resting() && relative(curve) <= checkpoint())))
|
||||
{
|
||||
_resting = true;
|
||||
audio.at("walk").stop();
|
||||
audio.at("reverse").stop();
|
||||
speed = 0.0f;
|
||||
break;
|
||||
}
|
||||
else if (speed > 0.0f && next_point_index > curve.length() - 1)
|
||||
{
|
||||
speed = 0.0f;
|
||||
break;
|
||||
}
|
||||
if (speed > 0.0f)
|
||||
{
|
||||
next_point = curve[next_point_index];
|
||||
}
|
||||
else
|
||||
{
|
||||
next_point = curve[next_point_index - 1];
|
||||
}
|
||||
distance = glm::distance(position, next_point);
|
||||
if (distance < distance_remaining)
|
||||
{
|
||||
distance_remaining -= distance;
|
||||
position = next_point;
|
||||
next_point_index += speed < 0.0f ? -1 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
step = glm::vec3{sb::Segment(position, next_point).step(distance_remaining), 0.0f};
|
||||
position += step;
|
||||
distance_remaining = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update collision box location. */
|
||||
_box.south(position);
|
||||
}
|
||||
}
|
||||
|
||||
void Character::draw(const Curve& curve, GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform)
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#include "json/json.hpp"
|
||||
#include "SDL_mixer.h"
|
||||
|
||||
#include "Configuration.hpp"
|
||||
#include "Switch.hpp"
|
||||
|
@ -11,6 +13,7 @@
|
|||
#include "Sprite.hpp"
|
||||
#include "Curve.hpp"
|
||||
#include "Segment.hpp"
|
||||
#include "Audio.hpp"
|
||||
|
||||
class Character
|
||||
{
|
||||
|
@ -20,8 +23,9 @@ private:
|
|||
/* Convert pixel size to NDC size. */
|
||||
inline static const glm::vec2 size {20.0f / 486.0f};
|
||||
|
||||
/* Keep a reference to the global configuration */
|
||||
/* Keep references to the global configuration and audio data */
|
||||
const Configuration& configuration;
|
||||
std::map<std::string, sb::audio::Chunk>& audio;
|
||||
|
||||
/* Set at runtime based on the configuration */
|
||||
sb::Sprite _sprite;
|
||||
|
@ -31,6 +35,7 @@ private:
|
|||
float speed {0.0f}, _checkpoint {0.0f};
|
||||
int next_point_index {0};
|
||||
Box _box {{0.0f, 0.0f}, 2.0f * this->size};
|
||||
bool _resting = true;
|
||||
|
||||
/*!
|
||||
* A JSON object containing the fields: name, speed increment, speed decrement, max speed, increment mod, decrement mod, and animation frames.
|
||||
|
@ -43,6 +48,10 @@ private:
|
|||
|
||||
public:
|
||||
|
||||
/* Two SDL mixer channels reserved for walk sound effects */
|
||||
inline static const int walk_channel = 0;
|
||||
inline static const int reverse_channel = 1;
|
||||
|
||||
/* Create an object which can be switched on and off to move and stop the character. */
|
||||
sb::Switch<> accelerating = false;
|
||||
|
||||
|
@ -52,7 +61,7 @@ public:
|
|||
/*!
|
||||
* @param configuration reference to the global configuration which is expected to contain profile entries for character physics
|
||||
*/
|
||||
Character(const Configuration& configuration);
|
||||
Character(const Configuration& configuration, std::map<std::string, sb::audio::Chunk>& audio);
|
||||
|
||||
/*!
|
||||
* Change the character's physics and texture by specifying a profile name. The name refers to a JSON profile stored in an array of profiles
|
||||
|
@ -121,6 +130,11 @@ public:
|
|||
*/
|
||||
bool at_end(const Curve& curve) const;
|
||||
|
||||
/*!
|
||||
* @return True if the character has just respawned or the level just began
|
||||
*/
|
||||
bool resting() const;
|
||||
|
||||
/*!
|
||||
* @return character's relative position on the given curve
|
||||
*/
|
||||
|
@ -131,8 +145,9 @@ public:
|
|||
*
|
||||
* @param curve the curve to update against
|
||||
* @param timer a timer object that is updated once per frame, so that it provides delta time for movement
|
||||
* @param muted flag for preventing the walk sound effect output
|
||||
*/
|
||||
void update(const Curve& curve, const sb::Timer& timer);
|
||||
void update(const Curve& curve, const sb::Timer& timer, bool muted = false);
|
||||
|
||||
/*!
|
||||
* Perform GL drawing operations using the character's sprite object.
|
||||
|
|
|
@ -1,25 +1,35 @@
|
|||
<?php
|
||||
|
||||
/* Get a unique ID from the browser which PHP creates and manages using a browser cookie */
|
||||
session_start();
|
||||
|
||||
/* The log of play history is stored in JSON format in a file in the same directory as this script. If this file doesn't exist yet, it
|
||||
* will be created at write time. */
|
||||
$history_path = "Play_History.json";
|
||||
if (file_exists($history_path))
|
||||
|
||||
/* Read JSON data from the history path. If the file doesn't exist, is not in JSON format, or any other exception occurs, just set the
|
||||
* history to an empty array. */
|
||||
try
|
||||
{
|
||||
$history = json_decode(file_get_contents($history_path), true);
|
||||
$history = json_decode(file_get_contents($history_path), true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
else
|
||||
catch (Exception $e)
|
||||
{
|
||||
$history = array();
|
||||
}
|
||||
|
||||
/* JSON data containing the user's play history is passed as a POST request from JavaScript */
|
||||
$submitted_user_log = array(session_id() => json_decode(file_get_contents("php://input"), true)["progress"]);
|
||||
|
||||
/* Merge the passed play history into the history array, overwriting any existing data at the current session ID, or adding a new section
|
||||
* to the array if the session ID doesn't exist as a key in the array yet. Write the array to the history path. */
|
||||
file_put_contents(
|
||||
$history_path,
|
||||
json_encode(
|
||||
array_merge($history, $submitted_user_log),
|
||||
JSON_PRETTY_PRINT) . "\n");
|
||||
|
||||
/* Print the session ID formatted as JSON, so that the JavaScript program can get the ID as a response. */
|
||||
echo json_encode(array("id" => session_id()));
|
||||
|
||||
?>
|
||||
|
|
Loading…
Reference in New Issue