577 lines
17 KiB
C++
577 lines
17 KiB
C++
#include "Enemy.hpp"
|
|
|
|
void Enemy::reset()
|
|
{
|
|
if (!coin_collected)
|
|
{
|
|
_coin_taken = false;
|
|
}
|
|
}
|
|
|
|
bool Enemy::collide(const sb::Box& box, const sb::Sprite& sprite, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
|
|
{
|
|
sb::Box wrapped = this->box;
|
|
wrapped.center(sb::wrap_point({this->box.center(), 0.0f}, clip_lower, clip_upper));
|
|
sb::Box wrapped_other = box;
|
|
wrapped_other.center(sb::wrap_point({box.center(), 0.0f}, clip_lower, clip_upper));
|
|
return wrapped.collide(wrapped_other);
|
|
}
|
|
|
|
bool Enemy::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
|
|
{
|
|
/* If there is no coin, return false immediately */
|
|
if (!_coin.has_value() || coin_taken() || coin_collected)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* Returning true defers the full check to the derived class */
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void Enemy::take_coin()
|
|
{
|
|
if (_coin.has_value())
|
|
{
|
|
_coin_taken = true;
|
|
}
|
|
}
|
|
|
|
bool Enemy::coin_taken() const
|
|
{
|
|
return _coin_taken;
|
|
}
|
|
|
|
void Enemy::collect_coin()
|
|
{
|
|
if (_coin.has_value())
|
|
{
|
|
coin_collected = true;
|
|
}
|
|
}
|
|
|
|
void Enemy::coin_translation(const glm::vec3& translation)
|
|
{
|
|
if (_coin.has_value())
|
|
{
|
|
_coin->translate({translation.x, translation.y, 0.0f});
|
|
}
|
|
}
|
|
|
|
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_collected)
|
|
{
|
|
glUniform4fv(color_addition_uniform, 1, &rotating_hue.normal()[0]);
|
|
_coin->draw(transformation_uniform, view, projection, texture_flag_uniform);
|
|
}
|
|
}
|
|
|
|
Slicer::Slicer(const Curve& curve, float relative, float speed, float stray) : curve(curve), relative(relative), speed(speed), stray(stray)
|
|
{
|
|
position = start();
|
|
sprite.texture("resource/slicer/slicer-1.png", GL_LINEAR);
|
|
sprite.texture("resource/slicer/slicer-2.png", GL_LINEAR);
|
|
sprite.frames.frame_length(0.5f);
|
|
sprite.frames.play();
|
|
|
|
/* Get size as the pixels divided by the original resolution */
|
|
float size = 12.0f / 486.0f;
|
|
|
|
/* Apply size to sprite and box objects */
|
|
sprite.scale(glm::vec2{size});
|
|
box.size(2.0f * glm::vec2{size});
|
|
};
|
|
|
|
void Slicer::coin(const sb::Sprite& sprite, float radius, float angle)
|
|
{
|
|
_coin = sprite;
|
|
coin_radius = radius;
|
|
coin_angle = angle;
|
|
}
|
|
|
|
void Slicer::update(const sb::Timer& timer)
|
|
{
|
|
sprite.update(timer);
|
|
|
|
float move = timer.delta(speed);
|
|
glm::vec3 destination;
|
|
|
|
/* Use a loop to move repeatedly between start and end when there is a long move, for example when there is major lag between frames. */
|
|
while (move > 0.0f)
|
|
{
|
|
if (toward_end)
|
|
{
|
|
destination = end();
|
|
}
|
|
else
|
|
{
|
|
destination = start();
|
|
}
|
|
float distance = glm::distance(position, destination);
|
|
if (distance < move)
|
|
{
|
|
position = destination;
|
|
move -= distance;
|
|
toward_end = !toward_end;
|
|
}
|
|
else
|
|
{
|
|
position += glm::vec3{sb::Segment(position, destination).step(move), 0.0f};
|
|
move = 0.0f;
|
|
}
|
|
}
|
|
|
|
/* Move hit box */
|
|
box.center(position);
|
|
|
|
/* Update challenge coin position */
|
|
if (_coin.has_value() && !coin_taken() && !coin_collected)
|
|
{
|
|
glm::vec3 coin_position = position + glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f};
|
|
_coin->translate(curve.get().wrap(coin_position));
|
|
}
|
|
|
|
/* Place sprite */
|
|
sprite.translate(curve.get().wrap(position));
|
|
}
|
|
|
|
glm::vec3 Slicer::center() const
|
|
{
|
|
return curve.get().relative(relative);
|
|
}
|
|
|
|
float Slicer::angle() const
|
|
{
|
|
int index = curve.get().index(relative);
|
|
int next_index = std::min(index + 1, curve.get().length() - 1);
|
|
int prev_index = std::max(index - 1, 0);
|
|
return sb::angle_between(curve.get()[prev_index], curve.get()[next_index]) + glm::half_pi<float>();
|
|
}
|
|
|
|
glm::vec3 Slicer::start() const
|
|
{
|
|
return center() + glm::vec3{sb::angle_to_vector(angle(), stray), 0.0f};
|
|
}
|
|
|
|
glm::vec3 Slicer::end() const
|
|
{
|
|
return center() + glm::vec3{sb::angle_to_vector(angle() + glm::pi<float>(), stray), 0.0f};
|
|
}
|
|
|
|
bool Slicer::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
|
|
{
|
|
if (Enemy::collide_coin(box, clip_lower, clip_upper))
|
|
{
|
|
sb::Box coin_box = this->box;
|
|
coin_box.move(glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f});
|
|
coin_box.center(sb::wrap_point({coin_box.center(), 0.0f}, clip_lower, clip_upper));
|
|
box.center(sb::wrap_point({box.center(), 0.0f}, clip_lower, clip_upper));
|
|
return coin_box.collide(box);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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", GL_LINEAR);
|
|
sprite.texture("resource/fish/fish-2.png", GL_LINEAR);
|
|
sprite.frames.frame_length(0.3f);
|
|
sprite.frames.play();
|
|
|
|
/* Set size of objects */
|
|
glm::vec2 size {12.0f / 486.0f};
|
|
sprite.scale(size);
|
|
box.size(2.0f * size);
|
|
};
|
|
|
|
void Fish::coin(const sb::Sprite& sprite, float angle)
|
|
{
|
|
_coin = sprite;
|
|
coin_angle = angle;
|
|
}
|
|
|
|
void Fish::coin(const sb::Sprite& sprite, float radius, float angle)
|
|
{
|
|
coin_radius = radius;
|
|
Fish::coin(sprite, angle);
|
|
}
|
|
|
|
void Fish::update(const sb::Timer& timer)
|
|
{
|
|
sprite.update(timer);
|
|
|
|
/* On the first update, record the timestamp of the timer, so the difference between subsequent timestamps and the first timestamp can
|
|
* be used to measure the angle position of the fish. Do this instead of adding to the angle position every frame to avoid letting
|
|
* floating point addition error accumulate differently among fish with different speeds. */
|
|
if (!first_update_time.has_value())
|
|
{
|
|
first_update_time = timer.elapsed();
|
|
}
|
|
|
|
/* The angle position is the seconds since the first update multiplied by radians per second plus the fish's given angle offset. */
|
|
angle = (timer.elapsed() - *first_update_time) * speed + offset;
|
|
|
|
/* Place the fish an amount away from its center using the angle and radius to get the vector. */
|
|
glm::vec3 position = center() + glm::vec3{sb::angle_to_vector(angle, radius), 0.0f};
|
|
|
|
/* Update challenge coin position */
|
|
if (_coin.has_value() && !coin_taken() && !coin_collected)
|
|
{
|
|
glm::vec3 coin_position;
|
|
|
|
/* Place coin either along the fish's circle or translated a vector away from the fish, depending on whether or not the radius is
|
|
* set. */
|
|
if (coin_radius == 0.0f)
|
|
{
|
|
coin_position = center() + glm::vec3{sb::angle_to_vector(angle + coin_angle, radius), 0.0f};
|
|
}
|
|
else
|
|
{
|
|
coin_position = position + glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f};
|
|
}
|
|
|
|
_coin->translate(curve.get().wrap(coin_position));
|
|
}
|
|
|
|
sprite.translate(curve.get().wrap(position));
|
|
box.center(position);
|
|
}
|
|
|
|
glm::vec3 Fish::center() const
|
|
{
|
|
return curve.get().relative(relative);
|
|
}
|
|
|
|
bool Fish::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
|
|
{
|
|
if (Enemy::collide_coin(box, clip_lower, clip_upper))
|
|
{
|
|
sb::Box coin_box = this->box;
|
|
coin_box.center(sb::wrap_point(center() + glm::vec3{sb::angle_to_vector(angle + coin_angle, radius), 0.0f}, clip_lower, clip_upper));
|
|
box.center(sb::wrap_point({box.center(), 0.0f}, clip_lower, clip_upper));
|
|
return coin_box.collide(box);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Projectile::Projectile(const glm::vec3& position, const glm::vec3& target, float speed) : position(position), target(target), speed(speed)
|
|
{
|
|
/* Set angle once at construction so it will continue moving past the target once it reaches. */
|
|
angle = sb::angle_between(position, target);
|
|
|
|
/* Initialize sprite */
|
|
sprite.texture("resource/projectile/projectile-1.png", GL_LINEAR);
|
|
sprite.texture("resource/projectile/projectile-2.png", GL_LINEAR);
|
|
sprite.texture("resource/projectile/projectile-3.png", GL_LINEAR);
|
|
sprite.texture("resource/projectile/projectile-4.png", GL_LINEAR);
|
|
sprite.frames.frame_length(0.05f);
|
|
sprite.frames.play();
|
|
|
|
/* Set size of objects */
|
|
glm::vec2 size {12.0f / 486.0f};
|
|
sprite.scale(size);
|
|
box.size(2.0f * size);
|
|
|
|
/* Place objects at initial position */
|
|
sprite.translate(position);
|
|
box.center(position);
|
|
};
|
|
|
|
void Projectile::coinify(const sb::Sprite& sprite)
|
|
{
|
|
_coin = sprite;
|
|
}
|
|
|
|
void Projectile::update(const sb::Timer& timer)
|
|
{
|
|
sprite.update(timer);
|
|
|
|
/* Move */
|
|
position += glm::vec3{sb::angle_to_vector(angle, timer.delta(speed)), 0.0f};
|
|
sprite.translate(position);
|
|
|
|
/* Update challenge coin */
|
|
if (_coin.has_value())
|
|
{
|
|
_coin->translate(position);
|
|
}
|
|
|
|
box.center(position);
|
|
}
|
|
|
|
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
|
|
{
|
|
sprite.draw(transformation_uniform, view, projection, texture_flag_uniform);
|
|
}
|
|
}
|
|
|
|
bool Projectile::out_of_bounds() const
|
|
{
|
|
return position.x > 1.777f || position.x < -1.777f || position.y > 1.0f || position.y < -1.0f;
|
|
}
|
|
|
|
bool Projectile::coinified() const
|
|
{
|
|
return _coin.has_value();
|
|
}
|
|
|
|
Projector::Projector(const Character& character, const glm::vec3& position, float speed, float rate, float release_delay) :
|
|
character(character), position(position), speed(speed), release_delay(release_delay)
|
|
{
|
|
animation_charge.frame_length(rate);
|
|
sprite.texture("resource/projector/projector-1.png", GL_LINEAR);
|
|
sprite.texture("resource/projector/projector-2.png", GL_LINEAR);
|
|
|
|
/* Set size and position of objects */
|
|
glm::vec2 size {12.0f / 486.0f};
|
|
sprite.scale(size);
|
|
sprite.translate(position);
|
|
box.size(2.0f * size);
|
|
box.center(position);
|
|
|
|
/* Charge animation will always be playing */
|
|
animation_charge.play();
|
|
};
|
|
|
|
void Projector::reset()
|
|
{
|
|
Enemy::reset();
|
|
projectiles.clear();
|
|
animation_release.pause();
|
|
count = 0;
|
|
sprite.texture_index(0);
|
|
}
|
|
|
|
void Projector::coin(const sb::Sprite& sprite, int frequency)
|
|
{
|
|
_coin = sprite;
|
|
coin_frequency = frequency;
|
|
}
|
|
|
|
void Projector::update(const sb::Timer& timer)
|
|
{
|
|
sprite.update(timer);
|
|
|
|
/* Update the animation timers */
|
|
animation_charge.update(timer.stamp());
|
|
animation_release.update(timer.stamp());
|
|
|
|
/* Set play state based on unpaused timer state */
|
|
animation_charge.toggle(timer);
|
|
animation_release.toggle(timer);
|
|
|
|
/* Erase projectiles which have gone off screen */
|
|
projectiles.erase(
|
|
std::remove_if(
|
|
projectiles.begin(),
|
|
projectiles.end(),
|
|
[](const Projectile& projectile)
|
|
{
|
|
return projectile.out_of_bounds();
|
|
}),
|
|
projectiles.end());
|
|
|
|
/* Update projectiles */
|
|
for (Projectile& projectile : projectiles)
|
|
{
|
|
projectile.update(timer);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (!projectile.coinified() && projectile.collide(box, sprite, clip_lower, clip_upper))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Projector::take_coin()
|
|
{
|
|
Enemy::take_coin();
|
|
|
|
/* Erase coinified projectiles */
|
|
projectiles.erase(
|
|
std::remove_if(
|
|
projectiles.begin(),
|
|
projectiles.end(),
|
|
[](const Projectile& projectile)
|
|
{
|
|
return projectile.coinified();
|
|
}),
|
|
projectiles.end());
|
|
}
|
|
|
|
bool Projector::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
|
|
{
|
|
if (Enemy::collide_coin(box, clip_lower, clip_upper))
|
|
{
|
|
for (const Projectile& projectile : projectiles)
|
|
{
|
|
if (projectile.coinified() && projectile.collide(box, sb::Sprite(), clip_lower, clip_upper))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Projector::charge()
|
|
{
|
|
sprite.texture_index(1);
|
|
animation_release.play_once(release_delay);
|
|
}
|
|
|
|
void Projector::release()
|
|
{
|
|
sprite.texture_index(0);
|
|
|
|
/* Keep a count of how many projectiles have been fired */
|
|
count++;
|
|
|
|
/* Temporarily hard code the wrapping since the current curve is not currently accessible from this function. */
|
|
glm::vec3 point = sb::wrap_point(character.position, {-1.77777f, -1.0f, 0.0f}, {1.77777f, 1.0f, 1.0f});
|
|
|
|
projectiles.emplace_back(position, point, speed);
|
|
|
|
/* Fire a challenge coin every frequency time if frequency is non-zero and coin has not been taken */
|
|
if (coin_frequency > 0 && _coin.has_value() && !coin_taken() && count % coin_frequency == 0)
|
|
{
|
|
projectiles.back().coinify(*_coin);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* Draw the coin separately if it has been taken and isn't collected yet */
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 */
|
|
if (!camo)
|
|
{
|
|
sprite.texture("resource/flame/flame-1.png", GL_LINEAR);
|
|
sprite.texture("resource/flame/flame-2.png", GL_LINEAR);
|
|
}
|
|
else
|
|
{
|
|
sprite.texture("resource/coin/coin-0.png", GL_LINEAR);
|
|
}
|
|
sprite.frames.frame_length(0.3f);
|
|
sprite.frames.play();
|
|
|
|
/* Initialize object size and position */
|
|
glm::vec2 size {12.0f / 486.0f};
|
|
sprite.scale(size);
|
|
sprite.translate(position);
|
|
box.size(2.0f * size);
|
|
box.center(position);
|
|
|
|
/* Only start the mirror effect if the interval is a positive value. */
|
|
if (mirror_interval >= 0.0f)
|
|
{
|
|
animation_mirror.frame_length(mirror_interval);
|
|
animation_mirror.play();
|
|
}
|
|
};
|
|
|
|
void Flame::coin(const sb::Sprite& sprite, float radius, float angle)
|
|
{
|
|
_coin = sprite;
|
|
coin_radius = radius;
|
|
coin_angle = angle;
|
|
}
|
|
|
|
void Flame::update(const sb::Timer& timer)
|
|
{
|
|
sprite.update(timer);
|
|
|
|
/* Move */
|
|
animation_mirror.update(timer.stamp());
|
|
position += glm::vec3{sb::angle_to_vector(angle, timer.delta(speed)), 0.0f};
|
|
|
|
/* Update challenge coin position */
|
|
if (_coin.has_value() && !coin_taken() && !coin_collected)
|
|
{
|
|
glm::vec3 coin_position = position + glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f};
|
|
_coin->translate(
|
|
sb::wrap_point(coin_position, glm::vec3{field.left(), field.bottom(), -1.0f}, glm::vec3{field.right(), field.top(), 1.0f}));
|
|
}
|
|
|
|
box.center(position);
|
|
sprite.translate(
|
|
sb::wrap_point(position, glm::vec3{field.left(), field.bottom(), -1.0f}, glm::vec3{field.right(), field.top(), 1.0f}));
|
|
}
|
|
|
|
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. */
|
|
if (mirrored)
|
|
{
|
|
angle = initial_angle;
|
|
position = initial_position;
|
|
}
|
|
else
|
|
{
|
|
angle += glm::pi<float>();
|
|
}
|
|
|
|
/* Indicate whether flame is moving away from its original position or toward it. */
|
|
mirrored = !mirrored;
|
|
}
|
|
|
|
bool Flame::collide_coin(sb::Box box, const glm::vec3& clip_lower, const glm::vec3& clip_upper) const
|
|
{
|
|
if (Enemy::collide_coin(box, clip_lower, clip_upper))
|
|
{
|
|
sb::Box coin_box = this->box;
|
|
coin_box.move(glm::vec3{sb::angle_to_vector(coin_angle, coin_radius), 0.0f});
|
|
coin_box.center(sb::wrap_point({coin_box.center(), 0.0f}, clip_lower, clip_upper));
|
|
box.center(sb::wrap_point({box.center(), 0.0f}, clip_lower, clip_upper));
|
|
return coin_box.collide(box);
|
|
}
|
|
return false;
|
|
}
|