paradiso/examples/quickwings/quickwings.cpp

530 lines
16 KiB
C++
Raw Permalink Normal View History

2023-09-23 22:12:50 +02:00
/**
* paradiso - Paradigmen der Softwareentwicklung
*
2024-05-28 23:33:10 +02:00
* (c) Copyright 2023-2024 Hartmut Seichter, Robin Rottstädt, brxxh (Hannes Brothuhn)
2023-09-23 22:12:50 +02:00
*
*/
#include <paradiso/bitmap.hpp>
2023-10-10 17:59:48 +02:00
#include <paradiso/bitmap_io.hpp>
2023-09-23 22:12:50 +02:00
#include <paradiso/context.hpp>
#include <paradiso/geometry.hpp>
#include <paradiso/renderer.hpp>
#include <paradiso/shader.hpp>
#include <paradiso/sprite.hpp>
#include <paradiso/vector.hpp>
#include <paradiso/window.hpp>
2023-09-23 23:51:13 +02:00
#include <chrono>
2023-10-10 17:59:48 +02:00
#include <thread>
2023-09-23 22:12:50 +02:00
2023-09-23 23:51:13 +02:00
const int frame_rate = 60;
2023-11-23 18:59:30 +01:00
bool game_over = false;
float risky_pos_x;
float risky_pos_max_x;
float risky_pos_bottom_y;
float risky_pos_top_y;
float risky_pos_bottom_max_y;
float risky_pos_top_max_y;
2023-09-23 23:51:13 +02:00
2023-09-23 22:12:50 +02:00
struct Background {
paradiso::Sprite backgroundLeft;
paradiso::Sprite backgroundRight;
paradiso::Sprite grassLeft;
paradiso::Sprite grassRight;
paradiso::Sprite* scrolling[2] = {&backgroundLeft, &backgroundRight};
2023-11-24 17:43:17 +01:00
paradiso::Renderer renderer{};
2023-09-23 22:12:50 +02:00
Background() {
auto backgroundImage =
2023-10-10 17:59:48 +02:00
paradiso::BitmapIO::get().load("background-day.png");
2023-09-23 22:12:50 +02:00
backgroundLeft = paradiso::Sprite{
.bitmap = backgroundImage,
.pivot = {paradiso::Vector2<float>::make(0.0f, 0.16f)},
.scale = {paradiso::Vector2<float>::make(1.01f, 1.3f)}};
2023-09-23 22:12:50 +02:00
backgroundRight = paradiso::Sprite{
.bitmap = backgroundImage,
.pivot = {paradiso::Vector2<float>::make(2.018f, 0.16f)},
.scale = {paradiso::Vector2<float>::make(1.01f, 1.3f)}};
2023-09-23 22:12:50 +02:00
2023-10-10 17:59:48 +02:00
auto grassImage = paradiso::BitmapIO::get().load("base.png");
2023-09-23 22:12:50 +02:00
grassLeft = paradiso::Sprite{
.bitmap = grassImage,
.pivot = {paradiso::Vector2<float>::make(0.0f, -1.0f)},
.scale = {paradiso::Vector2<float>::make(1.0f, 0.33333f)}};
2023-09-23 22:12:50 +02:00
grassRight = paradiso::Sprite{
.bitmap = grassImage,
.pivot = {paradiso::Vector2<float>::make(2.0f, -1.0f)},
.scale = {paradiso::Vector2<float>::make(1.0f, 0.33333f)}};
2023-09-23 22:12:50 +02:00
}
void draw(const paradiso::Shader& shader) {
for (auto sprite : scrolling) {
2023-11-24 17:43:17 +01:00
if (game_over == false) {
if (sprite->pivot.x() <= -2.0f) {
sprite->pivot.x() += 4.0f;
}
sprite->pivot.x() -= 0.002f;
2023-09-23 22:12:50 +02:00
}
shader.set_uniform("pivot", sprite->pivot);
shader.set_uniform("scale", sprite->scale);
shader.set_uniform("rotation", sprite->rotation);
renderer.draw(*sprite, shader);
}
}
};
struct Pipe {
paradiso::Sprite pipe_top;
paradiso::Sprite pipe_bottom;
paradiso::Renderer renderer1{};
paradiso::Renderer renderer2{};
2023-11-24 17:43:17 +01:00
bool paused = false;
int pipe_spawn_rand_int = rand() % 80 + 15;
float pipe_spawn_rand = float(pipe_spawn_rand_int) / 100;
bool pos_reset = false;
Pipe() {
auto pipe_image = paradiso::BitmapIO::get().load("pipe-green.png");
pipe_top = paradiso::Sprite{
.bitmap = pipe_image,
2023-11-24 17:43:17 +01:00
.pivot = {paradiso::Vector2<float>::make(1.4f, pipe_spawn_rand + 1.0f)},
.scale = {paradiso::Vector2<float>::make(((500.0f - (500.0f - 52.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 320.0f)) / 700.0f) * 2.25f)},
.rotation = 3.1415926f};
pipe_bottom = paradiso::Sprite{
.bitmap = pipe_image,
2023-11-24 17:43:17 +01:00
.pivot = {paradiso::Vector2<float>::make(1.4f, pipe_spawn_rand - 1.5f)},
.scale = {paradiso::Vector2<float>::make(((500.0f - (500.0f - 52.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 320.0f)) / 700.0f) * 2.25f)}};
2023-11-24 17:43:17 +01:00
paused = true;
2024-05-28 23:33:10 +02:00
}
void update() {
2023-11-24 17:43:17 +01:00
if (game_over == true) {
paused = true;
}
2023-11-24 17:43:17 +01:00
if (paused == true) {
return;
}
else {
pipe_spawn_rand_int = rand() % 80 + 15;
pipe_spawn_rand = float(pipe_spawn_rand_int) / 100;
2023-11-24 17:43:17 +01:00
pipe_top.pivot.x() -= 0.02f;
pipe_bottom.pivot.x() -= 0.02f;
2023-11-24 17:43:17 +01:00
risky_pos_x = pipe_top.pivot.x();
risky_pos_max_x = pipe_top.pivot.x() + ((500.0f - (500.0f - 52.0f)) / 500.0f) * 2.25f;
2023-11-24 17:43:17 +01:00
risky_pos_top_y = pipe_top.pivot.y();
risky_pos_top_max_y = pipe_top.pivot.y() + 10.0f;
2023-11-24 17:43:17 +01:00
risky_pos_bottom_y = pipe_bottom.pivot.y();
risky_pos_bottom_max_y = pipe_bottom.pivot.y() - 10.0f;
2023-11-24 17:43:17 +01:00
if (pipe_top.pivot.x() <= -1.4f || pipe_bottom.pivot.x() <= -1.4f) {
pos_reset = true;
2023-11-24 17:43:17 +01:00
if (pos_reset == true) {
pipe_top.pivot.y() = pipe_spawn_rand + 1.0f;
pipe_bottom.pivot.y() = pipe_spawn_rand - 1.5;
pos_reset = false;
}
pipe_top.pivot.x() = 1.4f;
pipe_bottom.pivot.x() = 1.4f;
}
}
}
void draw(const paradiso::Shader& shader) {
shader.set_uniform("pivot", pipe_bottom.pivot);
shader.set_uniform("scale", pipe_bottom.scale);
renderer1.draw(pipe_bottom, shader);
2024-05-28 23:33:10 +02:00
shader.set_uniform("pivot", pipe_top.pivot);
shader.set_uniform("scale", pipe_top.scale);
shader.set_uniform("rotation", pipe_top.rotation);
renderer1.draw(pipe_top, shader);
}
};
2023-09-23 22:12:50 +02:00
struct Grass {
paradiso::Sprite grassLeft;
paradiso::Sprite grassRight;
paradiso::Sprite* scrolling[2] = {&grassLeft, &grassRight};
2023-11-24 18:13:58 +01:00
paradiso::Renderer renderer1{};
paradiso::Renderer renderer2{};
2023-09-23 22:12:50 +02:00
Grass() {
auto grassImage = paradiso::BitmapIO::get().load("base.png");
2023-09-23 22:12:50 +02:00
grassLeft = paradiso::Sprite{
.bitmap = grassImage,
2023-11-24 18:13:58 +01:00
.pivot = {paradiso::Vector2<float>::make(0.0f, -0.9f)},
.scale = {paradiso::Vector2<float>::make(((500.0f - (500.0f - 504.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 112.0f)) / 700.0f) * 2.25f)}};
2023-09-23 22:12:50 +02:00
grassRight = paradiso::Sprite{
.bitmap = grassImage,
2023-11-24 18:13:58 +01:00
.pivot = {paradiso::Vector2<float>::make(1.002f, -0.9f)},
.scale = {paradiso::Vector2<float>::make(((500.0f - (500.0f - 504.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 112.0f)) / 700.0f) * 2.25f)}};
2023-09-23 22:12:50 +02:00
}
void draw(const paradiso::Shader& shader) {
2023-11-24 18:13:58 +01:00
if (game_over == false) {
grassLeft.pivot.x() -= 0.02f;
grassRight.pivot.x() -= 0.02f;
if (grassRight.pivot.x() <= 0.0f) {
grassLeft.pivot.x() = 1.002f;
}
if (grassRight.pivot.x() <= -1.002f) {
grassRight.pivot.x() = 1.002f;
2023-09-23 22:12:50 +02:00
}
}
2023-11-24 18:13:58 +01:00
shader.set_uniform("pivot", grassLeft.pivot);
shader.set_uniform("scale", grassLeft.scale);
shader.set_uniform("rotation", grassLeft.rotation);
shader.set_uniform("pivot", grassRight.pivot);
shader.set_uniform("scale", grassRight.scale);
shader.set_uniform("rotation", grassRight.rotation);
renderer1.draw(grassLeft, shader);
renderer2.draw(grassRight, shader);
2023-09-23 22:12:50 +02:00
}
};
2023-10-10 17:59:48 +02:00
struct QuickWings {
2023-09-23 22:12:50 +02:00
paradiso::Renderer renderer1{};
paradiso::Renderer renderer2{};
paradiso::Renderer renderer3{};
std::array<paradiso::Sprite, 3> birds;
unsigned int flap = 0;
unsigned int flapSpeed = 15; // How many ticks per flap
unsigned int flapCounter = 0; // How many ticks since last flap
float velocity = 0.0f;
const float max_velocity = 0.02f;
2023-09-23 22:12:50 +02:00
const float gravity = -0.002f;
const float move_up_velocity = 0.02f;
2023-09-23 22:12:50 +02:00
bool move_up = false;
bool paused = true;
const float max_pos = 0.95f;
const float min_pos = -0.5f;
float pos = 0.34f;
2023-09-23 22:12:50 +02:00
float rotation = 0.0f;
2023-11-24 17:43:17 +01:00
int collision_counter = 0;
2023-10-10 17:59:48 +02:00
QuickWings() {
float scaleh = 0.08f;
float scalew = 0.158f;
2023-09-23 22:12:50 +02:00
birds = {
paradiso::Sprite{
.bitmap =
2023-10-10 17:59:48 +02:00
paradiso::BitmapIO::get().load("yellowbird-downflap.png"),
.pivot = {paradiso::Vector2<float>::make(0.0f, 0.0f)},
.scale = {paradiso::Vector2<float>::make(scalew, scaleh)}},
2023-09-23 22:12:50 +02:00
paradiso::Sprite{
.bitmap =
2023-10-10 17:59:48 +02:00
paradiso::BitmapIO::get().load("yellowbird-midflap.png"),
.pivot = {paradiso::Vector2<float>::make(0.0f, 0.0f)},
.scale = {paradiso::Vector2<float>::make(scalew, scaleh)}},
2023-09-23 22:12:50 +02:00
paradiso::Sprite{
.bitmap =
2023-10-10 17:59:48 +02:00
paradiso::BitmapIO::get().load("yellowbird-upflap.png"),
.pivot = {paradiso::Vector2<float>::make(0.0f, 0.0f)},
.scale = {paradiso::Vector2<float>::make(scalew, scaleh)}}};
2023-09-23 22:12:50 +02:00
}
void draw(const paradiso::Shader& shader) {
// Update flap state
if (flapCounter < flapSpeed) {
flapCounter++;
} else {
flapCounter = 0;
flap = (flap + 1) % birds.size();
}
auto bird = birds[flap];
bird.pivot.y() = pos;
bird.rotation = rotation;
shader.set_uniform("pivot", bird.pivot);
shader.set_uniform("scale", bird.scale);
shader.set_uniform("rotation", bird.rotation);
switch (flap) {
case 0:
renderer1.draw(bird, shader);
break;
case 1:
renderer2.draw(bird, shader);
break;
case 2:
renderer3.draw(bird, shader);
break;
}
}
void update() {
2023-11-23 18:59:30 +01:00
if (game_over == true) {
paused = true;
}
2024-05-28 23:33:10 +02:00
2023-09-23 22:12:50 +02:00
// Stop game
if (paused) {
2023-09-23 22:12:50 +02:00
return;
}
2024-05-28 23:33:10 +02:00
else {
2023-11-23 18:59:30 +01:00
// Apply gravity
velocity += gravity;
if (move_up)
velocity += move_up_velocity - gravity;
// Cap velocity
if (velocity > max_velocity)
velocity = max_velocity;
if (velocity < -max_velocity)
velocity = -max_velocity;
// Cap position
pos += velocity;
if (pos < min_pos) {
pos = min_pos;
velocity = 0.0f;
game_over = true;
}
if (pos > max_pos) {
pos = max_pos;
velocity = 0.0f;
game_over = true;
}
2023-09-23 22:12:50 +02:00
2023-11-23 18:59:30 +01:00
// Update rotation
rotation = velocity * 10.0f;
2023-09-23 22:12:50 +02:00
}
2023-11-24 17:43:17 +01:00
float final_risky_pos_top_y = risky_pos_top_y - 1.06f;
float final_risky_pos_bottom_y = risky_pos_bottom_y + 1.06f;
2023-11-24 17:43:17 +01:00
if (risky_pos_x - 0.3f <= 0.0f && risky_pos_max_x >= 0.0f) {
if (pos >= final_risky_pos_top_y && pos <= risky_pos_top_max_y) {
game_over = true;
2023-11-24 17:43:17 +01:00
}
if (pos <= final_risky_pos_bottom_y && pos >= risky_pos_bottom_max_y) {
if (collision_counter == 0) {
collision_counter++;
}
else {
collision_counter = 2;
}
if (collision_counter == 2) {
game_over = true;
}
}
}
2023-09-23 22:12:50 +02:00
}
// keyboard handler
void on_keyboard(const paradiso::Window::KeyboardInputStack& input) {
if (input.size()) {
paused = false;
2023-11-23 18:59:30 +01:00
if (paused == false) {
bool pressed_up = input.top().key == ' ' || input.top().key == 'W';
if (input.top().action == 1 && pressed_up) {
move_up = true;
} else if (input.top().action == 0 && pressed_up) {
move_up = false;
}
}
else {
return;
2023-09-23 22:12:50 +02:00
}
}
}
};
struct Message {
paradiso::Sprite messageSprite;
paradiso::Renderer renderer{};
2023-11-23 18:19:24 +01:00
bool start = false;
float pos = 100.0f;
Message() {
auto messageImage = paradiso::BitmapIO::get().load("message.png");
messageSprite = paradiso::Sprite{
.bitmap = messageImage,
.pivot = {paradiso::Vector2<float>::make(0.0f, 0.0f)},
.scale = {paradiso::Vector2<float>::make(0.8f, 0.8f)}
};
};
void draw(const paradiso::Shader& shader) {
shader.set_uniform("pivot", messageSprite.pivot);
shader.set_uniform("scale", messageSprite.scale);
renderer.draw(messageSprite, shader);
}
2023-11-23 18:19:24 +01:00
void update() {
if (start == true) {
messageSprite.pivot.y() = pos;
}
}
2024-05-28 23:33:10 +02:00
2023-11-23 18:19:24 +01:00
void on_keyboard(const paradiso::Window::KeyboardInputStack& input) {
if (input.size()) {
start = true;
}
}
};
2023-11-23 18:19:24 +01:00
2023-11-23 18:59:30 +01:00
struct GameOverMessage {
paradiso::Sprite messageSprite;
paradiso::Renderer renderer{};
GameOverMessage() {
auto messageImage = paradiso::BitmapIO::get().load("gameover.png");
messageSprite = paradiso::Sprite{
.bitmap = messageImage,
.pivot = {paradiso::Vector2<float>::make(0.0f, 0.4f)},
.scale = {paradiso::Vector2<float>::make(((500.0f - (500.0f - 192.0f)) / 500.0f) * 2.25f, ((700.0f - (700.0f - 42.0f)) / 700.0f) * 2.25f)}
};
};
void draw(const paradiso::Shader& shader) {
shader.set_uniform("pivot", messageSprite.pivot);
shader.set_uniform("scale", messageSprite.scale);
renderer.draw(messageSprite, shader);
}
};
2023-09-23 22:12:50 +02:00
auto main() -> int {
std::srand(std::time(nullptr));
2023-09-23 22:12:50 +02:00
// Ausgabefenster ... sieht aus als wäre es auf dem Stack
auto window = paradiso::Window();
auto size = paradiso::Size{.width = 500, .height = 700};
/*
window.set_resizecallback([](auto& w) -> void {
w.set_size(paradiso::Size{.width = 405, .height = 720});
});
*/
window
.set_size(size) // ... Grösse
.set_position(paradiso::Point{.x = 1920 / 2 - 500 / 2, .y = 1080 / 2 - 700 / 2}) // ... Position
2023-09-23 22:12:50 +02:00
.set_title("PardiSO.FlappyBird") // ... Titel
.set_visible(true); // ... und jetzt anzeigen!
// der Fenster Kontext
auto ctx = paradiso::Context{};
// ein Shader (Schattierungsprogramm)
auto shader = paradiso::Shader{};
// wir nutzen einen vorgefertigten shader
shader.load_preset(paradiso::Shader::Preset::Sprite);
// ein viewport stellt die Sicht der Kamera dar, d.h. bei quadratischen
// Pixeln sollte hier auch eine dementsprechende Grösse eingestellt
// werden
ctx.set_viewport(paradiso::Rectangle{
.position = paradiso::Point{.x = 0, .y = 0}, .size = size});
// nothing beats a classic look
ctx.set_clearcolor(paradiso::RGBA::from_rgb(0x00, 0x00, 0x00));
2023-10-10 17:59:48 +02:00
// Asset loader bekommt den Pfad
paradiso::BitmapIO::get().set_path("assets");
2023-09-23 22:12:50 +02:00
// Load
auto background = Background{};
auto grass = Grass{};
2023-10-10 17:59:48 +02:00
auto quickwingsapp = QuickWings{};
auto pipe = Pipe{};
auto message = Message{};
2023-11-23 18:59:30 +01:00
auto gameover = GameOverMessage{};
2023-09-23 22:12:50 +02:00
2023-11-24 17:43:17 +01:00
2023-09-23 23:51:13 +02:00
// timer
2023-09-23 22:12:50 +02:00
// das update führt den hier mitgegebnen Ausdruck innerhalb der internen
// Updates des Fensters auf. Es wird hier auch explizit ein bool gefordert
// damit das update auch zu jederzeit unterbrochen werden kann
while (window.update([&](paradiso::Window& w) -> bool {
ctx.set_viewport(paradiso::Rectangle{
.position = paradiso::Point{.x = 0, .y = 0}, .size = size});
ctx.clear();
2023-09-23 23:51:13 +02:00
auto t1 = std::chrono::high_resolution_clock::now();
2023-09-23 22:12:50 +02:00
// Keyboard and state change + update
2023-11-24 17:43:17 +01:00
if (quickwingsapp.paused == false) {
pipe.paused = false;
}
pipe.update();
2023-10-10 17:59:48 +02:00
quickwingsapp.on_keyboard(w.keyboard_input());
quickwingsapp.update();
2023-09-23 22:12:50 +02:00
2023-11-23 18:19:24 +01:00
message.on_keyboard(w.keyboard_input());
message.update();
2023-09-23 22:12:50 +02:00
// Draw
background.draw(shader);
pipe.draw(shader);
2023-09-23 22:12:50 +02:00
grass.draw(shader);
2023-11-23 18:19:24 +01:00
message.draw(shader);
2023-11-23 18:59:30 +01:00
2024-05-28 23:33:10 +02:00
if (game_over == true) {
2023-11-23 18:59:30 +01:00
gameover.draw(shader);
}
2023-10-10 17:59:48 +02:00
quickwingsapp.draw(shader);
2023-09-23 22:12:50 +02:00
2023-09-23 23:51:13 +02:00
// wait for frame rate
auto t2 = std::chrono::high_resolution_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1);
auto wait = std::chrono::microseconds(1000000 / frame_rate) - duration;
std::this_thread::sleep_for(wait);
2023-09-23 22:12:50 +02:00
// Quit
return !(w.keyboard_input().size() &&
w.keyboard_input().top().key == 'Q');
2023-11-24 17:43:17 +01:00
})) {
2023-09-23 22:12:50 +02:00
};
return 0;
}