This commit is contained in:
Erris
2026-01-31 10:24:42 +01:00
parent 736591415c
commit fda9bc915c
5 changed files with 602 additions and 6 deletions

View File

@@ -0,0 +1,223 @@
#ifndef OVERLAY_HPP
#define OVERLAY_HPP
/*
"""
Portions of this software are copyright © 2025 The FreeType
Project (https://freetype.org). All rights reserved.
"""
*/
#include <glm/ext/matrix_clip_space.hpp>
#include <open_engine.hpp>
#include "freetype/fttypes.h"
#include "open_engine/events/event.hpp"
#include "open_engine/logging.hpp"
#include "open_engine/renderer/shader.hpp"
#include <freetype2/ft2build.h>
#include FT_FREETYPE_H
#include <glad/glad.h>
#include <map>
struct Character {
unsigned int texture_id; // ID handle of the glyph texture
glm::ivec2 size; // Size of glyph
glm::ivec2 bearing; // Offset from baseline to left/top of glyph
FT_Long advance; // Offset to advance to next glyph
};
class Overlay : public OpenEngine::Layer
{
public:
Overlay()
{};
~Overlay() {};
void RenderText(std::string text, float x, float y, float scale, glm::vec3 color)
{
// activate corresponding render state
text_shader->Bind();
text_shader->SetVec3("textColor", glm::vec3(color.x, color.y, color.z));
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(VAO);
// iterate through all characters
std::string::const_iterator c;
for (c = text.begin(); c != text.end(); c++)
{
Character ch = characters[*c];
float xpos = x + ch.bearing.x * scale;
float ypos = y - (ch.size.y - ch.bearing.y) * scale;
float w = ch.size.x * scale;
float h = ch.size.y * scale;
// update VBO for each character
float vertices[6][4] = {
{ xpos, ypos + h, 0.0f, 0.0f },
{ xpos, ypos, 0.0f, 1.0f },
{ xpos + w, ypos, 1.0f, 1.0f },
{ xpos, ypos + h, 0.0f, 0.0f },
{ xpos + w, ypos, 1.0f, 1.0f },
{ xpos + w, ypos + h, 1.0f, 0.0f }
};
// render glyph texture over quad
glBindTexture(GL_TEXTURE_2D, ch.texture_id);
// update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// render quad
glDrawArrays(GL_TRIANGLES, 0, 6);
// now advance cursors for next glyph (note that advance is number of 1/64 pixels)
x += (ch.advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64)
}
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
glm::vec2 GetTextSize(const std::string& text, float scale)
{
float width = 0.0f;
float height = 0.0f;
for (char c : text)
{
Character ch = characters[c];
width += (ch.advance >> 6) * scale; // Accumulate total width
// Track the maximum height
float char_height = ch.size.y * scale;
if (char_height > height)
height = char_height;
}
return glm::vec2(width, height);
}
void RenderTextCentered(std::string text, float center_x, float center_y, float scale, glm::vec3 color)
{
glm::vec2 text_size = GetTextSize(text, scale);
// Calculate top-left position to make text centered
float x = center_x - (text_size.x / 2.0f);
float y = center_y - (text_size.y / 2.0f);
RenderText(text, x, y, scale, color);
}
private:
void OnAttach() override
{
OE_INFO("Loading Freetype...");
int error;
FT_Library ft;
error = FT_Init_FreeType(&ft);
if (error != 0) {
OE_ERROR("FREETYPE: Could not init FreeType Library");
return;
}
FT_Face face;
error = FT_New_Face(ft, "/usr/share/fonts/TTF/JetBrainsMono-Regular.ttf", 0, &face);
if (error != 0) {
OE_ERROR("FREETYPE: Failed to load font");
return;
}
FT_Set_Pixel_Sizes(face, 0, 48);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for (unsigned char c = 0; c < 128; c++)
{
// load character glyph
if (FT_Load_Char(face, c, FT_LOAD_RENDER))
{
OE_ERROR("FREETYTPE: Failed to load Glyph: {}", c);
continue;
}
// generate texture
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RED,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
GL_RED,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer
);
// set texture options
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// now store character for later use
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
face->glyph->advance.x
};
characters.insert(std::pair<char, Character>(c, character));
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
FT_Done_Face(face);
FT_Done_FreeType(ft);
text_shader = OpenEngine::Shader::Create("assets/shaders/text.glsl");
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
};
void OnUpdate() override
{
text_shader->Bind();
int width = OpenEngine::Application::Get().GetWindow().GetWidth();
int height = OpenEngine::Application::Get().GetWindow().GetHeight();
projection = glm::ortho(0.0f, static_cast<float>(width),
0.0f, static_cast<float>(height));
text_shader->SetMat4("u_ViewProjection", projection);
float x = height - 40;
float y = 0;
//RenderText("Test String", 0.0f, 0.0f, 1.0f, {0.8f, 0.3f, 0.2f});
};
void OnEvent(OpenEngine::Event& event) override
{
};
private:
std::map<char, Character> characters;
OpenEngine::Ref<OpenEngine::Shader> text_shader;
glm::mat4 projection;
unsigned int VAO, VBO;
};
#endif // OVERLAY_HPP

View File

@@ -101,6 +101,7 @@ class SandboxLayer : public OpenEngine::Layer
texture_shader->Bind();
texture_shader->SetInt("u_Texture", 0);
texture_shader->SetVec4("u_Color", glm::vec4(1.0f));
texture_shader->SetFloat("u_TilingFactor", 1.0f);
}
{

View File

@@ -5,6 +5,7 @@
#include <dlfcn.h>
#include "imgui.h"
#include "open_engine/renderer/renderer2d.hpp"
#include <glm/ext/matrix_transform.hpp>
#include <glm/glm.hpp>
@@ -50,10 +51,15 @@ class Sandbox2DLayer : public OpenEngine::Layer
}
{
OE_PROFILE_SCOPE("Drawing Quads");
OpenEngine::Renderer2D::DrawQuad(glm::vec2(0.5f, 0.5f), glm::vec2(0.3f), glm::vec4(color[0], color[1], color[2], color[3]));
OpenEngine::Renderer2D::DrawQuad(glm::vec2(-0.2f, -0.2f), glm::vec2(0.5f, 0.2f), {0.5f, 0.3f, 0.8f, 1.0f});
OpenEngine::Renderer2D::DrawQuad(glm::vec3(0.0f, 0.0f, -0.1f), glm::vec2(1.0f, 1.0f), face);
OpenEngine::Transform tr1 = {glm::vec3(0.5f, 0.5f, 1.0f), glm::vec3(0.3f), 20.0f};
OpenEngine::Transform tr2 = {glm::vec3(-0.2f, -0.2f, 1.0f), glm::vec3(0.5f, 0.2f, 1.0f), 10.0f};
OpenEngine::Transform tr3 = {glm::vec3(0.0f, 0.0f, -0.1f), glm::vec3(1.0f, 1.0f, 1.0f), 45.0f};
OpenEngine::Renderer2D::DrawQuad({glm::vec3(0.5f, 0.5f, 1.0f), glm::vec3(0.3f), 20.0f}, glm::vec4(color[0], color[1], color[2], color[3]));
OpenEngine::Renderer2D::DrawQuad(tr2, {0.5f, 0.3f, 0.8f, 1.0f});
OpenEngine::Renderer2D::DrawQuad(tr3, face);
}
OpenEngine::Renderer2D::EndScene();
}

360
application/include/shmup.hpp Executable file
View File

@@ -0,0 +1,360 @@
#ifndef SHMUP_HPP
#define SHMUP_HPP
#include <iostream>
#include <open_engine.hpp>
#include "imgui.h"
#include "overlay.hpp"
#include <fstream>
#include <dlfcn.h>
#include <glm/ext/matrix_transform.hpp>
#include <glm/glm.hpp>
#include <random>
#include <string>
#include <unistd.h>
#include <vector>
class Shmup : public OpenEngine::Layer
{
public:
Shmup(OpenEngine::Ref<Overlay>& overlay)
: OpenEngine::Layer("shmup"), overlay(overlay),
camera((float)OpenEngine::Application::Get().GetWindow().GetWidth() /
OpenEngine::Application::Get().GetWindow().GetHeight(), 1.0f)
{
}
~Shmup() {};
void OnAttach() override
{
OE_PROFILE_FUNCTION();
{
OE_PROFILE_SCOPE("Texture2D Creation");
ship = OpenEngine::Texture2D::Create("assets/textures/shmup/ship.png");
enemy_ship = OpenEngine::Texture2D::Create("assets/textures/shmup/enemy_ship.png");
bullet_tx = OpenEngine::Texture2D::Create("assets/textures/shmup/bullet.png");
}
tr1 = {glm::vec3(0.0f, -0.9f, 1.0f), glm::vec3(0.1f), 0.0f};
LoadMap();
}
void OnDetach() override
{
}
struct Bounds {
glm::vec2 min; // Bottom-Left
glm::vec2 max; // Top-Right
};
Bounds GetBounds(glm::vec2 position, glm::vec2 scale) {
Bounds b;
// Because the original vertices are -0.5 to 0.5,
// the distance from center to edge is exactly scale * 0.5
glm::vec2 halfScale = scale * 0.5f;
b.min = position - halfScale;
b.max = position + halfScale;
return b;
}
bool CheckCollision(const Bounds& a, const Bounds& b) {
bool overlapX = a.max.x >= b.min.x && b.max.x >= a.min.x;
bool overlapY = a.max.y >= b.min.y && b.max.y >= a.min.y;
return overlapX && overlapY;
}
void MoveShip()
{
double delta = OpenEngine::Time::DeltaTime();
if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Up)) {
tr1.position.y += 1 * delta;
}
if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Down)) {
tr1.position.y -= 1 * delta;
}
if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Right)) {
tr1.position.x += 1 * delta;
}
if (OpenEngine::Input::IsKeyPressed(OpenEngine::KeyCode::Left)) {
tr1.position.x -= 1 * delta;
}
if (tr1.position.y <= -0.9)
tr1.position.y = -0.9;
if (tr1.position.y >= -0.3)
tr1.position.y = -0.3;
if (tr1.position.x <= -0.8)
tr1.position.x = -0.8;
if (tr1.position.x >= 0.8)
tr1.position.x = 0.8;
}
void UpdateEntity()
{
double delta = OpenEngine::Time::DeltaTime();
for (auto& enemy : enemies) {
enemy.position.y -= 0.3 * delta;
if (enemy.position.y <= -1.1 || CheckCollision(
GetBounds(enemy.position, enemy.size),
GetBounds(tr1.position, tr1.size))){
lives--;
}
}
for (auto& bullet : bullets) {
bullet.position.y += 1.1 * delta;
}
std::erase_if(enemies, [](const auto& enemy) {
return enemy.position.y <= -1.1f;
});
std::erase_if(bullets, [](const auto& bullet) {
return bullet.position.y >= 1.2f;
});
for (auto& bullet : bullets) {
for (auto& enemy : enemies) {
Bounds bullet_bounds = GetBounds(bullet.position, bullet.size);
Bounds enemy_bounds = GetBounds(enemy.position, enemy.size);
if (CheckCollision(bullet_bounds, enemy_bounds)) {
std::erase_if(bullets, [&](const OpenEngine::Transform& _bullet) {
return _bullet.position == bullet.position;
});
std::erase_if(enemies, [&](const auto& _enemy) {
return _enemy.position == enemy.position;
});
score++;
}
}
}
}
void GameOver()
{
paused = true;
auto& window = OpenEngine::Application::Get().GetWindow();
int width = window.GetWidth();
int height = window.GetHeight();
enemies.clear();
bullets.clear();
float x = width / 2.0f;
float y = height / 2.0f;
overlay->RenderTextCentered("Game Over!", x, y, 1.0f, glm::vec3(0.8f, 0.2f, 0.3f));
};
void OnUpdate() override
{
float delta = OpenEngine::Time::DeltaTime();
OE_PROFILE_FUNCTION()
{
OE_PROFILE_SCOPE("Setting up Rendering");
//OpenEngine::RenderCommand::SetClearColor({0.11f, 0.11f, 0.15f, 1.0f});
OpenEngine::RenderCommand::SetClearColor({1.0f, 1.0f, 1.0f, 1.0f});
OpenEngine::RenderCommand::Clear();
OpenEngine::Renderer2D::BeginScene(camera.GetCamera());
}
{
OE_PROFILE_SCOPE("Drawing Quads");
OpenEngine::Renderer2D::DrawQuad(tr1, ship);
for (auto& enemy : enemies)
OpenEngine::Renderer2D::DrawQuad(enemy, enemy_ship);
for (auto& bullet : bullets)
OpenEngine::Renderer2D::DrawQuad(bullet, bullet_tx);
}
OpenEngine::Renderer2D::EndScene();
if (!paused) {
if (spawn_cooldown >= spawn_min_time) {
SpawnEnemyRandom();
//ReadMap();
spawn_cooldown = 0;
if (spawn_min_time > 0.5)
spawn_min_time -= 0.01;
}
MoveShip();
UpdateEntity();
if (shooting)
SpawnBullet();
for (auto& enemy : enemies) {
if (CheckCollision(GetBounds(enemy.position, enemy.size), GetBounds(tr1.position, tr1.size)))
game_over = true;
}
shot_cooldown += delta;
spawn_cooldown += delta;
}
if (lives <= 0)
game_over = true;
if (game_over)
GameOver();
std::string s_score = "Score: " + std::to_string(score);
std::string s_lives = std::to_string(lives) + " lives";
overlay->RenderText(s_score, 10, OpenEngine::Application::Get().GetWindow().GetHeight() - 50, 1.0f, {0.8f, 0.2f, 0.3f});
overlay->RenderText(
s_lives,
OpenEngine::Application::Get().GetWindow().GetWidth() - 220,
OpenEngine::Application::Get().GetWindow().GetHeight() - 50,
1.0f,
{0.8f, 0.2f, 0.3f});
}
void Reset()
{
tr1 = {glm::vec3(0.0f, -0.9f, 1.0f), glm::vec3(0.1f), 0.0f};
score = 0;
lives = 5;
game_over = false;
paused = false;
}
void SpawnEnemyRandom()
{
static std::uniform_int_distribution<int> dist{-8, 8};
OpenEngine::Transform enemy_tr({glm::vec3(dist(rd)/10.0f, 1.2f, -0.1f), glm::vec3(0.1f), 180.0f});
enemies.emplace_back(enemy_tr);
};
void SpawnEnemy(int x)
{
OpenEngine::Transform enemy_tr({{x/10.0f, 1.2f, 0.0f}, glm::vec3(0.1f), 180.0f});
enemies.emplace_back(enemy_tr);
};
void LoadMap()
{
std::string buffer;
std::ifstream map_file("assets/maps/lvl1.txt");
while (std::getline(map_file, buffer)) {
map.emplace_back(buffer);
}
};
void ReadMap()
{
static int line_n = 0;
if (line_n >= map.size())
return;
auto line = map[line_n];
int position = -8;
for (int i = 0; i < line.length(); i++) {
if (line[i] == '#')
SpawnEnemy(position);
position++;
}
line_n++;
};
void SpawnBullet()
{
if (shot_cooldown >= 0.2) {
OpenEngine::Transform bullet_tr;
bullet_tr.size = glm::vec3(0.03f);
bullet_tr.position.x = tr1.position.x;
bullet_tr.position.y = tr1.position.y + 0.1;
bullets.emplace_back(bullet_tr);
shot_cooldown = 0;
}
}
bool ProcessKeyPressedEvents(OpenEngine::KeyPressedEvent& event)
{
if (event.GetKeyCode() == OpenEngine::KeyCode::Space) {
shooting = true;
}
if (game_over && event.GetKeyCode() == OpenEngine::KeyCode::Enter) {
Reset();
}
return false;
};
bool ProcessKeyReleased(OpenEngine::KeyReleasedEvent& event)
{
if (event.GetKeyCode() == OpenEngine::KeyCode::Space)
shooting = false;
return false;
}
void OnEvent(OpenEngine::Event& event) override
{
OE_PROFILE_FUNCTION();
OpenEngine::EventDispatcher dispatcher(event);
dispatcher.Dispatch<OpenEngine::KeyPressedEvent>(BIND_EVENT_FN(Shmup::ProcessKeyPressedEvents));
dispatcher.Dispatch<OpenEngine::KeyReleasedEvent>(BIND_EVENT_FN(Shmup::ProcessKeyReleased));
{
OE_PROFILE_SCOPE("Camera OnEvent");
camera.OnEvent(event);
}
}
void OnImGuiRender() override
{
ImGuiIO io = ImGui::GetIO();
static auto m_Font = io.Fonts->AddFontFromFileTTF("/usr/share/fonts/TTF/JetBrainsMono-Regular.ttf", 120.0f);
ImGui::GetForegroundDrawList()->AddText(m_Font, 48.0f, {5.0f, 5.0f}, 0xffffffff, "test");
}
private:
//glm::vec3 square_pos = glm::vec3(0.0f);
//glm::vec4 square_color = glm::vec4(1.0f);
bool paused = false;
bool game_over = false;
//float color[4] = {0.5f, 0.3f, 0.4f, 1.0f};
OpenEngine::Ref<OpenEngine::Texture2D> ship;
OpenEngine::Ref<OpenEngine::Texture2D> enemy_ship;
OpenEngine::Ref<OpenEngine::Texture2D> bullet_tx;
OpenEngine::Transform tr1;
std::vector<OpenEngine::Transform> enemies, bullets;
int score = 0;
int lives = 5;
bool shooting = false;
float spawn_cooldown = 0, shot_cooldown = 0;
float spawn_min_time = 1.0f;
std::vector<std::string> map;
std::random_device rd;
std::mt19937 e{rd()}; // or std::default_random_engine e{rd()};
//std::unordered_map<std::string, unsigned int> bindings;
OpenEngine::OrthographicCameraController camera;
OpenEngine::Ref<Overlay> overlay;
//std::vector<OpenEngine::Time::ProfilingResult> profiling_results;
};
#endif // SHMUP_HPP