- add coins to ending

- add animation frames to sprites
- track best quest time
- hue shift effect on coin
This commit is contained in:
ohsqueezy 2023-12-04 21:55:11 -05:00
parent fd6ef890cf
commit d11cffc210
31 changed files with 280 additions and 124 deletions

View File

@ -16,7 +16,7 @@
"checkpoint distance": 0.1,
"clock hud scale": [0.15, 0.04],
"clock hud translation": [1.6, 0.92],
"clock hud large scale": [1.0, 0.35],
"clock hud large scale": [0.65, 0.2],
"clock hud large translation": [0.0, 0.5],
"clock hud foreground": [255.0, 255.0, 255.0, 255.0],
"clock hud background": [0.0, 0.0, 0.0, 60.0],
@ -51,7 +51,17 @@
"qr texture": "resource/qr.png",
"qr translation": [1.49, -0.7],
"qr scale": [0.205, 0.225],
"end screen timeout": 40.0
"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 translation": [-1.42, 0.92],
"quest best dimensions": [160.0, 19.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,
"hue shift frequency": 0.025
},
"configuration":
@ -111,6 +121,12 @@
}
},
"texture":
{
"coin": ["resource/coin/coin-0.png"],
"flame": ["resource/flame/flame-1.png", "resource/flame/flame-2.png"]
},
"curve":
{
"bezier resolution": 60
@ -276,15 +292,21 @@
"name": "RESUME ARCADE",
"time limit": 30.0,
"checkpoint addition": 10.0,
"checkpoint addition advanced": 15.0,
"level addition": 20.0,
"bank bonus": 5.0
"level addition advanced": 30.0,
"bank bonus": 5.0,
"advanced": 14
},
{
"name": "ARCADE",
"time limit": 30.0,
"checkpoint addition": 10.0,
"checkpoint addition advanced": 15.0,
"level addition": 20.0,
"bank bonus": 5.0
"level addition advanced": 30.0,
"bank bonus": 5.0,
"advanced": 14
}
],
@ -311,18 +333,25 @@
"main": "resource/azu main theme_amp.ogg",
"menu": "resource/azu menu music_amp.ogg",
"take": "resource/Coin_.wav",
"checkpoint": "resource/arrive_0.wav"
"checkpoint": "resource/arrive_0.wav",
"bong": "resource/bong.ogg"
},
"volume": {
"restart": 0.5,
"restart": 0.3,
"teleport": 0.7,
"walk": 0.5,
"reverse": 0.46,
"walk": 0.55,
"reverse": 0.5,
"main": 1.0,
"menu": 1.0,
"take": 0.5,
"checkpoint": 0.5
},
"fade": 2.0
},
"ending":
{
"coin range": [-0.5, 0.5],
"coin y": 0.07
}
}

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -4,6 +4,7 @@
<head>
<meta charset="utf-8">
<link rel="icon" href="favicon.ico" />
<style>
@import url("../../../lib/fonts/rounded-mplus/2m-medium/style.css");
@import url("../../../lib/fonts/rounded-mplus/2p-thin/style.css ");

2
lib/sb

@ -1 +1 @@
Subproject commit 0a42df9a0029164448d600ec8270268382c4a438
Subproject commit a5f55be257eee0886c4fdd9287c9d91c9de22d8d

0
resource/BPmono-LICENSE.txt Executable file → Normal file
View File

BIN
resource/beef/cake2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
resource/beef/cake3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
resource/beef/cake4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
resource/bong.ogg Normal file

Binary file not shown.

BIN
resource/buffalo/cake2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
resource/buffalo/cake3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
resource/buffalo/cake4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
resource/cake/cake2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
resource/cake/cake3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
resource/cake/cake4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
resource/down_arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
resource/fish/fish-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

BIN
resource/flame/flame-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

View File

@ -423,7 +423,7 @@
["fish", 0.41, -0.03, 80.0, 4.73],
["slicer", 0.135, 2.5, 30.0],
["slicer", 0.57, 5.0, 100.0],
["slicer", 0.57, 5.0, 100.0, 0.6, 2.65],
["projector", [64, 120, 0.0], 5.5, 6.5, 0.6],
["projector", [800, 120, 0.0], 5.5, 6.5, 0.6],
@ -432,8 +432,8 @@
],
"checkpoints": [
{
"position": 0.245,
"angle": 4.4
"position": 0.34,
"angle": 5.2
}
]
},
@ -482,7 +482,7 @@
[ 432.0, 34.0], [ 784.0, 98.0], [ 738.0, 372.0], [ 432.0, 451.0], [ 95.0, 367.0], [ 105.0, 86.0],
[ 432.0, 34.0], [ 964.25, 49.13], [ 794.44, 464.65], [ 432.0, 451.0], [ 1.8, 450.48], [ -61.82, 30.06],
[ 432.0, 34.0], [ 1065.74, 32.26], [ 896.07, 452.75], [ 432.0, 451.0], [ 4.34, 466.9], [ -228.42, 53.41],
[ 432.0, 34.0], [ 432.0, 34.0], [ 432.0, 261.0], [ 432.0, 261.0]
[ 432.0, 34.0], [ 432.0, 34.0], [ 433.0, 261.0], [ 433.0, 261.0]
],
"enemies": [
["projector", [64, 64, 0.0], 6.00000, 7.0, 0.6],
@ -515,7 +515,7 @@
["fish", 0.6, 0.025, 120, 0.0],
["fish", 0.499, 0.025, 40, 0.0],
["fish", 0.499, 0.025, 60, 0.79],
["fish", 0.499, 0.025, 60, 0.79, 3.14],
["fish", 0.499, 0.025, 80, 1.57],
["fish", 0.499, 0.025, 100, 2.36],
["fish", 0.499, 0.025, 120, 3.14],
@ -589,8 +589,8 @@
],
"checkpoints": [
{
"position": 0.528,
"angle": 0.5
"position": 0.6661,
"angle": 0.0
}
]
},
@ -633,7 +633,7 @@
["slicer", 0.83, 8.00000, 90.50000],
["slicer", 0.426, 5.00000, 70.50000],
["slicer", 0.43, 5.00000, 70.50000],
["slicer", 0.434, 5.00000, 70.50000],
["slicer", 0.434, 5.00000, 70.50000, 0.075, 3.14],
["slicer", 0.4535, 2.00000, 32.5],
["fish", 0.4535, -0.1, 30.00000, 0.0],
["fish", 0.95, -0.05, 25.00000, 0.0],
@ -791,7 +791,7 @@
["flame", [0.8175, 0.59, 0.0], 0.15, 1.5707963267948966, 2.3],
["flame", [1.17, 0.59, 0.0], 0.15, -1.5707963267948966, 2.3],
["flame", [1.15, 0.2, 0.0], 0.15, -1.5707963267948966, 2.3],
["flame", [1.15, 0.2, 0.0], 0.15, -1.5707963267948966, 2.3, 0.5, 3.14],
["flame", [0.9875, -0.075, 0.0], 0.15, 3.141592653589793, 2.3],
@ -833,7 +833,7 @@
"checkpoints": [
{
"position": 0.4175,
"position": 0.636,
"angle": 4.0
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

BIN
resource/up_arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -46,6 +46,7 @@ Cakefoot::Cakefoot()
{"quest time", 0.0f},
{"quest coin", false},
{"quest bank", 0},
{"quest best", 0.0},
{"arcade level", 1},
{"arcade checkpoint", 0.0f},
{"arcade difficulty", 0},
@ -173,6 +174,10 @@ Cakefoot::Cakefoot()
/* Initialize scoreboard content */
refresh_scoreboard();
/* Start tracking hue rotation */
shift_hue_animation.frame_length(configuration()("display", "hue shift frequency"));
shift_hue_animation.play();
if (!use_play_button)
{
/* Load title screen */
@ -809,6 +814,20 @@ void Cakefoot::set_up_hud()
qr_code.texture(configuration()("display", "qr texture").get<std::string>());
qr_code.translate(configuration()("display", "qr translation"));
qr_code.scale(configuration()("display", "qr scale"));
/* Style the quest best time indicator */
label.at("quest best").foreground(configuration()("display", "quest best foreground").get<glm::vec4>());
label.at("quest best").background(configuration()("display", "quest best background").get<glm::vec4>());
label.at("quest best").untransform();
label.at("quest best").translate(configuration()("display", "quest best translation"));
label.at("quest best").scale(configuration()("display", "quest best scale"));
label.at("quest best").dimensions(configuration()("display", "quest best dimensions"));
if (configuration()("progress", "quest best") > 0.0f)
{
label.at("quest best").content(configuration()("display", "quest best text").get<std::string>() +
format_clock(configuration()("progress", "quest best")));
}
}
void Cakefoot::load_vbo()
@ -1033,10 +1052,23 @@ void Cakefoot::load_level(int index)
}
}
/* If the level is the end screen, reset the player's current level to the beginning. 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 (static_cast<std::size_t>(index) == configuration()("levels").size() - 1)
/* 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 (end_screen())
{
/* Load ending coins */
std::string coin_texture;
ending_coins.clear();
glm::vec2 coin_range = configuration()("ending", "coin range").get<glm::vec2>();
float coin_step = (coin_range.y - coin_range.x) / (bank() - 1);
for (std::size_t ii = 0; ii < bank(); ii++)
{
Flame coin {
field, glm::vec3{coin_range.x + coin_step * ii, configuration()("ending", "coin y").get<float>(), 0.0f}, 0.0f, 0.0f, -1.0f, bank() < max_bank()
};
ending_coins.push_back(coin);
}
configuration()["progress"]["current level"] = 1;
/* Update save progress */
@ -1061,12 +1093,23 @@ void Cakefoot::load_level(int index)
}
/* In quest mode, unlock higher difficulty at the end of the game: increase difficulty if there is a higher difficulty than the current one.
* Then reset the max level because it will now refer to max level of the next difficulty. */
if (quest() && profile_index < static_cast<int>(configuration()("character", "profile").size()) - 1)
* Then reset the max level because it will now refer to max level of the next difficulty. Save the time if it is better than the existing
* record. */
if (quest())
{
configuration()["progress"]["max difficulty"] = ++profile_index;
configuration()["progress"]["max level"] = 1;
character.profile(configuration()("character", "profile", profile_index, "name"));
if (profile_index < static_cast<int>(configuration()("character", "profile").size()) - 1)
{
configuration()["progress"]["max difficulty"] = ++profile_index;
configuration()["progress"]["max level"] = 1;
character.profile(configuration()("character", "profile", profile_index, "name"));
}
float best = configuration()("progress", "quest best");
if (best <= 0.0f || run_timer.elapsed() < best)
{
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()));
}
}
write_progress();
@ -1133,7 +1176,7 @@ void Cakefoot::load_level(int index)
}
/* If it's the title or end level, stop the run timer. */
if (index == 0 || static_cast<std::size_t>(index) == _configuration("levels").size() - 1)
if (index == 0 || end_screen())
{
run_timer.off();
@ -1304,18 +1347,25 @@ float Cakefoot::limit() const
{
const nlohmann::json& levels {configuration()("levels")};
for (auto level = levels.begin() + 1; level != levels.begin() + level_index + 1; level++) {
std::string level_addition = "level addition";
std::string checkpoint_addition = "checkpoint addition";
if (level >= levels.begin() + configuration()("challenge", challenge_index, "advanced").get<int>())
{
level_addition += " advanced";
checkpoint_addition += " advanced";
}
if (level < levels.begin() + level_index)
{
limit += configuration()("challenge", challenge_index, "level addition").get<float>();
limit += configuration()("challenge", challenge_index, level_addition).get<float>();
if (level->contains("checkpoints"))
limit += configuration()("challenge", challenge_index, "checkpoint addition").get<float>() * level->at("checkpoints").size();
limit += configuration()("challenge", challenge_index, checkpoint_addition).get<float>() * level->at("checkpoints").size();
}
else if (level->contains("checkpoints"))
{
for (const nlohmann::json& checkpoint : level->at("checkpoints"))
{
if (checkpoint.at("position").get<float>() <= character.checkpoint())
limit += configuration()("challenge", challenge_index, "checkpoint addition").get<float>();
limit += configuration()("challenge", challenge_index, checkpoint_addition).get<float>();
} } }
/* Add bank bonus */
@ -1342,11 +1392,31 @@ bool Cakefoot::level_select() const
return !arcade() && configuration()("challenge", challenge_index, "name") == "LEVEL SELECT";
}
bool Cakefoot::end_screen(std::optional<std::size_t> index) const
{
if (!index.has_value())
{
index = level_index;
}
return static_cast<std::size_t>(index.value()) == _configuration("levels").size() - 1;
}
bool Cakefoot::resuming() const
{
return configuration()("challenge", challenge_index, "name") == "RESUME QUEST" || configuration()("challenge", challenge_index, "name") == "RESUME ARCADE";
}
std::size_t Cakefoot::bank() const
{
std::string mode = quest() ? "quest bank" : "arcade bank";
return configuration()("progress", mode).get<std::size_t>();
}
std::size_t Cakefoot::max_bank() const
{
return configuration()("levels").size() - 2;
}
void Cakefoot::collect_coin(bool add_to_bank)
{
if (!coin_collected)
@ -1394,6 +1464,11 @@ void Cakefoot::submit_score()
load_level(0);
}
void Cakefoot::shift_hue()
{
rotating_hue.shift_hue(configuration()("display", "hue shift").get<float>());
}
std::string Cakefoot::format_clock(float amount)
{
int minutes = int(amount) / 60;
@ -1739,6 +1814,7 @@ void Cakefoot::update(float timestamp)
/* Keep animation time updated */
game_over_animation.update(timestamp);
submit_score_animation.update(timestamp);
shift_hue_animation.update(timestamp);
/* Transformation for looking at the center of the field of play from the camera position. */
view = glm::lookAt(camera_position, {0.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f});
@ -1755,6 +1831,9 @@ void Cakefoot::update(float timestamp)
}
projection = glm::perspective(fov, window_box().aspect(), 0.1f, 100.0f);
/* Transformation that applies the rotation state of the entire scene */
glm::mat4 rotation_matrix = glm::rotate(glm::mat4(1), rotation.x, {0.0f, 1.0f, 0.0f}) * glm::rotate(glm::mat4(1), rotation.y, {1.0f, 0.0f, 0.0f});
/* Clear screen to configured color */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -1786,7 +1865,8 @@ void Cakefoot::update(float timestamp)
}
/* End run if there is a time limit and the time limit is passed. Queue end level to load after a delay. */
bool game_over_active = arcade() && run_timer.elapsed() > extended_limit && static_cast<std::size_t>(level_index) < configuration()("levels").size() - 1;
bool game_over_active = arcade() && level_index > 0 &&
run_timer.elapsed() > extended_limit && static_cast<std::size_t>(level_index) < configuration()("levels").size() - 1;
if (game_over_active && !game_over_animation.playing())
{
/* Play once with a delay to let the game over screen display temporarily before the end level is loaded. */
@ -1837,9 +1917,9 @@ void Cakefoot::update(float timestamp)
/* Collect any previously taken coins */
collect_coin();
/* Load next level */
/* Load next level, or reload current level if in level select mode */
audio.at("teleport").play();
load_level(level_index + 1);
load_level(level_select() ? level_index : level_index + 1);
}
}
else
@ -1888,6 +1968,25 @@ void Cakefoot::update(float timestamp)
}
}
/* Collide with ending screen coins */
if (end_screen())
{
ending_coins.erase(
std::remove_if(
ending_coins.begin(),
ending_coins.end(),
[&](Flame& coin)
{
if (coin.collide(character.box(), character.sprite(), {-1.0f, -1.0f, -1.0f}, {1.0f, 1.0f, 1.0f}))
{
coin.camo() ? audio.at("take").play() : audio.at("bong").play();
return true;
}
return false;
}),
ending_coins.end());
}
/* Respawn */
if (!character.resting() && enemy_collision)
{
@ -1905,9 +2004,6 @@ void Cakefoot::update(float timestamp)
}
}
/* Transformation that applies the rotation state of the entire scene */
glm::mat4 rotation_matrix = glm::rotate(glm::mat4(1), rotation.x, {0.0f, 1.0f, 0.0f}) * glm::rotate(glm::mat4(1), rotation.y, {1.0f, 0.0f, 0.0f});
/* Plane position vertices will be used for everything before the curve */
sb::Plane::position->bind("vertex_position", shader_program);
@ -1920,6 +2016,7 @@ void Cakefoot::update(float timestamp)
playing_field.attributes("color")->disable();
/* Reset color addition, and draw curve. */
glUniform4fv(uniform["color addition"], 1, &glm::vec4(0)[0]);
glm::mat4 vp = projection * view * rotation_matrix;
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &vp[0][0]);
curve().color.bind("vertex_color", shader_program);
@ -1960,11 +2057,20 @@ void Cakefoot::update(float timestamp)
/* Draw enemies */
for (auto& enemy : enemies)
{
enemy->draw(uniform["mvp"], view * rotation_matrix, projection, uniform["texture enabled"]);
enemy->draw(uniform.at("mvp"), view * rotation_matrix, projection, uniform.at("texture enabled"), rotating_hue, uniform.at("color addition"));
}
/* Draw cake */
character.draw(curve(), uniform["mvp"], view * rotation_matrix, projection, uniform["texture enabled"]);
character.draw(curve(), uniform.at("mvp"), view * rotation_matrix, projection, uniform.at("texture enabled"));
/* Draw end screen coins */
if (end_screen())
{
for (Flame& coin : ending_coins)
{
coin.draw(uniform.at("mvp"), view * rotation_matrix, projection, uniform.at("texture enabled"), rotating_hue, uniform.at("color addition"));
}
}
/* Check if any buttons should be disabled */
bool profile_spinner_enabled = configuration()("progress", "max difficulty") > 0 && !resuming();
@ -2011,10 +2117,7 @@ void Cakefoot::update(float timestamp)
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label_transformation[0][0]);
label.at(name).enable();
glDrawArrays(GL_TRIANGLES, 0, label.at(name).attributes("position")->count());
}
}
}
}
} } } }
else
{
if (unpaused_timer)
@ -2026,8 +2129,7 @@ void Cakefoot::update(float timestamp)
for (std::string name : {"resume", "reset"})
{
button.at(name).draw(uniform["mvp"], view, projection, uniform["texture enabled"]);
}
}
} }
/* Draw name entry */
if (static_cast<std::size_t>(level_index) == configuration()("levels").size() - 1 && arcade())
@ -2037,9 +2139,7 @@ void Cakefoot::update(float timestamp)
"name " + std::to_string(name_entry_index + 1) + " decrement"})
{
button.at(button_name).draw(uniform.at("mvp"), view, projection, uniform.at("texture enabled"));
}
}
}
} } }
/* Draw the clock */
float amount;
@ -2100,10 +2200,9 @@ void Cakefoot::update(float timestamp)
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label_transformation[0][0]);
label.at(name).enable();
glDrawArrays(GL_TRIANGLES, 0, label.at(name).attributes("position")->count());
}
}
} }
/* Draw scoreboard and QR on title screen */
/* Draw scoreboard, QR, and quest best on title screen */
if (level_index == 0)
{
/* Only draw scoreboard if arcade mode is selected */
@ -2115,6 +2214,14 @@ void Cakefoot::update(float timestamp)
scoreboard.enable();
glDrawArrays(GL_TRIANGLES, 0, scoreboard.attributes("position")->count());
}
else if (quest() && configuration()("progress", "quest best") > 0.0f)
{
label.at("quest best").texture(0).bind();
label_transformation = projection * view * label.at("quest best").transformation();
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &label_transformation[0][0]);
label.at("quest best").enable();
glDrawArrays(GL_TRIANGLES, 0, label.at("quest best").attributes("position")->count());
}
/* Draw QR */
qr_code.draw(uniform.at("mvp"), view, projection, uniform.at("texture enabled"));
@ -2138,9 +2245,7 @@ void Cakefoot::update(float timestamp)
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());
}
}
}
} } }
else
{
/* Draw the play button if it is enabled and hasn't been pressed yet */

View File

@ -235,7 +235,8 @@ private:
{"view", sb::Text(font())},
{"game over", sb::Text(font())},
{"arcade rank", sb::Text(large_font)},
{"arcade distance", sb::Text(large_font)}
{"arcade distance", sb::Text(large_font)},
{"quest best", sb::Text(font())}
};
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;
@ -253,6 +254,8 @@ private:
ArcadeScores::Score arcade_score;
std::string name_entry = "AAA";
sb::Text scoreboard {large_font};
std::vector<Flame> ending_coins;
sb::Color rotating_hue {128, 0, 0, 0};
/*!
* Load sound effects and music into objects that can be used by the SDL mixer library. Use chunk objects for background music instead of
@ -356,11 +359,27 @@ private:
*/
bool level_select() const;
/*!
* @param index optional level index
* @return True if level is currently the end screen
*/
bool end_screen(std::optional<std::size_t> index = std::nullopt) const;
/*!
* @return True if resume quest or resume arcade is the current mode
*/
bool resuming() const;
/*!
* @return Count of coins collected
*/
std::size_t bank() const;
/*!
* @return Maximum number of coins that can be collected
*/
std::size_t max_bank() const;
inline bool skip_resume_quest()
{
return configuration()("challenge", challenge_index, "name") == "RESUME QUEST" && configuration()("progress", "quest level") == 1 &&
@ -402,6 +421,14 @@ private:
/* Can be used to time out the name entry screen */
Animation submit_score_animation {sb::Animation(std::bind(&Cakefoot::submit_score, this))};
/*!
* Shift the hue of the global sb::Color object that tracks the hue shift by the given amount in the configuration.
*/
void shift_hue();
/* Shift the hue by the configured amount once per configured amount of seconds */
Animation shift_hue_animation {sb::Animation(std::bind(&Cakefoot::shift_hue, this))};
/*!
* Get the arcade time as the amount of time remaining before the limit is reached.
*

View File

@ -264,9 +264,7 @@ void Character::draw(const Curve& curve, GLuint transformation_uniform, const gl
/* Reflect if walking left */
if (curve[next_point_index].x < position.x)
{
glm::mat4 reflection = glm::mat4{1.0f};
reflection[0][0] = -1.0f;
_sprite.transform(reflection);
_sprite.transform(glm::scale(glm::vec3{-1.0f, 1.0f, 1.0f}));
}
_sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);

View File

@ -52,6 +52,18 @@ void Enemy::collect_coin()
}
}
void Enemy::draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform)
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
if (_coin.has_value() && !coin_taken() && !coin_collected)
{
glUniform4fv(color_addition_uniform, 1, &rotating_hue.normal()[0]);
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
glUniform4fv(color_addition_uniform, 1, &glm::vec4(0)[0]);
}
}
Slicer::Slicer(const Curve& curve, float relative, float speed, float stray) : curve(curve), relative(relative), speed(speed), stray(stray)
{
position = start();
@ -121,15 +133,6 @@ void Slicer::update(const sb::Timer& timer)
sprite.translate(curve.get().wrap(position));
}
void Slicer::draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform)
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
if (_coin.has_value() && !coin_taken() && !coin_collected)
{
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
}
}
glm::vec3 Slicer::center() const
{
return curve.get().relative(relative);
@ -232,15 +235,6 @@ void Fish::update(const sb::Timer& timer)
box.center(position);
}
void Fish::draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform)
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
if (_coin.has_value() && !coin_taken() && !coin_collected)
{
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
}
}
glm::vec3 Fish::center() const
{
return curve.get().relative(relative);
@ -303,13 +297,16 @@ void Projectile::update(const sb::Timer& timer)
box.center(position);
}
void Projectile::draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform)
void Projectile::draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform)
{
if (_coin.has_value())
{
if (!coin_taken() && !coin_collected)
{
glUniform4fv(color_addition_uniform, 1, &rotating_hue.normal()[0]);
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
glUniform4fv(color_addition_uniform, 1, &glm::vec4(0)[0]);
}
}
else
@ -386,15 +383,6 @@ void Projector::update(const sb::Timer& timer)
}
}
void Projector::draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform)
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
for (Projectile& projectile : projectiles)
{
projectile.draw(transformation_uniform, view, projection, texture_flag_uniform);
}
}
bool Projector::collide(const sb::Box& box, const sb::Sprite& sprite, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
{
for (const Projectile& projectile : projectiles)
@ -463,16 +451,33 @@ void Projector::release()
}
}
Flame::Flame(const sb::Box field, const glm::vec3& position, float speed, float angle, float mirror_interval) :
field(field), position(position), speed(speed), angle(angle)
void Projector::draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform)
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
for (Projectile& projectile : projectiles)
{
projectile.draw(transformation_uniform, view, projection, texture_flag_uniform, rotating_hue, color_addition_uniform);
}
}
Flame::Flame(const sb::Box& field, const glm::vec3& position, float speed, float angle, float mirror_interval, bool camo) :
field(field), position(position), speed(speed), angle(angle), _camo(camo)
{
/* Save initial values */
initial_angle = angle;
initial_position = position;
/* Set up animation */
sprite.texture("resource/flame/flame-1.png");
sprite.texture("resource/flame/flame-2.png");
if (!camo)
{
sprite.texture("resource/flame/flame-1.png");
sprite.texture("resource/flame/flame-2.png");
}
else
{
sprite.texture("resource/coin/coin-0.png");
}
sprite.frames.frame_length(0.3f);
sprite.frames.play();
@ -517,15 +522,6 @@ void Flame::update(const sb::Timer& timer)
sprite.translate(sb::wrap_point(position, glm::vec3{field.left(), field.bottom(), -1.0f}, glm::vec3{field.right(), field.top(), 1.0f}));
}
void Flame::draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform)
{
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
if (_coin.has_value() && !coin_taken() && !coin_collected)
{
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
}
}
void Flame::mirror()
{
/* Reset the flame position and angle at the end of a cycle so overflow in time and movement don't begin to accumulate and desync the mirror cycle. */

View File

@ -17,6 +17,7 @@ class Enemy
protected:
sb::Sprite sprite;
sb::Box box;
/* Enemies have an optional challenge coin defined by different parameters depending on the enemy type. */
@ -63,8 +64,11 @@ public:
* @param view view transformation matrix
* @param projection projection transformation matrix
* @param texture_flag_uniform uniform location in the shader program of the boolean that turns texture drawing on or off
* @param rotating_hue color of the hue rotation effect
* @param color_addition_uniform uniform location in the shader program of the RGBA value for color addition effect
*/
virtual void draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform) = 0;
virtual void draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform);
/*!
* Check whether the enemy collides with the given ::Sprite positioned at the given ::Box. The sprite object may be used to perform
@ -108,7 +112,6 @@ private:
std::reference_wrapper<const Curve> curve;
float relative = 0.0f, speed = 0.0f, stray = 0.0f, coin_radius = 0.0f, coin_angle = 0.0f;
bool toward_end = true;
sb::Sprite sprite;
/*!
*/
@ -153,10 +156,6 @@ public:
/*!
*/
void update(const sb::Timer& timer);
/*!
*/
void draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform);
};
/*!
@ -169,7 +168,6 @@ private:
std::reference_wrapper<const Curve> curve;
float relative = 0.0f, speed = 0.0f, radius = 0.0f, angle = 0.0f, offset = 0.0f, coin_angle = 0.0f, coin_radius = 0.0f;
sb::Sprite sprite;
std::optional<float> first_update_time;
/*!
@ -223,10 +221,6 @@ public:
*/
void update(const sb::Timer& timer);
/*!
*/
void draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform);
};
/*!
@ -240,7 +234,6 @@ private:
glm::vec3 position = glm::vec3{0.0f}, target = glm::vec3{0.0f};
float speed = 0.0f, angle = 0.0f;
sb::Sprite sprite;
public:
@ -268,7 +261,8 @@ public:
/*!
*/
void draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform);
void draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform);
/*!
* @return true if the projectile has left the clip space
@ -294,7 +288,6 @@ private:
glm::vec3 position = glm::vec3{0.0f};
float speed = 0.0f, rate = 0.0f;
float release_delay;
sb::Sprite sprite;
sb::Animation animation_charge = sb::Animation(std::bind(&Projector::charge, this)),
animation_release = sb::Animation(std::bind(&Projector::release, this));
std::vector<Projectile> projectiles;
@ -348,7 +341,8 @@ public:
/*!
*/
void draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform);
void draw(GLuint transformation_uniform, const glm::mat4& view, const glm::mat4& projection, GLuint texture_flag_uniform,
const sb::Color& rotating_hue, GLuint color_addition_uniform);
/*!
* Check if projectiles collide.
@ -368,12 +362,11 @@ class Flame : public Enemy
private:
const sb::Box field;
sb::Box field;
glm::vec3 position = glm::vec3{0.0f}, initial_position = glm::vec3{0.0f};
float speed = 0.0f, angle = 0.0f, coin_angle = 0.0f, coin_radius = 0.0f, initial_angle = 0.0f;
sb::Sprite sprite;
sb::Animation animation_mirror = sb::Animation(std::bind(&Flame::mirror, this));
bool mirrored = false;
bool mirrored = false, _camo = false;
/*!
* Rotate direction 180 degrees.
@ -393,8 +386,9 @@ public:
* @param speed distance in world coordinates the flame will travel per second
* @param angle direction the flame will travel
* @param mirror_interval optional interval in seconds the direction should mirror, negative values turn off mirroring
* @param camo disguise the flame as a coin
*/
Flame(const sb::Box field, const glm::vec3& position, float speed, float angle, float mirror_interval = -1.0f);
Flame(const sb::Box& field, const glm::vec3& position, float speed, float angle, float mirror_interval = -1.0f, bool camo = false);
/*!
* Add a challenge coin to this flame enemy. The sprite will be owned, moved, and drawn by this object.
@ -412,12 +406,17 @@ public:
*/
bool collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const;
inline void camo(bool state)
{
_camo = state;
}
bool camo() const
{
return _camo;
}
/*!
*/
void update(const sb::Timer& timer);
/*!
*/
void draw(GLuint transformation_uniform, const glm::mat4 view, const glm::mat4 projection, GLuint texture_flag_uniform);
};

View File

@ -25,6 +25,7 @@ void main()
* at its start point. Something related to reading the texel at the boundary versus the center. But adjusting the UV into the center of the
* texels causes pixels at the borders to display incorrectly. Maybe related to half pixel adjustment https://gamedev.stackexchange.com/a/49585
*/
output_color = float(texture_enabled) * texture(model_texture, fragment_uv + 0.00001);
// output_color = float(texture_enabled) * texture(model_texture, fragment_uv + 0.00001);
output_color = float(texture_enabled) * (texture(model_texture, fragment_uv) + color_addition);
output_color += (1.0 - float(texture_enabled)) * (ex_color + color_addition);
}