- added run clock

- a few new levels
- new wave enemy
- added world background colors
- added color add blend to shader
- new offset parameter for fish
- script for converting SVG file to level curve
This commit is contained in:
ohsqueezy 2023-09-07 22:17:56 -04:00
parent ca67f4ea78
commit bd36c762eb
12 changed files with 1322 additions and 676 deletions

View File

@ -1,95 +1,122 @@
{
"display":
{
"dimensions": [864, 486],
"framerate": 60,
"title": "Microsoft Windows 11 Home Edition",
"debug": false,
"render driver": "opengl",
"show-cursor": true,
"fluid resize": true,
"fps": true,
"clear color": [0.0, 0.0, 0.0, 1.0],
"playing field aspect": 1.7777777,
"playing field color": [0.2, 0.2, 0.2, 1.0],
"fps indicator scale": 0.018,
"checkpoint distance": 0.1
},
"display":
{
"dimensions": [864, 486],
"title": "Microsoft Windows 11 Home Edition",
"debug": false,
"render driver": "opengl",
"show-cursor": true,
"fluid resize": true,
"fps": false,
"clear color": [0.0, 0.0, 0.0, 1.0],
"playing field aspect": 1.7777777,
"playing field color": [0.2, 0.2, 0.2, 1.0],
"fps indicator scale": 0.018,
"checkpoint distance": 0.1,
"clock hud scale": 0.07,
"clock hud translation": [0.9, 0.92],
"clock hud large scale": 0.35,
"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],
"level hud translation": [-0.92, 0.92],
"level hud foreground": [255.0, 255.0, 255.0, 255.0],
"level hud background": [0.0, 0.0, 0.0, 60.0]
},
"configuration":
{
"auto-refresh": true,
"auto-refresh-interval": 1.0
},
"configuration":
{
"auto-refresh": true,
"auto-refresh-interval": 1.0
},
"recording":
{
"screenshot-directory": "local/screenshots",
"video-directory": "local/video",
"enabled": true,
"write-mp4": true,
"video-frame-length": 33.333,
"max-video-memory": 2000,
"mp4-pixel-format": "yuv420p"
},
"recording":
{
"screenshot-directory": "local/screenshots",
"video-directory": "local/video",
"enabled": true,
"write-mp4": true,
"video-frame-length": 33.333,
"max-video-memory": 2000,
"mp4-pixel-format": "yuv420p"
},
"input":
{
"suppress-any-key-on-mods": true
},
"input":
{
"suppress-any-key-on-mods": true
},
"keys":
{
"fps": ["CTRL", "f"],
"skip forward": ["CTRL", "SHIFT", "right"],
"skip backward": ["CTRL", "SHIFT", "left"],
"memory": ["CTRL", "SHIFT", "m"],
"coords": ["CTRL", "SHIFT", "c"]
},
"keys":
{
"fps": ["CTRL", "f"],
"skip forward": ["CTRL", "SHIFT", "right"],
"skip backward": ["CTRL", "SHIFT", "left"],
"memory": ["CTRL", "SHIFT", "m"],
"coords": ["CTRL", "SHIFT", "c"]
},
"log":
{
"enabled": true,
"output-directory": "/var/log/sb/",
"debug-to-stdout": true,
"debug-to-file": true,
"info-file-name": "cakefoot_info.log",
"debug-file-name": "cakefoot_debug.log",
"verbose to stdout": false
},
"log":
{
"enabled": true,
"output-directory": "/var/log/sb/",
"debug-to-stdout": true,
"debug-to-file": true,
"info-file-name": "cakefoot_info.log",
"debug-file-name": "cakefoot_debug.log",
"verbose to stdout": false
},
"curve":
{
"bezier resolution": 60
},
"curve":
{
"bezier resolution": 60
},
"character":
{
"cake-speed-increment": 0.0514398,
"cake-speed-decrement": 0.0257196,
"cake-max-speed": 0.72016458,
"cake-min-speed": -0.4115226,
"cake-increment-mod": 0.125,
"cake-decrement-mod": 0.1,
"hitbox": 0.7
},
"character":
{
"cake-speed-increment": 0.0514398,
"cake-speed-decrement": 0.0257196,
"cake-max-speed": 0.72016458,
"cake-min-speed": -0.4115226,
"cake-increment-mod": 0.125,
"cake-decrement-mod": 0.1,
"hitbox": 0.7
},
"button":
{
"font": "BPmono.ttf",
"font size": 24,
"pause texture": "resource/pause.png",
"pause translation": [1.65, -0.85],
"pause scale": 0.08,
"text size": [240.0, 40.0],
"text scale": 0.5,
"text foreground": [200.0, 200.0, 200.0, 255.0],
"text background": [60.0, 60.0, 60.0, 190.0],
"start text": "PLAY",
"start translation": [0.0, -0.5],
"resume text": "RESUME",
"resume translation": [0.0, 0.25],
"reset text": "RESET",
"reset translation": [0.0, -0.25]
}
"button":
{
"font": "BPmono.ttf",
"font size": 24,
"pause texture": "resource/pause.png",
"pause translation": [1.65, -0.85],
"pause scale": 0.08,
"text size": [240.0, 40.0],
"text scale": 0.5,
"text foreground": [200.0, 200.0, 200.0, 255.0],
"text background": [60.0, 60.0, 60.0, 190.0],
"start text": "PLAY",
"start translation": [0.0, -0.5],
"resume text": "RESUME",
"resume translation": [0.0, 0.25],
"reset text": "RESET",
"reset translation": [0.0, -0.25]
},
"world": [
{
"start": 0,
"color": [0.2, 0.2, 0.2, 1.0]
},
{
"start": 6,
"color": [0.071, 0.161, 0.216, 1.0]
},
{
"start": 11,
"color": [0.151, 0.23, 0.12, 1.0]
},
{
"start": 17,
"color": [0.23, 0.12, 0.18, 1.0]
}
]
}

View File

@ -20,8 +20,9 @@
}
canvas
{
width: 100%;
max-width: 864px;
width: 90%;
/* width: 864px; */
/* max-width: 864px; */
max-height: 100%;
aspect-ratio: 16 / 9;
}
@ -35,7 +36,13 @@
<script>
var Module = {
/* Set Emscripten to use a canvas for display. */
canvas: document.getElementById("canvas")
canvas: document.getElementById("canvas"),
/* After Module is finished loading, turn off canvas image smoothing */
onRuntimeInitialized: function()
{
canvas.imageSmoothingEnabled = false;
}
};
</script>

2
lib/sb

@ -1 +1 @@
Subproject commit e453a62679ba7fbaa541fbed229d216bad00c786
Subproject commit fb76e08707a4fc5d80250a6d259f4b7fb36a94a8

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 220 B

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,6 @@
/* >> Cakefoot << */
/*
* CAKEFOOT by @ohsqueezy https://ohsqueezy.itch.io
*/
#if defined(__ANDROID__) || defined(ANDROID)
#include <android/asset_manager_jni.h>
@ -25,7 +27,6 @@ Cakefoot::Cakefoot()
/* Set up playing field, the plane that provides the background of the curve, character, and enemies */
sb::Plane playing_field_plane;
playing_field_plane.scale(glm::vec3{_configuration("display", "playing field aspect"), 1.0f, 1.0f});
playing_field_plane.attributes(sb::Attributes(std::vector(6, _configuration("display", "playing field color").get<glm::vec4>())), "color");
playing_field = sb::Sprite(playing_field_plane);
/* Open the configuration and load the curve data per level */
@ -83,6 +84,7 @@ Cakefoot::Cakefoot()
});
text_buttons["resume"].on_state_change([&](bool state){
unpaused_timer.on();
run_timer.on();
});
text_buttons["reset"].on_state_change([&](bool state){
sb::Delegate::post(reset_command_name, false);
@ -98,6 +100,7 @@ Cakefoot::Cakefoot()
pause_button = sb::Pad<>{pause_plane, configuration()("button", "pause translation"), configuration()("button", "pause scale"), 1.0f};
pause_button.on_state_change([&](bool state){
unpaused_timer.off();
run_timer.off();
});
/* Set up checkpoint on and off sprites */
@ -191,12 +194,16 @@ void Cakefoot::load_gl_context()
uniform["coordinate bound"] = glGetUniformLocation(shader_program, "coordinate_bound");
uniform["model texture"] = glGetUniformLocation(shader_program, "model_texture");
uniform["texture enabled"] = glGetUniformLocation(shader_program, "texture_enabled");
uniform["color addition"] = glGetUniformLocation(shader_program, "color_addition");
/* Set the active texture and uniform once at context load time because only one texture is used per draw */
glActiveTexture(GL_TEXTURE0);
glUniform1i(uniform["model texture"], 0);
sb::Log::gl_errors("after uniform locations");
/* Initialize color addition to zero. */
glUniform4f(uniform["color addition"], 0.0f, 0.0f, 0.0f, 0.0f);
/* Enable alpha rendering, disable depth test, set clear color */
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
@ -217,13 +224,12 @@ void Cakefoot::load_vbo()
/*!
* Fill VBO with attribute data:
*
* Postion, UV, and color vertices for a single sb::Plane, playing field colors, and curve.
* Postion, UV, and color vertices for a single sb::Plane, and curve.
*/
vbo.allocate(sb::Plane().size() + playing_field.attributes("color")->size() + curve_byte_count, GL_STATIC_DRAW);
vbo.add(*sb::Plane::position);
vbo.add(*sb::Plane::uv);
vbo.add(*sb::Plane::color);
vbo.add(*playing_field.attributes("color"));
for (Curve& curve : curves)
{
for (sb::Attributes& attr : curve.position)
@ -264,16 +270,17 @@ void Cakefoot::load_level(int index)
nlohmann::json enemies = configuration()("levels", index, "enemies");
for (std::size_t ii = 0; ii < enemies.size(); ii++)
{
std::string type = enemies[ii][0];
nlohmann::json enemy = enemies[ii];
std::string type = enemy[0];
if (type == "slicer")
{
std::shared_ptr<Slicer> slicer = std::make_shared<Slicer>(
curve(), enemies[ii][1].get<float>(), 2.0f * 25.0f * enemies[ii][2].get<float>() / 486.0f, 2.0f * enemies[ii][3].get<float>() / 486.0f);
curve(), enemy[1].get<float>(), 2.0f * 25.0f * enemy[2].get<float>() / 486.0f, 2.0f * enemy[3].get<float>() / 486.0f);
/* Add coin to slicer */
if (enemies[ii].size() > 4)
if (enemy.size() > 4)
{
slicer->coin(coin, enemies[ii][4], enemies[ii][5]);
slicer->coin(coin, enemy[4].get<float>(), enemy[5].get<float>());
}
this->enemies.push_back(slicer);
@ -281,16 +288,17 @@ void Cakefoot::load_level(int index)
else if (type == "fish")
{
std::shared_ptr<Fish> fish = std::make_shared<Fish>(
curve(), enemies[ii][1].get<float>(), 25.0f * enemies[ii][2].get<float>(), 2.0f * enemies[ii][3].get<float>() / 486.0f);
curve(), enemy[1].get<float>(), 25.0f * enemy[2].get<float>(), 2.0f * enemy[3].get<float>() / 486.0f,
enemy[4].get<float>());
/* Add coin to fish */
if (enemies[ii].size() > 5)
if (enemy.size() > 6)
{
fish->coin(coin, enemies[ii][4], enemies[ii][5]);
fish->coin(coin, enemy[5].get<float>(), enemy[6].get<float>());
}
else if (enemies[ii].size() > 4)
else if (enemy.size() > 5)
{
fish->coin(coin, enemies[ii][4]);
fish->coin(coin, enemy[5].get<float>());
}
this->enemies.push_back(fish);
@ -299,92 +307,145 @@ void Cakefoot::load_level(int index)
{
std::shared_ptr<Projector> projector = std::make_shared<Projector>(
character,
(glm::vec3{2.0f * 1.7777f, 2.0f, 1.0f} * enemies[ii][1].get<glm::vec3>() / glm::vec3{864.0, 486.0, 1.0} -
glm::vec3(1.77777, 1.0, 0.0)) * glm::vec3(1.0, -1.0, 0.0),
2.0f * 25.0f * enemies[ii][2].get<float>() / 486.0, enemies[ii][3].get<float>(), enemies[ii][4].get<float>());
(glm::vec3{2.0f * 1.7777f, 2.0f, 1.0f} * enemy[1].get<glm::fvec3>() / glm::vec3{864.0f, 486.0f, 1.0f} -
glm::vec3(1.77777f, 1.0f, 0.0f)) * glm::vec3(1.0f, -1.0f, 0.0f),
2.0f * 25.0f * enemy[2].get<float>() / 486.0, enemy[3].get<float>(), enemy[4].get<float>());
/* Add coin to projector */
if (enemies[ii].size() > 5)
if (enemy.size() > 5)
{
projector->coin(coin, enemies[ii][5]);
projector->coin(coin, enemy[5].get<float>());
}
this->enemies.push_back(projector);
}
else if (type == "flame")
{
std::shared_ptr<Flame> flame = std::make_shared<Flame>(field, enemies[ii][1], enemies[ii][2], enemies[ii][3], enemies[ii][4]);
std::shared_ptr<Flame> flame = std::make_shared<Flame>(
field, enemy[1].get<glm::fvec3>(), enemy[2].get<float>(), enemy[3].get<float>(), enemy[4].get<float>());
/* Add coin to flame */
if (enemies[ii].size() > 5)
if (enemy.size() > 5)
{
flame->coin(coin, enemies[ii][5], enemies[ii][6]);
flame->coin(coin, enemy[5], enemy[6]);
}
this->enemies.push_back(flame);
}
}
}
/* Enemies programmed to be added directly (not through the configuration). */
if (index == configuration()("pattern", "fire grid 1"))
{
float y = field.top();
glm::vec2 margin {0.59259f, 0.5f};
bool shift = false;
int count = 0;
while (y > field.bottom())
{
float x = field.left() + shift * margin.x / 2.0f;
while (x < field.right())
else if (type == "grid")
{
std::shared_ptr<Flame> flame = std::make_shared<Flame>(field, glm::vec3{x, y, 0.0f}, 0.41152263f, 25.0f * glm::quarter_pi<float>());
/* Add a challenge coin */
if (++count == 15)
/* Add a grid of flame objects */
float y = field.top();
glm::vec2 margin {0.59259f, 0.5f};
bool shift = false;
int count = 0;
while (y > field.bottom())
{
flame->coin(coin, margin.x / 2.0f, 1.57f);
}
float x = field.left() + shift * margin.x / 2.0f;
while (x < field.right())
{
std::shared_ptr<Flame> flame = std::make_shared<Flame>(field, glm::vec3{x, y, 0.0f}, 0.41152263f, 25.0f * glm::quarter_pi<float>());
this->enemies.push_back(flame);
x += margin.x;
/* Add a challenge coin */
if (++count == 15)
{
flame->coin(coin, margin.x / 2.0f, 1.57f);
}
this->enemies.push_back(flame);
x += margin.x;
}
shift = !shift;
y -= margin.y;
}
}
shift = !shift;
y -= margin.y;
}
}
else if (index == configuration()("pattern", "fire grid 2"))
{
float y = field.top();
glm::vec2 margin {0.59259f, 0.5f};
bool shift = false;
int count = 0;
while (y > field.bottom())
{
float x = field.left() + shift * margin.x / 2.0f;
while (x < field.right())
else if (type == "wave")
{
std::shared_ptr<Flame> flame = std::make_shared<Flame>(field, glm::vec3{x, y, 0.0f}, 0.30864198f, 25.0f * glm::quarter_pi<float>(), 4.0f);
/* Add a challenge coin */
if (++count == 19)
/* Add a wave of flame objects */
float y = 0.0f;
float speed = enemy[4].get<float>();
float amplitude = enemy[1].get<float>();
float period = enemy[2].get<float>();
float step = enemy[3].get<float>();
float shift = enemy[5].get<float>();
float mirror = -1.0f;
if (enemy.size() > 7)
{
flame->coin(coin, 0.3f, 3.14f);
mirror = enemy[7].get<float>();
}
glm::vec2 range {field.left(), field.right()};
if (enemy.size() > 6)
{
range = enemy[6].get<glm::vec2>();
}
float x = range.x;
while (x < range.y)
{
y = amplitude * glm::sin(period * x) + shift;
std::shared_ptr<Flame> flame = std::make_shared<Flame>(field, glm::vec3{x, y, 0.0f}, speed, 3.0f * glm::half_pi<float>(), mirror);
this->enemies.push_back(flame);
x += step;
}
this->enemies.push_back(flame);
x += margin.x;
}
shift = !shift;
y -= margin.y;
}
}
/* Reset and start the run timer if it's the first level */
if (index == 1)
/* If it's the first or last level, stop the run timer. Otherwise, turn it on. */
if (index == 0 || static_cast<std::size_t>(index) == _configuration("levels").size() - 1)
{
run_timer.off();
}
else
{
run_timer.reset();
run_timer.on();
/* If it's the first level, reset the clock. */
if (index == 1)
{
run_timer.reset();
}
}
/* Blow up the time if it's the last level. */
std::string clock_scale_key, clock_translation_key;
if (static_cast<std::size_t>(index) == _configuration("levels").size() - 1)
{
clock_scale_key = "clock hud large scale";
clock_translation_key = "clock hud large translation";
}
/* Otherwise, set it to standard HUD size. */
else
{
clock_scale_key = "clock hud scale";
clock_translation_key = "clock hud translation";
}
/* Style the clock */
clock_hud.foreground(_configuration("display", "clock hud foreground").get<glm::vec4>());
clock_hud.background(_configuration("display", "clock hud background").get<glm::vec4>());
clock_hud.untransform();
float clock_scale_modifier = _configuration("display", clock_scale_key);
glm::vec3 clock_scale {clock_scale_modifier, clock_scale_modifier * window_box().aspect() * 0.3f, 1.0f};
clock_hud.translate(_configuration("display", clock_translation_key));
clock_hud.scale(clock_scale);
/* Style the level indicator */
level_hud.foreground(_configuration("display", "level hud foreground").get<glm::vec4>());
level_hud.background(_configuration("display", "level hud background").get<glm::vec4>());
level_hud.untransform();
glm::vec3 level_hud_scale = glm::vec3{1.35f * clock_scale.y, clock_scale.y, 1.0f};
level_hud.translate(_configuration("display", "level hud translation"));
level_hud.scale(level_hud_scale);
/* Set the color background according to current world */
nlohmann::json world = configuration()("world");
for (std::size_t ii = 0; ii < world.size(); ii++)
{
if (ii == world.size() - 1 || world[ii + 1].at("start").get<int>() > index)
{
world_color = world[ii].at("color").get<glm::fvec4>();
break;
}
}
}
@ -530,6 +591,7 @@ void Cakefoot::respond(SDL_Event& event)
rotation = {0.0f, 0.0f};
load_level(0);
unpaused_timer.on();
run_timer.reset();
}
else if (sb::Delegate::compare(event, "reconfig"))
@ -613,7 +675,6 @@ void Cakefoot::update(float timestamp)
/* Update time in seconds the game has been running for, pass to the shader. */
on_timer.update(timestamp);
set_framerate(configuration()["display"]["framerate"]);
glUniform1f(uniform["time"], on_timer.elapsed());
/* Update other timers */
@ -699,14 +760,15 @@ void Cakefoot::update(float timestamp)
/* Clear screen to configured color */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* Draw playing field (background) */
/* Disable texture, set background color for the current world using the color addition uniform, and draw playing field (background) */
glUniform1i(uniform["texture enabled"], false);
glUniform4fv(uniform["color addition"], 1, &world_color[0]);
playing_field.attributes("color")->bind("vertex_color", shader_program);
playing_field.attributes("color")->enable();
playing_field.draw(uniform["mvp"], view * rotation_matrix, projection, uniform["texture enabled"]);
playing_field.attributes("color")->disable();
/* Draw curve */
glUniform1i(uniform["texture enabled"], false);
/* Reset color addition, and draw curve. */
glm::mat4 vp = projection * view * rotation_matrix;
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &vp[0][0]);
curve().color.bind("vertex_color", shader_program);
@ -774,6 +836,31 @@ void Cakefoot::update(float timestamp)
}
}
/* Draw the clock */
int minutes = int(run_timer.elapsed()) / 60;
float seconds = run_timer.elapsed() - (minutes * 60);
std::stringstream clock;
clock << std::setw(2) << std::setfill('0') << minutes << ":" << std::setw(4) << std::setprecision(1) << std::fixed << seconds;
clock_hud.content(clock.str());
sb::Plane::position->bind("vertex_position", shader_program);
glUniform1i(uniform["texture enabled"], true);
clock_hud.texture().bind();
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &clock_hud.transformation()[0][0]);
clock_hud.enable();
glDrawArrays(GL_TRIANGLES, 0, clock_hud.attributes("position")->count());
/* Draw the level indicator */
if (level_index > 0 && static_cast<std::size_t>(level_index) < _configuration("levels").size() - 1)
{
std::stringstream level_indicator;
level_indicator << std::setw(2) << std::setfill('0') << level_index << "/" << std::setw(2) << _configuration("levels").size() - 2;
level_hud.content(level_indicator.str());
level_hud.texture().bind();
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &level_hud.transformation()[0][0]);
level_hud.enable();
glDrawArrays(GL_TRIANGLES, 0, level_hud.attributes("position")->count());
}
/* Update FPS indicator display to the current FPS count and draw. */
if (configuration()["display"]["fps"])
{

View File

@ -9,6 +9,7 @@
#include <stdlib.h>
#include <string>
#include <iostream>
#include <iomanip>
#include <map>
#include <memory>
#include <functional>
@ -92,9 +93,10 @@ private:
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};
sb::Text fps{font()};
sb::Text fps{font()}, clock_hud{font()}, level_hud{font()};
std::vector<Curve> curves;
std::vector<std::shared_ptr<Enemy>> enemies;
glm::vec4 world_color {0.2f, 0.2f, 0.2f, 1.0f};
/*!
* Open configuration and load curve data into the object.

View File

@ -133,6 +133,13 @@ void Character::draw(const Curve& curve, GLuint transformation_uniform, const gl
glm::vec2 translation = sb::wrap_point(glm::vec3{_box.center(), 0.0f}, {-curve.aspect, -1.0f, -1.0f}, {curve.aspect, 1.0f, 1.0f});
_sprite.translate(glm::vec3{translation, 0.0f});
_sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
/* Draw hitbox */
// sb::Sprite hitbox;
// hitbox.scale(glm::vec3{_box.w / 2.0f});
// hitbox.translate(glm::vec3{translation, 0.0f});
// glUniform1i(texture_flag_uniform, false);
// hitbox.draw(transformation_uniform, view, projection, texture_flag_uniform);
}
const nlohmann::json& Character::entry(const std::string& suffix) const

View File

@ -155,7 +155,8 @@ bool Slicer::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::v
return false;
}
Fish::Fish(const Curve& curve, float relative, float speed, float radius) : curve(curve), relative(relative), speed(speed), radius(radius)
Fish::Fish(const Curve& curve, float relative, float speed, float radius, float offset) :
curve(curve), relative(relative), speed(speed), radius(radius), offset(offset)
{
sprite.texture("resource/fish/fish-1.png");
@ -180,7 +181,7 @@ void Fish::coin(const sb::Sprite& sprite, float radius, float angle)
void Fish::update(const sb::Timer& timer)
{
angle += timer.delta(speed);
glm::vec3 position = center() + glm::vec3{sb::angle_to_vector(angle, radius), 0.0f};
glm::vec3 position = center() + glm::vec3{sb::angle_to_vector(angle + offset, radius), 0.0f};
/* Update challenge coin position */
if (_coin.has_value() && !coin_taken() && !coin_collected)

View File

@ -168,7 +168,7 @@ class Fish : public Enemy
private:
std::reference_wrapper<const Curve> curve;
float relative = 0.0f, speed = 0.0f, radius = 0.0f, angle = 0.0f, coin_angle = 0.0f, coin_radius = 0.0f;
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;
/*!
@ -179,7 +179,8 @@ private:
public:
/*!
* Create a Fish object from relative position on a given curve, speed, and radius.
* Create a Fish object from relative position on a given curve, speed, and radius. Optionally, an offset angle can be added to start
* the fish that amount away on its circle.
*
* The speed is in radians to rotate per second.
*
@ -187,8 +188,9 @@ public:
* @param relative the position on the curve from 0 to 1
* @param speed radians to rotate per second
* @param radius distance from the center to rotate around
* @param offset angle amount in radians to offset start point
*/
Fish(const Curve& curve, float relative, float speed = 0.01010029f, float radius = 0.12345679f);
Fish(const Curve& curve, float relative, float speed = 0.01010029f, float radius = 0.12345679f, float offset = 0.0f);
/*!
* Add a challenge coin to this fish enemy. The coin will rotate on the same circle as the fish, offset by the given angle. The sprite will

79
src/extract_svg.py Normal file
View File

@ -0,0 +1,79 @@
# CAKEFOOT by @ohsqueezy for <https://foam.shampoo.ooo> and <https://ohsqueezy.itch.io>
#
# This file contains functions to facilitate transforming an SVG file with bezier curve data exported from GIMP into a Cakefoot level.
#
# This script is not essential for building the game. It is intended to be used manually to help build the `curve` field for a level in
# `resource/levels.json`.
#
# Create a path in GIMP, and export it to file by right-clicking the path in the path tool tab. Call the groups function on the path, and
# a list will be returned with one group of points per curve in the file.
import pathlib
import numpy as np
from bs4 import BeautifulSoup as BS
def groups(path: pathlib.Path, reverse: bool = False) -> list[list[tuple[float, float]]]:
"""
Read the groups of bezier curves in an SVG file exported by the GIMP path tool. A list of curves will be returned, each group in the
list consisting of a list of 2D floating point coordinates.
@param path SVG file with bezier curve
@param reverse For each group of points, return the reverse order they appear in the file
@return A three dimensional list, groups of 2D float coordinates
"""
groups = []
with path.open() as svg:
# Parse using XML mode, extract the "d" attribute from the "path" element
for content in BS(svg, features="xml").find("path").attrs["d"].split():
# Unless the content is just "M" or "C", it should contain a coordinate
if content != "M" and content != "C":
group = content
# Trim off the M that indicates the next group start at the end of the content
if content.endswith("M"):
group = content[:-1]
# Parse the coordinate data
groups[-1].append(tuple(float(point) for point in group.split(",")))
# A single "M" or a coordinate ending in "M" indicates a new group is starting
if content.endswith("M"):
groups.append([])
# Reverse groups after finished parsing if requested
if reverse:
for ii in range(len(groups)):
groups[ii] = list(reversed(groups[ii]))
return groups
def wrap_group(group: list[tuple[float, float]], x_offset: float, y_offset: float) -> list[tuple[float, float]]:
"""
Offset a given a list of 2D points by the given offset.
@param group List of 2D points
@param x_offset Amount to move in the X direction
@param y_offset Amount to move in the Y direction
@return Group after applying offset
"""
offset_group = []
for coordinates in group:
offset_group.append((coordinates[0] + x_offset, coordinates[1] + y_offset))
return offset_group
def format_group(group: list[tuple[float, float]]) -> str:
"""
@param group A 2D bezier curve as a list of float coordinates (see `groups` function)
@return Curve printed as a string appropriate for a JSON array for use with the Cakefoot levels file
"""
output = "\"curve\": ["
for ii, coordinates in enumerate(group):
if ii % 6 == 0:
output += "\n "
output += f"[{coordinates[0]:8}, {coordinates[1]:8}]"
if ii < len(group) - 1:
output += ", "
output += "\n]"
return output

View File

@ -12,6 +12,7 @@ uniform sampler2D model_texture;
uniform int uv_transformation;
uniform float coordinate_bound;
uniform bool texture_enabled;
uniform vec4 color_addition;
out vec4 output_color;
@ -25,5 +26,5 @@ void main()
* 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 += (1.0 - float(texture_enabled)) * ex_color;
output_color += (1.0 - float(texture_enabled)) * (ex_color + color_addition);
}