#include #include #include #include #include #include #include #include #include #include #include #include using namespace psemek; struct map { std::vector> platforms; }; struct player { math::vector size; math::point position; math::vector velocity; math::point ghost_position; math::vector ghost_velocity; bool grounded = false; math::box bbox() const { return math::expand(math::box::singleton(position), size); } }; struct particle { math::point position; math::vector velocity; float size; float lifetime; gfx::color_rgba color; }; struct platformer_app : app::application_base { platformer_app(options const &, context const &) { map_.platforms.push_back({{{-10.f, 10.f}, {-5.f, -4.f}}}); map_.platforms.push_back({{{-5.f, -3.f}, {-2.5f, -2.f}}}); map_.platforms.push_back({{{-1.f, 1.f}, {-2.f, -1.5f}}}); map_.platforms.push_back({{{ 3.f, 5.f}, {-1.5f, -1.f}}}); map_.platforms.push_back({{{-7.f, -6.f}, {-3.5f, -3.f}}}); player_.size = {0.25f, 0.5f}; player_.position = {0.f, 5.f}; player_.velocity = {0.f, 0.f}; player_.ghost_position = player_.position; player_.ghost_velocity = {0.f, 0.f}; } void update() override { float const dt = frame_clock_.restart().count(); float const gravity = -50.f; float const acceleration = 200.f; float const friction = 25.f; float const jump_speed = 15.f; math::vector const ghost_spring_force{200.f, 500.f}; math::vector const ghost_spring_damping{10.f, 20.f}; float const particle_grow_speed = 0.1f; float const move_particle_spawn_period = 1.f / 32.f; int const jump_particle_count = 16; int const land_particle_count = 32; player_.velocity[0] *= std::exp(- friction * dt); player_.velocity[1] += dt * gravity; player_.position += player_.velocity * dt; { auto v = (player_.position - player_.ghost_position); player_.ghost_velocity += math::pointwise_mult(v, ghost_spring_force) * dt; player_.ghost_velocity[0] *= std::exp(- ghost_spring_damping[0] * dt); player_.ghost_velocity[1] *= std::exp(- ghost_spring_damping[1] * dt); } player_.ghost_position += player_.ghost_velocity * dt; bool was_grounded = player_.grounded; auto velocity_before_collision = player_.velocity; player_.grounded = false; for (auto const & platform : map_.platforms) { auto const player_box = player_.bbox(); auto const intersection = platform & player_box; if (intersection.empty()) continue; if (intersection[0].length() < intersection[1].length()) { if (intersection[0].center() < player_.position[0]) player_.position[0] += intersection[0].length(); else player_.position[0] -= intersection[0].length(); player_.velocity[0] = 0.f; } else { if (intersection[1].center() < player_.position[1]) { player_.position[1] += intersection[1].length(); player_.grounded = true; } else player_.position[1] -= intersection[1].length(); player_.velocity[1] = 0.f; } } if (state().key_down.contains(app::keycode::A)) player_.velocity[0] -= acceleration * dt; if (state().key_down.contains(app::keycode::D)) player_.velocity[0] += acceleration * dt; if (player_.grounded && (state().key_down.contains(app::keycode::A) ^ state().key_down.contains(app::keycode::D))) { move_particle_spawn_timer_ += dt; if (move_particle_spawn_timer_ > move_particle_spawn_period) { move_particle_spawn_timer_ -= move_particle_spawn_period; float s = state().key_down.contains(app::keycode::A) ? 1.f : -1.f; auto position = player_.position + math::vector{s * player_.size[0], -player_.size[1]}; auto velocity = math::direction(random::uniform(rng_, math::rad(60.f), math::rad(120.f))); auto size = random::uniform(rng_, 0.05f, 0.15f); auto lifetime = random::uniform(rng_, 0.25f, 0.5f); int c = random::uniform(rng_, 63, 191); gfx::color_rgba color{c, 0, c, 255}; particles_.push_back({position, velocity, size, lifetime, color}); } } if (player_.grounded && state().key_down.contains(app::keycode::W)) { player_.velocity[1] += jump_speed; for (int i = 0; i < jump_particle_count; ++i) { float s = random::uniform(rng_, -1.f, 1.f); auto position = player_.position + math::vector{s * player_.size[0], -player_.size[1]}; auto velocity = (random::uniform(rng_) ? 1.f : -1.f) * math::direction(random::uniform(rng_, math::rad(-30.f), math::rad(30.f))) * 3.f; auto size = random::uniform(rng_, 0.05f, 0.15f); auto lifetime = random::uniform(rng_, 0.25f, 0.5f); int c = random::uniform(rng_, 63, 191); gfx::color_rgba color{c, 0, c, 255}; particles_.push_back({position, velocity, size, lifetime, color}); } } if (!was_grounded && player_.grounded) { for (int i = 0; i < land_particle_count; ++i) { float s = random::uniform(rng_, -1.f, 1.f); auto position = player_.position + math::vector{s * player_.size[0], -player_.size[1]}; auto velocity = math::direction(random::uniform(rng_, math::rad(0.f), math::rad(180.f))) * (-velocity_before_collision[1]) * 0.2f; auto size = random::uniform(rng_, 0.05f, 0.15f); auto lifetime = random::uniform(rng_, 0.25f, 0.5f); int c = random::uniform(rng_, 63, 191); gfx::color_rgba color{c, 0, c, 255}; particles_.push_back({position, velocity, size, lifetime, color}); } } std::vector alive_particles; for (auto & p : particles_) { p.lifetime -= dt; if (p.lifetime <= 0.f) continue; p.position += p.velocity * dt; p.size += particle_grow_speed * dt; alive_particles.push_back(p); } particles_ = std::move(alive_particles); } void present() override { gl::ClearColor(1.f, 1.f, 1.f, 0.f); gl::Clear(gl::COLOR_BUFFER_BIT); for (auto const & box : map_.platforms) painter_.rect(box, {0, 0, 0, 255}); { math::point bottom = player_.position - math::vector{0.f, player_.size[1]}; math::point top = player_.ghost_position + math::vector{0.f, player_.size[1]}; auto d = math::vector{player_.size[0], 0.f}; gfx::color_rgba color{255, 0, 0, 255}; painter_.triangle(bottom - d, bottom + d, top - d, color); painter_.triangle(bottom + d, top - d, top + d, color); } for (auto const & p : particles_) { auto colorf = gfx::to_colorf(p.color); colorf[3] *= 1.f - std::exp(- 2.f * p.lifetime); painter_.circle(p.position, p.size, gfx::to_coloru8(colorf)); } float const aspect_ratio = state().size[0] * 1.f / state().size[1]; float const view_size = 5.f; math::box view_box{{{-view_size * aspect_ratio, view_size * aspect_ratio}, {-view_size, view_size}}}; painter_.render(math::orthographic_camera{view_box}.transform()); } private: map map_; player player_; std::vector particles_; float move_particle_spawn_timer_ = 0.f; random::generator rng_; util::clock<> frame_clock_; gfx::painter painter_; }; namespace psemek::app { std::unique_ptr make_application_factory() { return default_application_factory({.name = "Platformer example"}); } }