345 lines
16 KiB
C++
345 lines
16 KiB
C++
// this file is an absolute clusterfuck to demo the internals.
|
|
// if you know any better please make a pr. please.
|
|
// i hate lines++; but i don't know any better, do i look like i know frontend?
|
|
// branching? use arbitrary lines value, do i look like i know any better?
|
|
#include <cstdio>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <chrono>
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <window.h>
|
|
#include <text.h>
|
|
#include <compositor.h>
|
|
#include <image.h>
|
|
|
|
// --- RAII wrappers for engine objects ---
|
|
struct WindowHandle {
|
|
window_t* ptr;
|
|
WindowHandle(window_t* w) : ptr(w) {}
|
|
~WindowHandle() { if (ptr) window_destroy(ptr); }
|
|
window_t* get() { return ptr; }
|
|
operator window_t* () { return ptr; }
|
|
};
|
|
|
|
struct CompositorHandle {
|
|
compositor_t* ptr;
|
|
CompositorHandle(compositor_t* c) : ptr(c) {}
|
|
~CompositorHandle() { if (ptr) compositor_destroy(ptr); }
|
|
compositor_t* get() { return ptr; }
|
|
operator compositor_t* () { return ptr; }
|
|
};
|
|
|
|
struct TextHandle {
|
|
text_t* ptr;
|
|
TextHandle(text_t* t) : ptr(t) {}
|
|
~TextHandle() { if (ptr) text_destroy(ptr); }
|
|
text_t* get() { return ptr; }
|
|
operator text_t* () { return ptr; }
|
|
};
|
|
|
|
struct ImageHandle {
|
|
uint32_t* ptr;
|
|
ImageHandle(uint32_t* p) : ptr(p) {}
|
|
~ImageHandle() { if (ptr) image_free(ptr); }
|
|
uint32_t* get() { return ptr; }
|
|
operator uint32_t* () { return ptr; }
|
|
};
|
|
|
|
int main() {
|
|
const int WIDTH = 1280;
|
|
const int HEIGHT = 720;
|
|
const int CHAR_WIDTH = 400;
|
|
const int CHAR_HEIGHT = 600;
|
|
|
|
// --- Character Positioning Constants ---
|
|
// The vertical baseline for character sprites, making positioning independent of sprite height.
|
|
const int CHAR_BASELINE_Y = HEIGHT - 140;
|
|
// The horizontal center for the character on the left.
|
|
const int LEFT_CHAR_CENTER_X = 60 + (CHAR_WIDTH / 2);
|
|
// The horizontal center for the character on the right.
|
|
const int RIGHT_CHAR_CENTER_X = WIDTH - 60 - (CHAR_WIDTH / 2);
|
|
// An off-screen position to the left for transitions, based on the final position.
|
|
const int OFFSCREEN_LEFT_X = LEFT_CHAR_CENTER_X - 660;
|
|
|
|
WindowHandle window(window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine"));
|
|
if (!window.get()) {
|
|
printf("Failed to create window\n");
|
|
return 1;
|
|
}
|
|
|
|
CompositorHandle compositor(compositor_init(WIDTH, HEIGHT));
|
|
if (!compositor.get()) {
|
|
printf("Failed to initialize compositor\n");
|
|
return 1;
|
|
}
|
|
|
|
ImageHandle bg(image_load_resized("assets/background.png", WIDTH, HEIGHT));
|
|
if (bg.get()) {
|
|
uint32_t bg_id = compositor_insert(compositor.get(), bg.get(), WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT);
|
|
compositor_set_alpha(compositor.get(), bg_id, 1.0f);
|
|
}
|
|
|
|
ImageHandle textbox(image_load_resized("assets/textbox.png", WIDTH, 180));
|
|
if (textbox.get()) {
|
|
// Position via the bottom-left corner for easier alignment with screen bottom
|
|
compositor_insert(compositor.get(), textbox.get(), WIDTH, 180, 0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT);
|
|
}
|
|
|
|
ImageHandle maksw_happy(image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT));
|
|
// Insert off-screen left, anchored at bottom-center for natural scaling
|
|
uint32_t maksw_happy_id = compositor_insert(compositor.get(), maksw_happy.get(),
|
|
CHAR_WIDTH, CHAR_HEIGHT, OFFSCREEN_LEFT_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
|
|
|
|
ImageHandle maksw_neutral(image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT));
|
|
// Insert at the final left position, anchored at bottom-center
|
|
uint32_t maksw_neutral_id = compositor_insert(compositor.get(), maksw_neutral.get(),
|
|
CHAR_WIDTH, CHAR_HEIGHT, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
|
|
compositor_set_visible(compositor.get(), maksw_neutral_id, false);
|
|
|
|
ImageHandle wikor(image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT));
|
|
// Insert at the final right position, anchored at bottom-center
|
|
uint32_t wikor_id = compositor_insert(compositor.get(), wikor.get(),
|
|
CHAR_WIDTH, CHAR_HEIGHT, RIGHT_CHAR_CENTER_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER);
|
|
|
|
TextHandle name_text(text_init(300, 100));
|
|
text_set_font(name_text.get(), "assets/Aller_Rg.ttf", 40.0f);
|
|
// Text is anchored at top-left, positioned relative to textbox
|
|
uint32_t name_id = compositor_insert(compositor.get(), text_getfb(name_text.get()),
|
|
300, 100, 60, HEIGHT - 180, 4, 1.0f, ANCHOR_TOP_LEFT);
|
|
|
|
TextHandle dialogue_text(text_init(WIDTH - 140, 160));
|
|
text_set_font(dialogue_text.get(), "assets/Vera.ttf", 30.0f);
|
|
// Text is anchored at top-left, positioned relative to textbox
|
|
uint32_t dialogue_id = compositor_insert(compositor.get(), text_getfb(dialogue_text.get()),
|
|
WIDTH - 140, 160, 60, HEIGHT - 130, 4, 1.0f, ANCHOR_TOP_LEFT);
|
|
|
|
ImageHandle mask(image_load_resized("assets/mask.png", 1280, 1080));
|
|
// Use top-left anchor for full-screen wipes
|
|
uint32_t mask_id = compositor_insert(compositor.get(), mask.get(), 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
|
|
compositor_set_visible(compositor.get(), mask_id, false);
|
|
|
|
ImageHandle mask2(image_load_resized("assets/mask2.png", 1280, 1080));
|
|
// Use top-left anchor for full-screen wipes
|
|
uint32_t mask2_id = compositor_insert(compositor.get(), mask2.get(), 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
|
|
compositor_set_visible(compositor.get(), mask2_id, false);
|
|
|
|
// yeah! memory savings!
|
|
//std::unique_ptr<uint32_t[]> black(new uint32_t[WIDTH * HEIGHT]);
|
|
//for (int i = 0; i < WIDTH * HEIGHT; i++) black[i] = 0xFF000000;
|
|
//// Use top-left anchor for full-screen fade
|
|
//uint32_t black_id = compositor_insert(compositor.get(), black.get(), WIDTH, HEIGHT, 0, 0, 6, 1.0f, ANCHOR_TOP_LEFT);
|
|
//compositor_set_visible(compositor.get(), black_id, false);
|
|
//compositor_set_alpha(compositor.get(), black_id, 0.0f);
|
|
|
|
// Animate character into position using the new centered coordinates
|
|
compositor_animate_move(compositor.get(), maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1.0f, EASE_OUT_CUBIC);
|
|
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, true);
|
|
text_push(name_text.get(), "maksw", 0.0f, true);
|
|
text_push(dialogue_text.get(),
|
|
"Hello and welcome to the Obwarzanek Narrative Engine demo!",
|
|
30.0f, true);
|
|
|
|
bool running = true;
|
|
|
|
auto last_frame_time = std::chrono::high_resolution_clock::now();
|
|
auto last_report_time = last_frame_time;
|
|
|
|
int frame_count = 0;
|
|
float frame_time_sum = 0.0f;
|
|
const std::chrono::duration<float> REPORT_INTERVAL = std::chrono::seconds(5);
|
|
|
|
while (running) {
|
|
if (window_pollevent(window.get())) running = false;
|
|
|
|
auto current_time = std::chrono::high_resolution_clock::now();
|
|
std::chrono::duration<float> delta_duration = current_time - last_frame_time;
|
|
float delta_time_sec = delta_duration.count();
|
|
last_frame_time = current_time;
|
|
|
|
frame_count++;
|
|
frame_time_sum += delta_time_sec;
|
|
|
|
std::chrono::duration<float> time_since_last_report = current_time - last_report_time;
|
|
|
|
if (time_since_last_report >= REPORT_INTERVAL) {
|
|
if (frame_count > 0) {
|
|
float avg_delta_time = frame_time_sum / (float)frame_count;
|
|
float fps = 1.0f / avg_delta_time;
|
|
float actual_duration = time_since_last_report.count();
|
|
|
|
printf("FPS: %.2f (over %d frames in %.2f seconds)\n",
|
|
fps, frame_count, actual_duration);
|
|
}
|
|
frame_count = 0;
|
|
frame_time_sum = 0.0f;
|
|
last_report_time = current_time;
|
|
}
|
|
|
|
static bool mouse_was_down = false;
|
|
bool mouse_is_down = window_is_mouse_button_down(window.get(), MOUSE_BUTTON_LEFT);
|
|
bool mouse_clicked = mouse_is_down && !mouse_was_down;
|
|
mouse_was_down = mouse_is_down;
|
|
|
|
static bool key_was_down = false;
|
|
bool key_is_down = window_is_key_down(window.get(), KEY_SPACE);
|
|
bool key_pressed = key_is_down && !key_was_down;
|
|
key_was_down = key_is_down;
|
|
|
|
// don't judge me, it will work for now
|
|
static unsigned int lines = 2;
|
|
static bool dialog_finished = false;
|
|
static bool transitioning = false;
|
|
|
|
if (mouse_clicked || key_pressed) {
|
|
if (text_is_animating(dialogue_text.get())) {
|
|
text_show_all_current(dialogue_text.get());
|
|
}
|
|
else {
|
|
switch (lines) {
|
|
case 2:
|
|
compositor_animation_complete_all(compositor.get());
|
|
text_clear(dialogue_text.get());
|
|
text_push(dialogue_text.get(), "Today I'll present to you-", 30.0f, true);
|
|
lines++;
|
|
break;
|
|
case 3:
|
|
compositor_animation_complete_all(compositor.get());
|
|
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
text_clear(dialogue_text.get());
|
|
text_clear(name_text.get());
|
|
text_push(name_text.get(), "wikor", 0.0f, true);
|
|
text_push(dialogue_text.get(), "Yeah, hello to you too.", 30.0f, true);
|
|
lines++;
|
|
break;
|
|
case 4:
|
|
compositor_animation_complete_all(compositor.get());
|
|
compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
text_clear(dialogue_text.get());
|
|
text_clear(name_text.get());
|
|
text_push(name_text.get(), "maksw", 0.0f, true);
|
|
text_push(dialogue_text.get(), "Oh Wiktor, what a nice suprise!", 30.0f, true);
|
|
lines++;
|
|
break;
|
|
case 5:
|
|
compositor_animation_complete_all(compositor.get());
|
|
text_clear(dialogue_text.get());
|
|
text_push(dialogue_text.get(), "Do you need something?", 30.0f, true);
|
|
lines++;
|
|
break;
|
|
case 6:
|
|
compositor_animation_complete_all(compositor.get());
|
|
compositor_animate_scale(compositor.get(), maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
text_clear(dialogue_text.get());
|
|
text_clear(name_text.get());
|
|
text_push(name_text.get(), "wikor", 0.0f, true);
|
|
text_push(dialogue_text.get(), "No, i was just walking by.", 30.0f, true);
|
|
lines++;
|
|
break;
|
|
case 7:
|
|
compositor_animation_complete_all(compositor.get());
|
|
text_clear(dialogue_text.get());
|
|
text_push(dialogue_text.get(), "Cool engine though.", 30.0f, true);
|
|
lines++;
|
|
break;
|
|
case 8:
|
|
// fade transition
|
|
compositor_animation_complete_all(compositor.get());
|
|
compositor_set_visible(compositor.get(), mask_id, true);
|
|
compositor_animate_move(compositor.get(), mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC, false);
|
|
transitioning = true;
|
|
lines++;
|
|
break;
|
|
case 9:
|
|
// some fucking how?
|
|
break;
|
|
case 10:
|
|
compositor_animation_complete_all(compositor.get());
|
|
compositor_animate_scale(compositor.get(), wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
text_clear(dialogue_text.get());
|
|
text_clear(name_text.get());
|
|
text_push(name_text.get(), "maksw", 0.0f, true);
|
|
text_push(dialogue_text.get(), "Can't complain, the winter break is just a week away. Plus the compositor is finally cooperating.", 30.0f, true);
|
|
lines++;
|
|
break;
|
|
case 11:
|
|
compositor_animation_complete_all(compositor.get());
|
|
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
compositor_animate_scale(compositor.get(), wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
|
text_clear(dialogue_text.get());
|
|
text_clear(name_text.get());
|
|
text_push(name_text.get(), "wikor", 0.0f, true);
|
|
text_push(dialogue_text.get(), "Sure thing..", 30.0f, true);
|
|
lines++;
|
|
break;
|
|
case 12:
|
|
compositor_animation_complete_all(compositor.get());
|
|
text_clear(dialogue_text.get());
|
|
text_push(dialogue_text.get(), "You're talking to me like i know anything about computers.", 30.0f, true);
|
|
lines++;
|
|
break;
|
|
case 13:
|
|
// fade to black and end
|
|
compositor_animation_complete_all(compositor.get());
|
|
compositor_animate_scale(compositor.get(), maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
//compositor_set_visible(compositor.get(), black_id, true);
|
|
//compositor_animate_fade(compositor.get(), black_id, 1.0f, 0.7f, EASE_IN_CUBIC, false);
|
|
// yeah! memory savings!
|
|
compositor_set_visible(compositor.get(), mask_id, true);
|
|
compositor_set_alpha(compositor.get(), mask_id, 0.0f);
|
|
compositor_animate_fade(compositor.get(), mask_id, 1.0f, 0.8f, EASE_IN_OUT_CUBIC, false);
|
|
dialog_finished = true;
|
|
lines++;
|
|
break;
|
|
default:
|
|
running = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
compositor_tick(compositor.get(), delta_time_sec);
|
|
|
|
if (transitioning) {
|
|
if (!compositor_is_element_animating(compositor.get(), mask_id)) {
|
|
compositor_set_visible(compositor.get(), maksw_happy_id, false);
|
|
compositor_set_visible(compositor.get(), maksw_neutral_id, true);
|
|
compositor_set_scale(compositor.get(), maksw_neutral_id, 1.0f);
|
|
compositor_set_scale(compositor.get(), wikor_id, 1.05f);
|
|
text_clear(dialogue_text.get());
|
|
text_clear(name_text.get());
|
|
text_push(name_text.get(), "wikor", 0.0f, true);
|
|
text_push(dialogue_text.get(), "Anyways, how's your day going?", 30.0f, true);
|
|
|
|
compositor_set_visible(compositor.get(), mask_id, false);
|
|
compositor_set_visible(compositor.get(), mask2_id, true);
|
|
compositor_animate_move(compositor.get(), mask2_id, 0, HEIGHT, 0.5f, EASE_OUT_CUBIC, false);
|
|
|
|
transitioning = false;
|
|
lines++;
|
|
}
|
|
}
|
|
|
|
if (dialog_finished) {
|
|
compositor_mark_content_dirty(compositor.get(), mask_id);
|
|
if (!compositor_is_element_animating(compositor.get(), mask_id)) {
|
|
running = false;
|
|
}
|
|
}
|
|
|
|
if (text_nextframe(name_text.get(), delta_time_sec))
|
|
compositor_mark_content_dirty(compositor.get(), name_id);
|
|
if (text_nextframe(dialogue_text.get(), delta_time_sec))
|
|
compositor_mark_content_dirty(compositor.get(), dialogue_id);
|
|
|
|
const uint32_t* fb = compositor_compose(compositor.get());
|
|
window_blit(window.get(), fb);
|
|
}
|
|
|
|
return 0;
|
|
}
|