MVP for a Pong game
This commit is contained in:
parent
14343e7fd0
commit
e9d0de8cdd
17 changed files with 517 additions and 109 deletions
2
examples/CMakeLists.txt
Normal file
2
examples/CMakeLists.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
add_subdirectory(simple)
|
||||
add_subdirectory(pong)
|
4
examples/pong/CMakeLists.txt
Normal file
4
examples/pong/CMakeLists.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
add_executable(paradiso_pong pong.cpp)
|
||||
|
||||
target_link_libraries(paradiso_pong paradiso_core)
|
259
examples/pong/pong.cpp
Normal file
259
examples/pong/pong.cpp
Normal file
|
@ -0,0 +1,259 @@
|
|||
/**
|
||||
* paradiso - Paradigmen der Softwareentwicklung
|
||||
*
|
||||
* (c) Copyright 2023 Hartmut Seichter
|
||||
*
|
||||
*/
|
||||
|
||||
#include <paradiso/bitmap.hpp>
|
||||
#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>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
struct PongStage {
|
||||
|
||||
using Vec2 = paradiso::Vector2<float>;
|
||||
|
||||
Vec2 tl{Vec2::make(-1.0f, -1.0f)};
|
||||
Vec2 br{Vec2::make(+1.0f, +1.0f)};
|
||||
|
||||
enum class TouchPoint { None, Left, Right, Bottom, Top };
|
||||
|
||||
TouchPoint touch(const Vec2& o, float eps = 0.001f) noexcept {
|
||||
std::unordered_map<TouchPoint, float> deltas;
|
||||
deltas[TouchPoint::Left] = std::abs(o.x() - tl.x());
|
||||
deltas[TouchPoint::Right] = std::abs(o.x() - br.x());
|
||||
deltas[TouchPoint::Bottom] = std::abs(o.y() - br.y());
|
||||
deltas[TouchPoint::Top] = std::abs(o.y() - tl.y());
|
||||
|
||||
auto min_el = *std::min_element(
|
||||
std::begin(deltas), std::end(deltas),
|
||||
[](const auto& l, const auto& r) { return l.second < r.second; });
|
||||
|
||||
auto t = (min_el.second <= eps) ? min_el.first : TouchPoint::None;
|
||||
|
||||
if (t != TouchPoint::None) {
|
||||
sprite.bitmap.pixel(0,0) = paradiso::RGBA::white();
|
||||
} else {
|
||||
sprite.bitmap.pixel(0,0) = paradiso::RGBA::black();
|
||||
}
|
||||
|
||||
return t;
|
||||
};
|
||||
|
||||
// sprite
|
||||
paradiso::Sprite sprite{
|
||||
.bitmap = paradiso::Bitmap::from_data(paradiso::Size{1, 1},
|
||||
paradiso::RGBA::from_rgb(0x80,0xFF,0xFF))
|
||||
};
|
||||
|
||||
void draw(const paradiso::Shader& shader) {
|
||||
renderer.draw(sprite, shader);
|
||||
}
|
||||
|
||||
paradiso::Renderer renderer{};
|
||||
};
|
||||
|
||||
struct PongPaddle {
|
||||
|
||||
static constexpr float whoopiness = 0.95f;
|
||||
|
||||
// sprite
|
||||
paradiso::Sprite sprite{
|
||||
.bitmap = paradiso::Bitmap::from_data(paradiso::Size{1, 1},
|
||||
paradiso::RGBA::white()),
|
||||
.pivot = paradiso::Vector2<float>::make(0.0f, -0.9f),
|
||||
.scale = paradiso::Vector2<float>::make(0.25f, 0.01f)};
|
||||
|
||||
// keyboard handler
|
||||
void on_keyboard(const paradiso::Window::KeyboardInputStack& input) {
|
||||
|
||||
if (input.size()) {
|
||||
if (input.top().key == 'A' || input.top().key == 263) {
|
||||
velocity_horizontal -= 0.01;
|
||||
} else if (input.top().key == 'D' || input.top().key == 262) {
|
||||
velocity_horizontal += 0.01;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw(const paradiso::Shader& shader) {
|
||||
|
||||
// update internal state
|
||||
velocity_horizontal *= whoopiness;
|
||||
sprite.pivot.x() += velocity_horizontal;
|
||||
|
||||
std::clamp(sprite.pivot.x(), -0.5f, 0.5f);
|
||||
|
||||
// update shader uniforms
|
||||
shader.set_uniform("pivot", sprite.pivot);
|
||||
shader.set_uniform("scale", sprite.scale);
|
||||
shader.set_uniform("rotation", sprite.rotation);
|
||||
|
||||
renderer.draw(sprite, shader);
|
||||
}
|
||||
|
||||
float velocity_horizontal{};
|
||||
|
||||
paradiso::Renderer renderer{};
|
||||
};
|
||||
|
||||
struct PongBall {
|
||||
// sprite
|
||||
paradiso::Sprite sprite{
|
||||
.bitmap = paradiso::Bitmap::from_data(paradiso::Size{1, 1},
|
||||
paradiso::RGBA::white()),
|
||||
.pivot = paradiso::Vector2<float>::make(0.0f, 0.0f),
|
||||
.scale = paradiso::Vector2<float>::make(0.0125f, 0.0125f),
|
||||
};
|
||||
|
||||
void interact(PongStage& stage) {
|
||||
|
||||
auto touch = stage.touch(sprite.pivot);
|
||||
|
||||
switch (touch) {
|
||||
case PongStage::TouchPoint::Top:
|
||||
case PongStage::TouchPoint::Bottom:
|
||||
velocity.y() *= -1;
|
||||
break;
|
||||
case PongStage::TouchPoint::Left:
|
||||
case PongStage::TouchPoint::Right:
|
||||
velocity.x() *= -1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// interact ball & paddle
|
||||
void interact(const PongPaddle& paddle) {
|
||||
const auto& ps = paddle.sprite.scale;
|
||||
const auto& pp = paddle.sprite.pivot;
|
||||
|
||||
auto left_x = pp.x() - ps.x();
|
||||
auto right_x = pp.x() + ps.x();
|
||||
|
||||
static constexpr float eps{0.01f};
|
||||
|
||||
if (sprite.pivot.x() >= left_x && sprite.pivot.x() <= right_x &&
|
||||
std::abs(pp.y() - sprite.pivot.y()) < eps) {
|
||||
|
||||
velocity.y() *= -1;
|
||||
velocity.x() += paddle.velocity_horizontal * paddle.whoopiness;
|
||||
}
|
||||
}
|
||||
|
||||
void update() { sprite.pivot += velocity; }
|
||||
|
||||
void draw(const paradiso::Shader& shader) {
|
||||
|
||||
std::clamp(sprite.pivot.x(), -0.5f, 0.5f);
|
||||
|
||||
// update shader uniforms
|
||||
shader.set_uniform("pivot", sprite.pivot);
|
||||
shader.set_uniform("scale", sprite.scale);
|
||||
shader.set_uniform("rotation", sprite.rotation);
|
||||
|
||||
renderer.draw(sprite, shader);
|
||||
}
|
||||
|
||||
paradiso::Vector2<float> velocity{};
|
||||
|
||||
paradiso::Renderer renderer{};
|
||||
|
||||
constexpr void push(const auto& impulse) noexcept { velocity += impulse; }
|
||||
};
|
||||
|
||||
auto main() -> int {
|
||||
|
||||
// Ausgabefenster ... sieht aus als wäre es auf dem Stack
|
||||
auto window = paradiso::Window();
|
||||
|
||||
// wir bauen ein Fenster ...
|
||||
window
|
||||
.set_size(paradiso::Size{.width = 720, .height = 720}) // ... Grösse
|
||||
.set_position(paradiso::Point{.x = 100, .y = 100}) // ... Position
|
||||
.set_title("PardiSO.Pong") // ... 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);
|
||||
|
||||
auto paddle = PongPaddle{};
|
||||
auto ball = PongBall{};
|
||||
auto stage = PongStage{};
|
||||
|
||||
ball.push(paradiso::Vector2<float>::make(0.0001f, -0.0005f));
|
||||
|
||||
// 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 = window.client_size()
|
||||
.maximal_extent() // wir wollen das
|
||||
// Seitenverhältnis beibehalten
|
||||
});
|
||||
|
||||
// nothing beats a classic look
|
||||
ctx.set_clearcolor(paradiso::RGBA::from_rgb(0x00, 0x00, 0x00));
|
||||
|
||||
// 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 {
|
||||
auto me = window.client_size().maximal_extent();
|
||||
|
||||
ctx.set_viewport(paradiso::Rectangle{
|
||||
.position = paradiso::Point{.x = 0, .y = 0},
|
||||
.size = me // wir wollen das
|
||||
// Seitenverhältnis beibehalten
|
||||
});
|
||||
|
||||
// hier wird das eigentliche löschen des vorherigen Inhalts ausgelöst
|
||||
ctx.clear();
|
||||
|
||||
paddle.on_keyboard(w.keyboard_input());
|
||||
|
||||
// still here
|
||||
bool want_close =
|
||||
(w.keyboard_input().size() && w.keyboard_input().top().key == 'Q');
|
||||
|
||||
if (!w.keyboard_input().empty()) {
|
||||
if (w.keyboard_input().top().key == 'R') {
|
||||
ball.sprite.pivot.x() = 0.0f;
|
||||
ball.sprite.pivot.y() = 0.9f;
|
||||
}
|
||||
}
|
||||
|
||||
ball.interact(stage);
|
||||
ball.interact(paddle);
|
||||
|
||||
ball.update();
|
||||
|
||||
stage.draw(shader);
|
||||
paddle.draw(shader);
|
||||
ball.draw(shader);
|
||||
|
||||
// ... signalisiere ob wir weitermachen wollen ...
|
||||
return !want_close;
|
||||
})) {
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,16 +1,9 @@
|
|||
set(paradiso_src
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(
|
||||
paradiso
|
||||
${paradiso_src}
|
||||
paradiso_simple
|
||||
simple.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(paradiso
|
||||
target_link_libraries(paradiso_simple
|
||||
paradiso_core
|
||||
)
|
||||
|
||||
target_include_directories(paradiso
|
||||
PRIVATE
|
||||
lib)
|
|
@ -16,66 +16,6 @@
|
|||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
void setup_shaders(paradiso::Shader& shader) {
|
||||
const auto unlit_v = R"(
|
||||
#version 400 core
|
||||
|
||||
layout (location = 0) in vec3 vertices;
|
||||
layout (location = 1) in vec3 normals;
|
||||
layout (location = 2) in vec2 texture_coords;
|
||||
|
||||
// pivot der sprite
|
||||
uniform vec2 pivot = vec2( 0.0, 0.0 );
|
||||
// scale
|
||||
uniform vec2 scale = vec2( 1.0, 1.0 );
|
||||
// rotation
|
||||
uniform float rotation = 0.2;
|
||||
|
||||
// wir sind natuerlich in homogenenen 3D Koordinaten unterwegs
|
||||
mat4 mm = mat4(
|
||||
vec4( scale.x, 0.0, 0.0, 0.0),
|
||||
vec4( 0.0, scale.y, 0.0, 0.0),
|
||||
vec4( 0.0, 0.0, 1.0, 0.0),
|
||||
vec4( pivot, 0.0, 1.0)
|
||||
);
|
||||
|
||||
float sir = sin(rotation);
|
||||
float cor = cos(rotation);
|
||||
|
||||
mat4 mr = mat4(
|
||||
vec4( cor, sir, 0.0, 0.0),
|
||||
vec4(-sir, cor, 0.0, 0.0),
|
||||
vec4( 0.0, 0.0, 1.0, 0.0),
|
||||
vec4( 0.0, 0.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
out vec2 tex_c; // das hier reicht die texturkoordinaten durch
|
||||
|
||||
void main() {
|
||||
tex_c = texture_coords; // umstaendlich aber notwendig
|
||||
gl_Position = mm * mr * vec4(vertices, 1.0); // unsere eigentliche shader position
|
||||
}
|
||||
)";
|
||||
|
||||
const auto unlit_f = R"(
|
||||
#version 400 core
|
||||
|
||||
uniform sampler2D tex_color; // hier ist unsere sprite textur (bitmap)
|
||||
|
||||
in vec2 tex_c; // da sind die texturkoordinaten wieder
|
||||
|
||||
out vec4 frag_color; // das hier wird der output (pixelwert/fragment)
|
||||
|
||||
void main() {
|
||||
frag_color = texture(tex_color,tex_c);
|
||||
})";
|
||||
|
||||
shader.set_source(paradiso::Shader::Type::Vertex, unlit_v);
|
||||
shader.set_source(paradiso::Shader::Type::Fragment, unlit_f);
|
||||
|
||||
shader.build();
|
||||
}
|
||||
|
||||
auto main() -> int {
|
||||
|
||||
// Ausgabefenster ... sieht aus als wäre es auf dem Stack
|
||||
|
@ -85,7 +25,7 @@ auto main() -> int {
|
|||
window
|
||||
.set_size(paradiso::Size{.width = 1280, .height = 720}) // ... Grösse
|
||||
.set_position(paradiso::Point{.x = 100, .y = 100}) // ... Position
|
||||
.set_title("PardiSO") // ... Titel
|
||||
.set_title("PardiSO.Simple") // ... Titel
|
||||
.set_visible(true); // ... und jetzt anzeigen!
|
||||
|
||||
// der Fenster Kontext
|
||||
|
@ -107,36 +47,39 @@ auto main() -> int {
|
|||
// ein Shader (Schattierungsprogramm)
|
||||
auto shader = paradiso::Shader{};
|
||||
|
||||
// hier werden die Shader Programme geladen, kompiliert usw.
|
||||
setup_shaders(shader);
|
||||
// wir nutzen einen vorgefertigten shader
|
||||
shader.load_preset(paradiso::Shader::Preset::Sprite);
|
||||
|
||||
// kein schönes Design: dies sind globale Variablen ...
|
||||
uint8_t slider_value = 0xFF;
|
||||
bool want_close{false};
|
||||
|
||||
// eine sehr rudimentäre Eingabebehandlung. Bei vorhandenen
|
||||
// Eingaben landen diese hier. Wer sich am Design beteiligen
|
||||
// möchte: hier gibt es viel Potential zur Verbesserung ;)
|
||||
window.set_keyboardcallback(
|
||||
[&](auto& w, int key, int scancode, int action, int mods) {
|
||||
if (key == 'Q' || key == 256) // Q oder ESC beenden das Programm
|
||||
// hier "vor-deklariert" der Input-handler für unser Demo
|
||||
auto SimpleKeyboardHandler =
|
||||
[&](const paradiso::Window::KeyboardInputStack& input) {
|
||||
// ohne Input kein Handling ...
|
||||
if (input.empty())
|
||||
return;
|
||||
|
||||
if (input.top().key == 'Q' ||
|
||||
input.top().key == 256) // Q oder ESC beenden das Programm
|
||||
want_close = true;
|
||||
else if (key == 'B') { // kleine Spielerei
|
||||
else if (input.top().key == 'B') { // kleine Spielerei
|
||||
slider_value += 10;
|
||||
} else if (key == 'W') {
|
||||
} else if (input.top().key == 'W') {
|
||||
sprite.pivot.y() += 0.1f;
|
||||
} else if (key == 'S') {
|
||||
} else if (input.top().key == 'S') {
|
||||
sprite.pivot.y() -= 0.1f;
|
||||
} else if (key == 'A') {
|
||||
} else if (input.top().key == 'A') {
|
||||
sprite.pivot.x() -= 0.1f;
|
||||
} else if (key == 'D') {
|
||||
} else if (input.top().key == 'D') {
|
||||
sprite.pivot.x() += 0.1f;
|
||||
} else if (key == 'P') {
|
||||
} else if (input.top().key == 'P') {
|
||||
sprite.scale *= 0.9;
|
||||
} else if (key == 'R') {
|
||||
} else if (input.top().key == 'R') {
|
||||
sprite.rotation += 0.1;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// das update führt den hier mitgegebnen Ausdruck innerhalb der internen
|
||||
// Updates des Fensters auf. Es wird hier auch explizit ein bool gefordert
|
||||
|
@ -152,11 +95,15 @@ auto main() -> int {
|
|||
// Pixeln sollte hier auch eine dementsprechende Grösse eingestellt
|
||||
// werden
|
||||
ctx.set_viewport(paradiso::Rectangle{
|
||||
.position = paradiso::Point{.x = 0, .y = 0},
|
||||
.size =
|
||||
w.client_size().maximal_extent() // wir wollen das
|
||||
// Seitenverhältnis beibehalten
|
||||
});
|
||||
|
||||
// handle keyboard input ...
|
||||
SimpleKeyboardHandler(w.keyboard_input());
|
||||
|
||||
// hier wird das eigentliche löschen des vorherigen Inhalts ausgelöst
|
||||
ctx.clear();
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue