added jackpot, ending screen messages, title screen idle animation, play button flash

This commit is contained in:
ohsqueezy 2023-12-07 19:26:32 -05:00
parent 0121b33cbe
commit ec6165214b
12 changed files with 344 additions and 115 deletions

View File

@ -34,14 +34,14 @@
"game over background": [0.0, 0.0, 0.0, 255.0],
"game over translation": [0.0, 0.0],
"game over scale": [0.25, 0.05],
"arcade rank scale": [0.4, 0.1],
"arcade rank scale": [0.5, 0.15],
"arcade rank dimensions": [300.0, 80.0],
"arcade rank translation": [-1.2, 0.5],
"arcade distance scale": [0.4, 0.1],
"arcade rank translation": [0.3, 0.6],
"arcade distance scale": [0.3, 0.075],
"arcade distance dimensions": [300.0, 80.0],
"arcade distance translation": [0.0, 0.5],
"arcade time remaining scale": [0.4, 0.1],
"arcade time remaining translation": [1.2, 0.5],
"arcade distance translation": [1.1, 0.675],
"arcade time remaining scale": [0.3, 0.075],
"arcade time remaining translation": [1.1, 0.525],
"scoreboard foreground": [155.0, 155.0, 155.0, 255.0],
"scoreboard background": [0.0, 0.0, 0.0, 0.0],
"scoreboard translation": [-0.38, 0.845],
@ -52,11 +52,12 @@
"qr translation": [1.49, -0.7],
"qr scale": [0.205, 0.225],
"end screen timeout": 40.0,
"end screen timeout": 10040.0,
"enemy sprite scale": 0.024691358,
"quest best text": "BEST ",
"quest best scale": [0.31, 0.04],
"quest best text": " ",
"quest best scale": [0.25, 0.04],
"quest best translation": [-1.42, 0.92],
"quest best dimensions": [160.0, 19.0],
"quest best dimensions": [160.0, 24.0],
"quest best foreground": [255.0, 255.0, 255.0, 255.0],
"quest best background": [0.0, 0.0, 0.0, 60.0],
"hue shift": 10.0,
@ -111,15 +112,30 @@
"font":
{
"text":
"medium":
{
"path": "BPmono.ttf",
"path": "resource/BPmono.ttf",
"size": 24
},
"small text":
"small":
{
"path": "BPmono.ttf",
"path": "resource/BPmono.ttf",
"size": 14
},
"large":
{
"path": "resource/BPmono.ttf",
"size": 72
},
"glyph":
{
"path": "resource/DejaVuSans.ttf",
"size": 24
},
"glyph large":
{
"path": "resource/DejaVuSans.ttf",
"size": 44
}
},
@ -183,7 +199,14 @@
]
}
],
"hitbox": 0.7
"hitbox": 0.7,
"idle speed": 0.001,
"jackpot frames": [
"resource/gold/cake1.png",
"resource/gold/cake2.png",
"resource/gold/cake3.png",
"resource/gold/cake4.png"
]
},
"button":
@ -195,7 +218,7 @@
"text scale": 0.71,
"text foreground": [200.0, 200.0, 200.0, 255.0],
"text background": [60.0, 60.0, 60.0, 190.0],
"start text": "PLAY",
"start text": "PLAY",
"start translation": [0.0, -0.4],
"start alt texture": "resource/press_button_to_start.png",
"start alt translation": [0.0, -0.5],
@ -248,16 +271,16 @@
{
"arrow dimensions": [80.0, 40.0],
"arrow scale": [0.3, 0.15],
"arrow increment y": -0.25,
"arrow decrement y": -0.76,
"character 1 x": -0.4,
"character 2 x": 0.0,
"character 3 x": 0.4,
"arrow increment y": 0.85,
"arrow decrement y": 0.32,
"character 1 x": -1.2,
"character 2 x": -0.9,
"character 3 x": -0.6,
"arrow increment texture": "resource/up_arrow.png",
"arrow decrement texture": "resource/down_arrow.png",
"character dimensions": [60.0, 80.0],
"character scale": [0.2, 0.15],
"character y": -0.5
"character y": 0.6
},
"fullscreen texture": "resource/fullscreen.png",
"fullscreen translation": [-1.45, -0.85],
@ -350,7 +373,8 @@
"main": 1.0,
"menu": 1.0,
"take": 0.5,
"checkpoint": 0.5
"checkpoint": 0.5,
"bong": 0.5
},
"fade": 2.0
},
@ -358,6 +382,19 @@
"ending":
{
"coin range": [-0.5, 0.5],
"coin y": 0.07
"coin y": 0.07,
"messages y": -0.3,
"messages margin": -0.075,
"messages step": -0.125,
"messages dimensions": [500.0, 48.0],
"messages scale": [0.9, 0.08],
"messages foreground": [255.0, 255.0, 255.0, 255.0],
"end text": "THE END",
"unlock mirror": "✩ UNLOCKED MIRROR MODE ✩",
"unlock warped": "✩ UNLOCKED WARPED MODE ✩",
"unlock beef": "✩ UNLOCKED BEEF CAKE ✩",
"unlock buffalo": "✩ UNLOCKED BUFFALO BEEF CAKE ✩",
"unlock jackpot": "✩ UNLOCKED GOLDEN CAKE ✩",
"new best": "✩ NEW BEST TIME ✩"
}
}

2
lib/sb

@ -1 +1 @@
Subproject commit fb68938889ce200fc8623a5608d3c7adb58a9b34
Subproject commit c7fb948e3970eeaccb143225ab10bc84689bd09f

View File

@ -0,0 +1,97 @@
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
Bitstream Vera Fonts Copyright
———————————————
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license (“Fonts”) and associated
documentation files (the “Font Software”), to reproduce and distribute the
Font Software, including without limitation the rights to use, copy, merge,
publish, distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to the
following conditions:
The above copyright and trademark notices and this permission notice shall
be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional glyphs or characters may be added to the Fonts, only if the fonts
are renamed to names not containing either the words “Bitstream” or the word
“Vera”.
This License becomes null and void to the extent applicable to Fonts or Font
Software that has been modified and is distributed under the “Bitstream
Vera” names.
The Font Software may be sold as part of a larger software package but no
copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font Software
without prior written authorization from the Gnome Foundation or Bitstream
Inc., respectively. For further information, contact: fonts at gnome dot
org.
Arev Fonts Copyright
———————————————
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license (“Fonts”) and
associated documentation files (the “Font Software”), to reproduce
and distribute the modifications to the Bitstream Vera Font Software,
including without limitation the rights to use, copy, merge, publish,
distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to
the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words “Tavmjong Bah” or the word “Arev”.
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
“Tavmjong Bah Arev” names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not
be used in advertising or otherwise to promote the sale, use or other
dealings in this Font Software without prior written authorization
from Tavmjong Bah. For further information, contact: tavmjong @ free
. fr.

BIN
resource/DejaVuSans.ttf Normal file

Binary file not shown.

BIN
resource/gold/cake1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
resource/gold/cake2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
resource/gold/cake3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
resource/gold/cake4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -53,7 +53,8 @@ Cakefoot::Cakefoot()
{"arcade max distance", 0},
{"arcade time", 0.0f},
{"arcade coin", false},
{"arcade bank", 0}
{"arcade bank", 0},
{"jackpot", 0}
};
/* Overwrite progress data with saved data if it is available */
@ -131,26 +132,6 @@ Cakefoot::Cakefoot()
/* Set the character to use the profile stored to progress */
character.profile(configuration()("character", "profile", profile_index, "name"));
/* Try to load fonts. Default to the game object's font if the font doesn't load. */
for (auto [name, font_config] : configuration()("font").items())
{
fonts[name] = {TTF_OpenFont(font_config["path"].get<std::string>().c_str(), font_config["size"]), TTF_CloseFont};
std::ostringstream message;
if (fonts[name].get() == nullptr)
{
message << "Could not load " << font_config["path"] << ". Using framework's font instead.";
sb::Log::log(message, sb::Log::ERR);
/* Default to framework's font */
fonts[name] = font();
}
else
{
message << "Loaded " << font_config["path"];
sb::Log::log(message);
}
}
/* Set up checkpoint on and off sprites */
checkpoint_on = sb::Sprite {"resource/checkpoint/on.png", glm::vec2(12.0f / 486.0f)};
checkpoint_off = sb::Sprite {"resource/checkpoint/off.png", glm::vec2(12.0f / 486.0f)};
@ -328,7 +309,7 @@ void Cakefoot::set_up_buttons()
"challenge decrement", "view increment", "view decrement"
})
{
sb::Text text {fonts.at("text")};
sb::Text text {name == "start" ? fonts.at("glyph") : fonts.at("medium")};
float scale;
glm::vec2 dimensions;
if (name == "start" || name == "resume" || name == "reset")
@ -655,7 +636,7 @@ void Cakefoot::set_up_buttons()
for (const std::string& character_index : {"1", "2", "3"})
{
glm::vec2 character_dimensions {configuration()("button", "name", "character dimensions")};
sb::Text character {large_font, "", configuration()("display", "clock hud foreground").get<glm::vec4>(),
sb::Text character {fonts.at("large"), "", configuration()("display", "clock hud foreground").get<glm::vec4>(),
configuration()("display", "clock hud background").get<glm::vec4>(), character_dimensions};
character.content(name_entry[std::stoi(character_index) - 1]);
button.at("name " + character_index) = sb::Pad<>{
@ -761,7 +742,7 @@ void Cakefoot::set_up_hud()
glm::vec3 clock_scale, clock_translation;
if (static_cast<std::size_t>(level_index) == _configuration("levels").size() - 1)
{
label.at("clock").font(large_font);
label.at("clock").font(fonts.at("large"));
if (arcade())
{
/* Arcade results size */
@ -1065,8 +1046,8 @@ void Cakefoot::load_level(int index)
}
}
/* If the level is the end screen, reset the player's current level to the beginning and load ending screen coin list. Otherwise, if the level is not
* the title screen, save it as the current level in the player's progress. Also save the newly assigned current level in the level select index. */
/* If the level is the end screen, reset the player's current level to the beginning and load ending screen coin list. Unlock any new difficulty
* or view. Set a list of messages to be displayed on the end screen. */
if (end_screen())
{
/* Load ending coins */
@ -1082,6 +1063,44 @@ void Cakefoot::load_level(int index)
ending_coins.push_back(coin);
}
/* Clear list of ending messages */
ending_messages.clear();
/* Show the end for any run that beats all the levels */
if (quest() || (arcade() && configuration()("progress", "arcade level") >= configuration()("levels").size() - 2))
{
sb::Text message {fonts.at("glyph large"), configuration()("ending", "end text"), configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
ending_messages.push_back(message);
}
/* Unlocks for getting all coins */
if (bank() >= max_bank())
{
if (configuration()("progress", "max view").get<int>() < 1)
{
configuration()["progress"]["max view"] = 1;
sb::Text message {fonts.at("glyph"), configuration()("ending", "unlock mirror"), configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
ending_messages.push_back(message);
}
if (configuration()("progress", "max view").get<int>() < 2 && profile_index >= 1)
{
configuration()["progress"]["max view"] = 2;
sb::Text message {fonts.at("glyph"), configuration()("ending", "unlock warped"), configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
ending_messages.push_back(message);
}
if (configuration()("progress", "jackpot") != 777 && profile_index == 2)
{
configuration()["progress"]["jackpot"] = 777;
character.profile(configuration()("character", "profile", profile_index, "name"));
sb::Text message {fonts.at("glyph"), configuration()("ending", "unlock jackpot"), configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
ending_messages.push_back(message);
}
}
configuration()["progress"]["current level"] = 1;
/* Update save progress */
@ -1112,6 +1131,15 @@ void Cakefoot::load_level(int index)
{
if (profile_index < static_cast<int>(configuration()("character", "profile").size()) - 1)
{
if (profile_index == configuration()("progress", "max level"))
{
sb::Text message {fonts.at("glyph")};
message.foreground(configuration()("ending", "messages foreground").get<glm::vec4>());
profile_index == 1 ? message.content(configuration()("ending", "unlock beef")) : message.content(configuration()("ending", "unlock buffalo"));
message.dimensions(configuration()("ending", "messages dimensions"));
ending_messages.push_back(message);
}
configuration()["progress"]["max difficulty"] = ++profile_index;
configuration()["progress"]["max level"] = 1;
character.profile(configuration()("character", "profile", profile_index, "name"));
@ -1122,12 +1150,18 @@ void Cakefoot::load_level(int index)
{
configuration()["progress"]["quest best"] = run_timer.elapsed();
label.at("quest best").content(configuration()("display", "quest best text").get<std::string>() + " " + format_clock(run_timer.elapsed()));
sb::Text message {fonts.at("glyph"), configuration()("ending", "new best"), configuration()("ending", "messages foreground").get<glm::vec4>()};
message.dimensions(configuration()("ending", "messages dimensions"));
ending_messages.push_back(message);
}
}
write_progress();
level_select_index = 1;
}
/* Otherwise, if the level is not the title screen, save it as the current level in the player's progress. Also save the newly assigned current level
* in the level select index. */
else if (index > 0)
{
/* Unlock the level if it is a newly reached level */
@ -1926,7 +1960,8 @@ 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, !button.at("volume").pressed());
character.update(curve(), unpaused_timer, !button.at("volume").pressed(),
level_index ? std::nullopt : std::optional<float>(configuration()("character", "idle speed").get<float>()));
if (character.at_end(curve()))
{
/* On the end level, save the score and name entry. */
@ -1939,9 +1974,9 @@ void Cakefoot::update(float timestamp)
/* Collect any previously taken coins */
collect_coin();
/* Load next level, or reload current level if in level select mode */
/* Load next level, or reload current level if in level select mode or on title screen */
audio.at("teleport").play();
load_level(level_select() ? level_index : level_index + 1);
load_level(level_select() || level_index == 0 ? level_index : level_index + 1);
}
}
else
@ -2139,7 +2174,13 @@ void Cakefoot::update(float timestamp)
glm::mat4 label_transformation {0.0f};
if (level_index == 0)
{
/* Flash play button */
glUniform4fv(uniform.at("color addition"), 1, &rotating_hue.normal()[0]);
button.at("start").draw(uniform["mvp"], view, projection, uniform["texture enabled"]);
if (!flash_animation.playing())
{
glUniform4fv(uniform.at("color addition"), 1, &glm::vec4(0)[0]);
}
/* Disable spinners in arcade-only mode */
if (!configuration()("display", "arcade only"))
@ -2273,6 +2314,29 @@ void Cakefoot::update(float timestamp)
qr_code.draw(uniform.at("mvp"), view, projection, uniform.at("texture enabled"));
}
/* Draw end screen messages */
if (static_cast<std::size_t>(level_index) == configuration()("levels").size() - 1)
{
float y = configuration()("ending", "messages y").get<float>();
for (std::size_t message_ii = 0; message_ii < ending_messages.size(); message_ii++)
{
sb::Text& message = ending_messages[message_ii];
message.untransform();
message.translate({0.0f, y, 0.0f});
message.scale(configuration()("ending", "messages scale"));
message.texture(0).bind();
label_transformation = projection * view * message.transformation();
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label_transformation[0][0]);
message.enable();
glDrawArrays(GL_TRIANGLES, 0, message.attributes("position")->count());
if (message_ii == 0)
{
y += configuration()("ending", "messages margin").get<float>();
}
y += configuration()("ending", "messages step").get<float>();
}
}
/* Update FPS indicator display to the current FPS count and draw. */
if (configuration()["display"]["fps"])
{

View File

@ -224,8 +224,13 @@ private:
{"name 3 decrement", sb::Pad<>()},
{"fullscreen", sb::Pad<>()}
};
std::shared_ptr<TTF_Font> large_font {font(configuration()("display", "default font path").get<std::string>(), 72)},
small_font {font(configuration()("display", "default font path").get<std::string>(), 12)};
std::map<std::string, std::shared_ptr<TTF_Font>> fonts {
{"medium", font(configuration()("font", "medium", "path").get<std::string>(), configuration()("font", "medium", "size"))},
{"small", font(configuration()("font", "small", "path").get<std::string>(), configuration()("font", "small", "size"))},
{"large", font(configuration()("font", "large", "path").get<std::string>(), configuration()("font", "large", "size"))},
{"glyph", font(configuration()("font", "glyph", "path").get<std::string>(), configuration()("font", "glyph", "size"))},
{"glyph large", font(configuration()("font", "glyph large", "path").get<std::string>(), configuration()("font", "glyph large", "size"))}
};
std::map<std::string, sb::Text> label = {
{"fps", sb::Text(font())},
{"clock", sb::Text(font())},
@ -235,9 +240,9 @@ private:
{"challenge", sb::Text(font())},
{"view", sb::Text(font())},
{"game over", sb::Text(font())},
{"arcade rank", sb::Text(large_font)},
{"arcade distance", sb::Text(large_font)},
{"quest best", sb::Text(font())}
{"arcade rank", sb::Text(fonts.at("large"))},
{"arcade distance", sb::Text(fonts.at("large"))},
{"quest best", sb::Text(fonts.at("glyph"))}
};
sb::Sprite playing_field, checkpoint_on, checkpoint_off, coin {"resource/coin/coin-0.png", glm::vec2{12.0f / 486.0f}}, qr_code;
sb::Timer on_timer, run_timer, unpaused_timer;
@ -247,16 +252,16 @@ private:
std::vector<Curve> curves;
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};
bool use_play_button = false, coin_collected = false;
ArcadeScores arcade_scores;
ArcadeScores::Score arcade_score;
std::string name_entry = "AAA";
sb::Text scoreboard {large_font};
sb::Text scoreboard {fonts.at("large")};
std::vector<Flame> ending_coins;
sb::Color rotating_hue {128, 0, 0, 0};
std::vector<sb::Text> ending_messages;
/*!
* Load sound effects and music into objects that can be used by the SDL mixer library. Use chunk objects for background music instead of

View File

@ -14,7 +14,8 @@ void Character::profile(const std::string& name)
/* Reload the texture */
_sprite.clear_textures();
for (const std::string& path : profile().at("animation frames"))
nlohmann::json frames = configuration("progress", "jackpot") == 777 ? configuration("character", "jackpot frames") : profile().at("animation frames");
for (const std::string& path : frames)
{
_sprite.texture(path);
}
@ -99,22 +100,31 @@ float Character::relative(const Curve& curve) const
return float(next_point_index) / curve.length();
}
void Character::update(const Curve& curve, const sb::Timer& timer, bool muted)
void Character::update(const Curve& curve, const sb::Timer& timer, bool muted, std::optional<float> constant_speed)
{
if (timer.frame() > 0.0f)
{
/* Adjust speed based on acceleration state and character profile. */
if (accelerating)
if (constant_speed.has_value())
{
/* Override physics */
speed = constant_speed.value();
_resting = false;
/* 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;
/* 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>();
}
}
/* Clamp speed, applying delta time to the limits */
@ -124,44 +134,47 @@ void Character::update(const Curve& curve, const sb::Timer& timer, bool muted)
/* Calculate volume based on speed relative to max speed */
int volume;
if (speed >= 0.0f)
if (!constant_speed.has_value())
{
/* 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
{
/* Only play walking backward effect. */
audio.at("walk").stop();
if (!audio.at("reverse").playing())
{
audio.at("reverse").play(0.0f, reverse_channel);
}
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
if (speed >= 0.0f)
{
/* 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
{
/* Only play walking backward effect. */
audio.at("walk").stop();
if (!audio.at("reverse").playing())
{
audio.at("reverse").play(0.0f, reverse_channel);
}
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();
}
}
}
@ -177,16 +190,24 @@ void Character::update(const Curve& curve, const sb::Timer& timer, bool muted)
if (speed > 0.0f)
{
if (speed == max_speed)
{
walk.frame_length(0.095f);
}
else if (speed > max_speed * 0.9f)
{
walk.frame_length(0.1f);
}
else if (speed > max_speed / 2)
else if (speed > max_speed * 0.75f)
{
walk.frame_length(0.07f);
walk.frame_length(0.125f);
}
else if (speed > max_speed * 0.5)
{
walk.frame_length(0.15f);
}
else
{
walk.frame_length(0.04f);
walk.frame_length(0.175f);
}
_sprite.texture_increment(1);
}
@ -196,13 +217,17 @@ void Character::update(const Curve& curve, const sb::Timer& timer, bool muted)
{
walk.frame_length(0.1f);
}
else if (speed < max_speed / 2)
else if (speed < max_speed * 0.75f)
{
walk.frame_length(0.07f);
walk.frame_length(0.125f);
}
else if (speed < max_speed * 0.5f)
{
walk.frame_length(0.15f);
}
else
{
walk.frame_length(0.04f);
walk.frame_length(0.175f);
}
_sprite.texture_increment(-1);
}

View File

@ -77,7 +77,7 @@ public:
* However, the texture will be directly modified because re-reading the texture every frame is too slow. To automate re-reading the texture,
* insert a hook into the game's update loop that runs this function every time a configuration modification is detected.
*
* @param name string in the name field of the chosen profile in the list of profiles at `character -> profile`
* @param name String in the name field of the chosen profile in the list of profiles at `character -> profile`
*/
void profile(const std::string& name);
@ -146,11 +146,12 @@ public:
/*!
* Check acceleration state and adjust speed. Move character toward the next point on the curve.
*
* @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
* @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
* @param constant_speed override acceleration processing and set the movement to a constant speed
*/
void update(const Curve& curve, const sb::Timer& timer, bool muted = false);
void update(const Curve& curve, const sb::Timer& timer, bool muted = false, std::optional<float> constant_speed = std::nullopt);
/*!
* Perform GL drawing operations using the character's sprite object.