From aef9db4c9633cb2fc2f81beb1c45c2f35800325e Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Sat, 11 Jun 2022 18:12:53 +0800 Subject: [PATCH] Add credits state and language menu state --- CMakeLists.txt | 1 + src/animation/animator.cpp | 17 ++ src/animation/animator.hpp | 7 + src/application.cpp | 40 ++++- src/application.hpp | 9 + src/game/context.hpp | 28 ++- src/game/fonts.cpp | 178 +++++++++++++++++++ src/game/fonts.hpp | 31 ++++ src/game/states/boot.cpp | 18 +- src/game/states/credits.cpp | 142 +++++++++++++++ src/game/states/credits.hpp | 39 +++++ src/game/states/language-menu.cpp | 279 ++++++++++++++++++++++++++++++ src/game/states/language-menu.hpp | 39 +++++ src/game/states/loading.cpp | 109 +----------- src/game/states/main-menu.cpp | 255 +++++++++++++++++++-------- src/game/states/main-menu.hpp | 2 +- src/game/states/options-menu.cpp | 213 +++++++++++++++++++++++ src/game/states/options-menu.hpp | 39 +++++ src/game/states/splash.cpp | 117 +++++++++---- src/game/states/title.cpp | 212 ++++++++++++++++------- 20 files changed, 1486 insertions(+), 289 deletions(-) create mode 100644 src/game/fonts.cpp create mode 100644 src/game/fonts.hpp create mode 100644 src/game/states/credits.cpp create mode 100644 src/game/states/credits.hpp create mode 100644 src/game/states/language-menu.cpp create mode 100644 src/game/states/language-menu.hpp create mode 100644 src/game/states/options-menu.cpp create mode 100644 src/game/states/options-menu.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f0f857..d3614e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.7) + option(VERSION_STRING "Project version string" "0.0.0") project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX) diff --git a/src/animation/animator.cpp b/src/animation/animator.cpp index 33d81e1..bf1fa7e 100644 --- a/src/animation/animator.cpp +++ b/src/animation/animator.cpp @@ -19,22 +19,36 @@ #include "animator.hpp" #include "animation/animation.hpp" +#include + +animator::animator(): + animating(0) +{} void animator::animate(double dt) { + // Advance animations + ++animating; for (animation_base* animation: animations) { animation->advance(dt); } + --animating; } void animator::add_animation(animation_base* animation) { + if (animating) + throw std::runtime_error("Attempting to add animation to animator while animating"); + animations.emplace(animation); } void animator::remove_animation(animation_base* animation) { + if (animating) + throw std::runtime_error("Attempting to remove animation from animator while animating"); + auto it = animations.find(animation); if (it != animations.end()) { @@ -44,5 +58,8 @@ void animator::remove_animation(animation_base* animation) void animator::remove_animations() { + if (animating) + throw std::runtime_error("Attempting to remove animations from animator while animating"); + animations.clear(); } diff --git a/src/animation/animator.hpp b/src/animation/animator.hpp index 6bbb90a..9f7fe8b 100644 --- a/src/animation/animator.hpp +++ b/src/animation/animator.hpp @@ -20,6 +20,7 @@ #ifndef ANTKEEPER_ANIMATOR_HPP #define ANTKEEPER_ANIMATOR_HPP +#include #include class animation_base; @@ -30,6 +31,9 @@ class animation_base; class animator { public: + /// Constructs an animator. + animator(); + /** * Progresses all active animations by @p dt. * @@ -56,6 +60,9 @@ public: private: std::unordered_set animations; + std::list animations_to_add; + std::list animations_to_remove; + int animating; }; #endif // ANTKEEPER_ANIMATOR_HPP diff --git a/src/application.cpp b/src/application.cpp index 01d61f1..db1ac72 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -47,6 +47,7 @@ application::application(): vsync(true), cursor_visible(true), display_dimensions({0, 0}), + display_dpi(0.0f), window_dimensions({0, 0}), viewport_dimensions({0, 0}), mouse_position({0, 0}), @@ -97,6 +98,8 @@ application::application(): SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); @@ -104,15 +107,25 @@ application::application(): SDL_DisplayMode sdl_desktop_display_mode; if (SDL_GetDesktopDisplayMode(0, &sdl_desktop_display_mode) != 0) { - logger->error("Failed to get desktop display mode: \"" + std::string(SDL_GetError()) + "\""); + logger->error("Failed to detect desktop display mode: \"" + std::string(SDL_GetError()) + "\""); throw std::runtime_error("Failed to detect desktop display mode"); } else { - logger->log("Detected " + std::to_string(sdl_desktop_display_mode.w) + "x" + std::to_string(sdl_desktop_display_mode.h) + " display"); display_dimensions = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h}; } + // Get display DPI + if (SDL_GetDisplayDPI(0, &display_dpi, nullptr, nullptr) != 0) + { + logger->error("Failed to detect display DPI: \"" + std::string(SDL_GetError()) + "\""); + throw std::runtime_error("Failed to detect display DPI"); + } + else + { + logger->log("Detected " + std::to_string(sdl_desktop_display_mode.w) + "x" + std::to_string(sdl_desktop_display_mode.h) + " display with " + std::to_string(display_dpi) + " DPI"); + } + // Create a hidden fullscreen window logger->push_task("Creating " + std::to_string(display_dimensions[0]) + "x" + std::to_string(display_dimensions[1]) + " window"); sdl_window = SDL_CreateWindow @@ -259,6 +272,21 @@ int application::execute(const application::state& initial_state) // Schedule frames until closed while (!closed) { + translate_sdl_events(); + + // Enter queued state (if any) + if (queued_state.enter != nullptr || queued_state.exit != nullptr) + { + // Make a copy of the queued state + application::state queued_state_copy = queued_state; + + // Clear the queued state + queued_state = {std::string(), nullptr, nullptr}; + + // Enter the queued state + change_state(queued_state_copy); + } + // Tick frame scheduler frame_scheduler->tick(); @@ -354,13 +382,19 @@ std::shared_ptr application::capture_frame() const int h = viewport_dimensions[1]; std::shared_ptr frame = std::make_shared(); - frame->format(3, false); + frame->format(1, 3); frame->resize(w, h); + + logger->log("starting read"); // Read pixel data from framebuffer into image glReadBuffer(GL_BACK); + + logger->log("buffer read"); glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, frame->get_pixels()); + logger->log("ending read"); + return std::move(frame); } diff --git a/src/application.hpp b/src/application.hpp index 429b58b..5f1e614 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -194,6 +194,9 @@ public: /// Returns the dimensions of the current display. const std::array& get_display_dimensions() const; + /// Returns the DPI of the display. + float get_display_dpi() const; + /// Returns the dimensions of the window. const std::array& get_window_dimensions() const; @@ -238,6 +241,7 @@ private: bool vsync; bool cursor_visible; std::array display_dimensions; + float display_dpi; std::array window_dimensions; std::array viewport_dimensions; std::array mouse_position; @@ -273,6 +277,11 @@ inline const std::array& application::get_display_dimensions() const return display_dimensions; } +inline float application::get_display_dpi() const +{ + return display_dpi; +} + inline const std::array& application::get_window_dimensions() const { return window_dimensions; diff --git a/src/game/context.hpp b/src/game/context.hpp index 7e6f4e2..40634d7 100644 --- a/src/game/context.hpp +++ b/src/game/context.hpp @@ -43,6 +43,7 @@ #include #include #include +#include #include "resources/json.hpp" #include "type/typeface.hpp" #include "type/bitmap-font.hpp" @@ -141,6 +142,7 @@ struct context // Localization std::string language_code; + int language_count; int language_index; string_table* string_table; string_table_map string_table_map; @@ -200,12 +202,26 @@ struct context scene::billboard* splash_billboard; scene::billboard* camera_flash_billboard; scene::text* title_text; - scene::text* title_version_text; + scene::text* title_press_any_key_text; scene::text* main_menu_start_text; scene::text* main_menu_options_text; scene::text* main_menu_credits_text; scene::text* main_menu_quit_text; - scene::model_instance* ui_pointer; + int main_menu_index; + scene::text* credits_text; + scene::text* options_menu_controls_text; + scene::text* options_menu_graphics_text; + scene::text* options_menu_sound_text; + scene::text* options_menu_language_text; + scene::text* options_menu_back_text; + std::vector options_menu_texts; + std::vector> options_menu_callbacks; + int options_menu_index; + scene::text* language_menu_language_text; + scene::text* language_menu_back_text; + std::vector language_menu_texts; + std::vector> language_menu_callbacks; + int language_menu_index; // Surface scene scene::collection* surface_scene; @@ -229,6 +245,14 @@ struct context animation* equip_tool_animation; animation* unequip_tool_animation; animation* camera_flash_animation; + animation* splash_fade_in_animation; + animation* splash_fade_out_animation; + animation* title_fade_in_animation; + animation* title_fade_out_animation; + animation* title_press_any_key_animation; + animation* main_menu_fade_animation; + animation* credits_fade_in_animation; + animation* credits_scroll_animation; // Controls input::event_router* input_event_router; diff --git a/src/game/fonts.cpp b/src/game/fonts.cpp new file mode 100644 index 0000000..8f2ed98 --- /dev/null +++ b/src/game/fonts.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "game/fonts.hpp" +#include "application.hpp" +#include "type/type.hpp" +#include "resources/resource-manager.hpp" +#include "gl/texture-wrapping.hpp" +#include "gl/texture-filter.hpp" +#include "render/material.hpp" +#include "render/material-flags.hpp" +#include + +namespace game { + +static void build_bitmap_font(const type::typeface& typeface, float size, const std::unordered_set& charset, type::bitmap_font& font, render::material& material, gl::shader_program* shader_program) +{ + // Get font metrics for given size + if (type::font_metrics metrics; typeface.get_metrics(size, metrics)) + font.set_font_metrics(metrics); + + // Format font bitmap + image& font_bitmap = font.get_bitmap(); + font_bitmap.format(sizeof(std::byte), 1); + + // For each UTF-32 character code in the character set + for (char32_t code: charset) + { + // Skip missing glyphs + if (!typeface.has_glyph(code)) + continue; + + // Add glyph to font + type::bitmap_glyph& glyph = font[code]; + typeface.get_metrics(size, code, glyph.metrics); + typeface.get_bitmap(size, code, glyph.bitmap); + } + + // Pack glyph bitmaps into the font bitmap + font.pack(); + + // Create font texture from bitmap + gl::texture_2d* font_texture = new gl::texture_2d(font_bitmap.get_width(), font_bitmap.get_height(), gl::pixel_type::uint_8, gl::pixel_format::r, gl::color_space::linear, font_bitmap.get_pixels()); + font_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); + font_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); + + // Create font material + material.set_flags(MATERIAL_FLAG_TRANSLUCENT); + material.add_property("font_bitmap")->set_value(font_texture); + material.set_shader_program(shader_program); +} + +void load_fonts(game::context* ctx) +{ + // Load dyslexia-friendly typeface (if enabled) + bool dyslexia_font = false; + if (ctx->config->contains("dyslexia_font")) + { + dyslexia_font = (*ctx->config)["dyslexia_font"].get(); + + if (dyslexia_font) + { + if (auto it = ctx->strings->find("font_dyslexia"); it != ctx->strings->end() && !it->second.empty() && it->second[0] != '#') + { + ctx->logger->log(it->second); + ctx->typefaces["dyslexia"] = ctx->resource_manager->load(it->second); + } + else + { + dyslexia_font = false; + } + } + } + + // Load typefaces + if (dyslexia_font) + { + // Override standard typefaces with dyslexia-friendly typeface + ctx->typefaces["serif"] = ctx->typefaces["dyslexia"]; + ctx->typefaces["sans_serif"] = ctx->typefaces["dyslexia"]; + ctx->typefaces["monospace"] = ctx->typefaces["dyslexia"]; + } + else + { + // Load standard typefaces + if (auto it = ctx->strings->find("font_serif"); it != ctx->strings->end()) + ctx->typefaces["serif"] = ctx->resource_manager->load(it->second); + if (auto it = ctx->strings->find("font_sans_serif"); it != ctx->strings->end()) + ctx->typefaces["sans_serif"] = ctx->resource_manager->load(it->second); + if (auto it = ctx->strings->find("font_monospace"); it != ctx->strings->end()) + ctx->typefaces["monospace"] = ctx->resource_manager->load(it->second); + } + + // Build character set + std::unordered_set charset; + { + // Add all character codes from the basic Latin unicode block + for (char32_t code = type::unicode::block::basic_latin.first; code <= type::unicode::block::basic_latin.last; ++code) + charset.insert(code); + + // Add all character codes from game strings + for (auto it = ctx->strings->begin(); it != ctx->strings->end(); ++it) + { + // Convert UTF-8 string to UTF-32 + std::wstring_convert, char32_t> convert; + std::u32string u32 = convert.from_bytes(it->second); + + /// Insert each character code from the UTF-32 string into the character set + for (char32_t code: u32) + charset.insert(code); + } + } + + // Load bitmap font shader + gl::shader_program* bitmap_font_shader = ctx->resource_manager->load("bitmap-font.glsl"); + + // Determine font point sizes + float debug_font_size_pt = 12.0f; + float menu_font_size_pt = 12.0f; + float title_font_size_pt = 12.0f; + if (ctx->config->contains("debug_font_size")) + debug_font_size_pt = (*ctx->config)["debug_font_size"].get(); + if (ctx->config->contains("menu_font_size")) + menu_font_size_pt = (*ctx->config)["menu_font_size"].get(); + if (ctx->config->contains("title_font_size")) + title_font_size_pt = (*ctx->config)["title_font_size"].get(); + + // Scale font point sizes + if (ctx->config->contains("font_scale")) + { + const float font_scale = (*ctx->config)["font_scale"].get(); + debug_font_size_pt *= font_scale; + menu_font_size_pt *= font_scale; + title_font_size_pt *= font_scale; + } + + // Convert font point sizes to pixel sizes + const float dpi = ctx->app->get_display_dpi(); + const float debug_font_size_px = (debug_font_size_pt * dpi) / 72.0f; + const float menu_font_size_px = (menu_font_size_pt * dpi) / 72.0f; + const float title_font_size_px = (title_font_size_pt * dpi) / 72.0f; + + // Build debug font + if (auto it = ctx->typefaces.find("monospace"); it != ctx->typefaces.end()) + { + build_bitmap_font(*it->second, debug_font_size_px, charset, ctx->debug_font, ctx->debug_font_material, bitmap_font_shader); + } + + // Build menu font + if (auto it = ctx->typefaces.find("sans_serif"); it != ctx->typefaces.end()) + { + build_bitmap_font(*it->second, menu_font_size_px, charset, ctx->menu_font, ctx->menu_font_material, bitmap_font_shader); + } + + // Build title font + if (auto it = ctx->typefaces.find("serif"); it != ctx->typefaces.end()) + { + build_bitmap_font(*it->second, title_font_size_px, charset, ctx->title_font, ctx->title_font_material, bitmap_font_shader); + } +} + +} // namespace game diff --git a/src/game/fonts.hpp b/src/game/fonts.hpp new file mode 100644 index 0000000..8fabdfb --- /dev/null +++ b/src/game/fonts.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GAME_FONTS_HPP +#define ANTKEEPER_GAME_FONTS_HPP + +#include "game/context.hpp" + +namespace game { + +void load_fonts(game::context* ctx); + +} // namespace game + +#endif // ANTKEEPER_GAME_FONTS_HPP diff --git a/src/game/states/boot.cpp b/src/game/states/boot.cpp index 9f80b0e..9cb3dc2 100644 --- a/src/game/states/boot.cpp +++ b/src/game/states/boot.cpp @@ -381,10 +381,13 @@ void load_strings(game::context* ctx) for (int i = 2; i < (*ctx->string_table)[0].size(); ++i) { if ((*ctx->string_table)[0][i] == ctx->language_code) - ctx->language_index = i; + ctx->language_index = i - 2; } - logger->log("lang index: " + std::to_string(ctx->language_index)); + ctx->language_count = (*ctx->string_table)[0].size() - 2; + logger->log("language count: " + std::to_string(ctx->language_count)); + logger->log("language index: " + std::to_string(ctx->language_index)); + logger->log("language code: " + ctx->language_code); ctx->strings = &ctx->string_table_map[ctx->language_code]; @@ -439,7 +442,7 @@ void setup_window(game::context* ctx) app->set_vsync(vsync); // Set title - app->set_title((*ctx->strings)["title"]); + app->set_title((*ctx->strings)["application_title"]); // Show window ctx->app->get_rasterizer()->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f); @@ -694,6 +697,7 @@ void setup_scenes(game::context* ctx) const gl::texture_2d* splash_texture = ctx->resource_manager->load("splash.tex"); auto splash_dimensions = splash_texture->get_dimensions(); ctx->splash_billboard_material = new render::material(); + ctx->splash_billboard_material->set_flags(MATERIAL_FLAG_TRANSLUCENT); ctx->splash_billboard_material->set_shader_program(ctx->resource_manager->load("ui-element-textured.glsl")); ctx->splash_billboard_material->add_property("background")->set_value(splash_texture); ctx->splash_billboard_material->add_property("tint")->set_value(float4{1, 1, 1, 1}); @@ -790,14 +794,14 @@ void setup_animation(game::context* ctx) // Create inner radial transition ctx->radial_transition_inner = new screen_transition(); ctx->radial_transition_inner->get_material()->set_shader_program(ctx->resource_manager->load("radial-transition-inner.glsl")); - ctx->ui_scene->add_object(ctx->radial_transition_inner->get_billboard()); - ctx->animator->add_animation(ctx->radial_transition_inner->get_animation()); + //ctx->ui_scene->add_object(ctx->radial_transition_inner->get_billboard()); + //ctx->animator->add_animation(ctx->radial_transition_inner->get_animation()); // Create outer radial transition ctx->radial_transition_outer = new screen_transition(); ctx->radial_transition_outer->get_material()->set_shader_program(ctx->resource_manager->load("radial-transition-outer.glsl")); - ctx->ui_scene->add_object(ctx->radial_transition_outer->get_billboard()); - ctx->animator->add_animation(ctx->radial_transition_outer->get_animation()); + //ctx->ui_scene->add_object(ctx->radial_transition_outer->get_billboard()); + //ctx->animator->add_animation(ctx->radial_transition_outer->get_animation()); // Create camera flash animation ctx->camera_flash_animation = new animation(); diff --git a/src/game/states/credits.cpp b/src/game/states/credits.cpp new file mode 100644 index 0000000..32fa6ea --- /dev/null +++ b/src/game/states/credits.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "game/states/credits.hpp" +#include "game/states/main-menu.hpp" +#include "animation/ease.hpp" +#include "animation/animation.hpp" +#include "animation/animator.hpp" +#include "application.hpp" +#include "scene/text.hpp" +#include "render/passes/clear-pass.hpp" + +namespace game { +namespace state { +namespace credits { + +void enter(game::context* ctx) +{ + ctx->ui_clear_pass->set_cleared_buffers(true, true, false); + + // Construct credits text + ctx->credits_text = new scene::text(); + ctx->credits_text->set_material(&ctx->menu_font_material); + ctx->credits_text->set_font(&ctx->menu_font); + ctx->credits_text->set_color({1.0f, 1.0f, 1.0f, 0.0f}); + ctx->credits_text->set_content((*ctx->strings)["credits"]); + + // Align credits text + const auto& credits_aabb = static_cast&>(ctx->credits_text->get_local_bounds()); + float credits_w = credits_aabb.max_point.x - credits_aabb.min_point.x; + float credits_h = credits_aabb.max_point.y - credits_aabb.min_point.y; + ctx->credits_text->set_translation({std::round(-credits_w * 0.5f), std::round(-credits_h * 0.5f), 0.0f}); + + // Load animation timing configuration + double credits_fade_in_duration = 0.0; + double credits_scroll_duration = 0.0; + if (ctx->config->contains("credits_fade_in_duration")) + credits_fade_in_duration = (*ctx->config)["credits_fade_in_duration"].get(); + if (ctx->config->contains("credits_scroll_duration")) + credits_scroll_duration = (*ctx->config)["credits_scroll_duration"].get(); + + auto set_credits_opacity = [ctx](int channel, const float& opacity) + { + ctx->credits_text->set_color({1.0f, 1.0f, 1.0f, opacity}); + }; + + // Build credits fade in animation + ctx->credits_fade_in_animation = new animation(); + animation_channel* credits_fade_in_opacity_channel = ctx->credits_fade_in_animation->add_channel(0); + ctx->credits_fade_in_animation->set_interpolator(ease::in_quad); + credits_fade_in_opacity_channel->insert_keyframe({0.0, 0.0f}); + credits_fade_in_opacity_channel->insert_keyframe({credits_fade_in_duration, 1.0f}); + ctx->credits_fade_in_animation->set_frame_callback(set_credits_opacity); + + // Build credits scroll in animation + ctx->credits_scroll_animation = new animation(); + + // Trigger credits scroll animation after credits fade in animation ends + ctx->credits_fade_in_animation->set_end_callback + ( + [ctx]() + { + ctx->credits_scroll_animation->play(); + } + ); + + // Add credits animations to animator + ctx->animator->add_animation(ctx->credits_fade_in_animation); + ctx->animator->add_animation(ctx->credits_scroll_animation); + + // Start credits fade in animation + ctx->credits_fade_in_animation->play(); + + // Set up credits skipper + ctx->input_listener->set_callback + ( + [ctx](const event_base& event) + { + auto id = event.get_event_type_id(); + if (id != mouse_moved_event::event_type_id && id != mouse_wheel_scrolled_event::event_type_id && id != gamepad_axis_moved_event::event_type_id) + { + if (ctx->credits_text->get_color()[3] > 0.0f) + { + ctx->input_listener->set_enabled(false); + + // Change state + application::state next_state; + next_state.name = "main_menu"; + next_state.enter = std::bind(game::state::main_menu::enter, ctx, 2); + next_state.exit = std::bind(game::state::main_menu::exit, ctx); + ctx->app->change_state(next_state); + } + } + } + ); + ctx->input_listener->set_enabled(true); + + ctx->ui_scene->add_object(ctx->credits_text); + ctx->credits_text->update_tweens(); +} + +void exit(game::context* ctx) +{ + // Disable credits skipper + ctx->input_listener->set_enabled(false); + ctx->input_listener->set_callback(nullptr); + + // Destruct credits text + ctx->ui_scene->remove_object(ctx->credits_text); + delete ctx->credits_text; + ctx->credits_text = nullptr; + + // Destruct credits animations + ctx->animator->remove_animation(ctx->credits_fade_in_animation); + ctx->animator->remove_animation(ctx->credits_scroll_animation); + delete ctx->credits_fade_in_animation; + delete ctx->credits_scroll_animation; + ctx->credits_fade_in_animation = nullptr; + ctx->credits_scroll_animation = nullptr; + + ctx->ui_clear_pass->set_cleared_buffers(false, true, false); +} + +} // namespace credits +} // namespace state +} // namespace game diff --git a/src/game/states/credits.hpp b/src/game/states/credits.hpp new file mode 100644 index 0000000..6bbf69c --- /dev/null +++ b/src/game/states/credits.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GAME_STATE_CREDITS_HPP +#define ANTKEEPER_GAME_STATE_CREDITS_HPP + +#include "game/context.hpp" + +namespace game { +namespace state { + +/// Credits screen game state functions. +namespace credits { + +void enter(game::context* ctx); +void exit(game::context* ctx); + +} // namespace credits + +} // namespace state +} // namespace game + +#endif // ANTKEEPER_GAME_STATE_CREDITS_HPP diff --git a/src/game/states/language-menu.cpp b/src/game/states/language-menu.cpp new file mode 100644 index 0000000..7563fb8 --- /dev/null +++ b/src/game/states/language-menu.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "game/states/language-menu.hpp" +#include "game/states/options-menu.hpp" +#include "application.hpp" +#include "scene/text.hpp" +#include "render/passes/clear-pass.hpp" +#include "debug/logger.hpp" +#include "game/fonts.hpp" + +namespace game { +namespace state { +namespace language_menu { + +static void update_text_font(game::context* ctx) +{ + for (scene::text* text: ctx->language_menu_texts) + { + text->set_material(&ctx->menu_font_material); + text->set_font(&ctx->menu_font); + } +} + +static void update_text_color(game::context* ctx) +{ + float4 inactive_color = {1.0f, 1.0f, 1.0f, 0.5f}; + float4 active_color = {1.0f, 1.0f, 1.0f, 1.0f}; + + for (std::size_t i = 0; i < ctx->language_menu_texts.size(); ++i) + { + scene::text* text = ctx->language_menu_texts[i]; + + if (i == ctx->language_menu_index) + text->set_color(active_color); + else + text->set_color(inactive_color); + } +} + +static void update_text_content(game::context* ctx) +{ + ctx->language_menu_language_text->set_content((*ctx->strings)["language_name"]); + ctx->language_menu_back_text->set_content((*ctx->strings)["back"]); +} + +static void refresh_texts(game::context* ctx) +{ + for (scene::text* text: ctx->language_menu_texts) + { + text->refresh(); + } +} + +static void align_texts(game::context* ctx) +{ + float menu_width = 0.0f; + for (std::size_t i = 0; i < ctx->language_menu_texts.size(); ++i) + { + scene::text* text = ctx->language_menu_texts[i]; + + // Update menu width + const auto& bounds = static_cast&>(text->get_local_bounds()); + float width = bounds.max_point.x - bounds.min_point.x; + menu_width = std::max(menu_width, width); + } + + float menu_height = ctx->language_menu_texts.size() * ctx->menu_font.get_font_metrics().linespace; + float menu_x = -menu_width * 0.5f; + float menu_y = menu_height * 0.5f - ctx->menu_font.get_font_metrics().linespace; + for (std::size_t i = 0; i < ctx->language_menu_texts.size(); ++i) + { + scene::text* text = ctx->language_menu_texts[i]; + + // Align text + const auto& bounds = static_cast&>(text->get_local_bounds()); + float w = bounds.max_point.x - bounds.min_point.x; + float x = -w * 0.5f; + //float x = menu_x; + float y = menu_y - ctx->menu_font.get_font_metrics().linespace * i; + + text->set_translation({std::round(x), std::round(y), 0.0f}); + } +} + +static void update_text_tweens(game::context* ctx) +{ + for (scene::text* text: ctx->language_menu_texts) + { + text->update_tweens(); + } +} + +void enter(game::context* ctx) +{ + ctx->ui_clear_pass->set_cleared_buffers(true, true, false); + + ctx->language_menu_index = 0; + + // Construct language menu texts + ctx->language_menu_language_text = new scene::text(); + ctx->language_menu_back_text = new scene::text(); + + // Build list of language menu texts + ctx->language_menu_texts.push_back(ctx->language_menu_language_text); + ctx->language_menu_texts.push_back(ctx->language_menu_back_text); + + // Construct language menu callbacks + auto menu_back_callback = [ctx]() + { + application::state next_state; + next_state.name = "options_menu"; + next_state.enter = std::bind(game::state::options_menu::enter, ctx); + next_state.exit = std::bind(game::state::options_menu::exit, ctx); + ctx->app->change_state(next_state); + }; + auto next_language_callback = [ctx]() + { + if (ctx->language_menu_index != 0) + return; + + // Increment language index + ++ctx->language_index; + if (ctx->language_index >= ctx->language_count) + ctx->language_index = 0; + + // Find corresponding language code and strings + ctx->language_code = (*ctx->string_table)[0][ctx->language_index + 2]; + ctx->strings = &ctx->string_table_map[ctx->language_code]; + + // Update language in config + (*ctx->config)["language"] = ctx->language_code; + + ctx->logger->log("Language changed to \"" + ctx->language_code + "\""); + + // Reload fonts + ctx->logger->push_task("Reloading fonts"); + try + { + game::load_fonts(ctx); + } + catch (...) + { + ctx->logger->pop_task(EXIT_FAILURE); + } + ctx->logger->pop_task(EXIT_SUCCESS); + + update_text_font(ctx); + update_text_content(ctx); + refresh_texts(ctx); + align_texts(ctx); + update_text_tweens(ctx); + }; + auto previous_language_callback = [ctx]() + { + if (ctx->language_menu_index != 0) + return; + + // Increment language index + --ctx->language_index; + if (ctx->language_index < 0) + ctx->language_index = ctx->language_count - 1; + + // Find corresponding language code and strings + ctx->language_code = (*ctx->string_table)[0][ctx->language_index + 2]; + ctx->strings = &ctx->string_table_map[ctx->language_code]; + + ctx->logger->log("Language changed to \"" + ctx->language_code + "\""); + + // Reload fonts + ctx->logger->push_task("Reloading fonts"); + try + { + game::load_fonts(ctx); + } + catch (...) + { + ctx->logger->pop_task(EXIT_FAILURE); + } + ctx->logger->pop_task(EXIT_SUCCESS); + + update_text_font(ctx); + update_text_content(ctx); + refresh_texts(ctx); + align_texts(ctx); + update_text_tweens(ctx); + }; + + // Build list of language menu callbacks + ctx->language_menu_callbacks.push_back(next_language_callback); + ctx->language_menu_callbacks.push_back(menu_back_callback); + + ctx->controls["menu_down"]->set_activated_callback + ( + [ctx]() + { + ++ctx->language_menu_index; + if (ctx->language_menu_index >= ctx->language_menu_texts.size()) + ctx->language_menu_index = 0; + + update_text_color(ctx); + } + ); + ctx->controls["menu_up"]->set_activated_callback + ( + [ctx]() + { + --ctx->language_menu_index; + if (ctx->language_menu_index < 0) + ctx->language_menu_index = ctx->language_menu_texts.size() - 1; + + update_text_color(ctx); + } + ); + ctx->controls["menu_left"]->set_activated_callback(previous_language_callback); + ctx->controls["menu_right"]->set_activated_callback(next_language_callback); + ctx->controls["menu_select"]->set_activated_callback + ( + [ctx]() + { + auto callback = ctx->language_menu_callbacks[ctx->language_menu_index]; + if (callback != nullptr) + callback(); + } + ); + ctx->controls["menu_back"]->set_activated_callback(menu_back_callback); + + for (scene::text* text: ctx->language_menu_texts) + ctx->ui_scene->add_object(text); + update_text_font(ctx); + update_text_color(ctx); + update_text_content(ctx); + align_texts(ctx); + update_text_tweens(ctx); +} + +void exit(game::context* ctx) +{ + // Clear control callbacks + ctx->controls["menu_down"]->set_activated_callback(nullptr); + ctx->controls["menu_up"]->set_activated_callback(nullptr); + ctx->controls["menu_left"]->set_activated_callback(nullptr); + ctx->controls["menu_right"]->set_activated_callback(nullptr); + ctx->controls["menu_select"]->set_activated_callback(nullptr); + ctx->controls["menu_back"]->set_activated_callback(nullptr); + + // Clear language menu callbacks + ctx->language_menu_callbacks.clear(); + + // Destruct language menu texts + for (scene::text* text: ctx->language_menu_texts) + { + ctx->ui_scene->remove_object(text); + delete text; + } + ctx->language_menu_texts.clear(); + + ctx->ui_clear_pass->set_cleared_buffers(false, true, false); +} + +} // namespace language_menu +} // namespace state +} // namespace game diff --git a/src/game/states/language-menu.hpp b/src/game/states/language-menu.hpp new file mode 100644 index 0000000..b247c98 --- /dev/null +++ b/src/game/states/language-menu.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP +#define ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP + +#include "game/context.hpp" + +namespace game { +namespace state { + +/// Language menu screen game state functions. +namespace language_menu { + +void enter(game::context* ctx); +void exit(game::context* ctx); + +} // namespace language_menu + +} // namespace state +} // namespace game + +#endif // ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP diff --git a/src/game/states/loading.cpp b/src/game/states/loading.cpp index 45d05bd..d7e2d87 100644 --- a/src/game/states/loading.cpp +++ b/src/game/states/loading.cpp @@ -50,16 +50,13 @@ #include "scene/ambient-light.hpp" #include "scene/directional-light.hpp" #include "utility/timestamp.hpp" -#include "type/type.hpp" #include "configuration.hpp" -#include #include -#include #include "gl/texture-wrapping.hpp" #include "gl/texture-filter.hpp" -#include "scene/text.hpp" #include "render/material-flags.hpp" +#include "game/fonts.hpp" namespace game { namespace state { @@ -68,9 +65,6 @@ namespace loading { /// Loads control profile and calibrates gamepads static void load_controls(game::context* ctx); -/// Loads typefaces and builds fonts -static void load_fonts(game::context* ctx); - /// Creates the universe and solar system. static void cosmogenesis(game::context* ctx); @@ -107,7 +101,7 @@ void enter(game::context* ctx) ctx->logger->push_task("Loading fonts"); try { - load_fonts(ctx); + game::load_fonts(ctx); } catch (...) { @@ -115,6 +109,7 @@ void enter(game::context* ctx) } ctx->logger->pop_task(EXIT_SUCCESS); + /* // Create universe ctx->logger->push_task("Creating the universe"); try @@ -127,13 +122,14 @@ void enter(game::context* ctx) throw; } ctx->logger->pop_task(EXIT_SUCCESS); + */ // Determine next game state application::state next_state; if (ctx->option_quick_start.has_value()) { next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx); + next_state.enter = std::bind(game::state::main_menu::enter, ctx, 0); next_state.exit = std::bind(game::state::main_menu::exit, ctx); } else @@ -222,12 +218,6 @@ void load_controls(game::context* ctx) } ); - // Menu back - ctx->controls["menu_back"]->set_activated_callback - ( - std::bind(&application::close, ctx->app, 0) - ); - // Set activation threshold for menu navigation controls to mitigate drifting gamepad axes const float menu_activation_threshold = 0.1f; ctx->controls["menu_up"]->set_activation_threshold(menu_activation_threshold); @@ -236,95 +226,6 @@ void load_controls(game::context* ctx) ctx->controls["menu_right"]->set_activation_threshold(menu_activation_threshold); } -static void build_bitmap_font(const type::typeface& typeface, float size, const std::unordered_set& charset, type::bitmap_font& font, render::material& material, gl::shader_program* shader_program) -{ - // Get font metrics for given size - if (type::font_metrics metrics; typeface.get_metrics(size, metrics)) - font.set_font_metrics(metrics); - - // Format font bitmap - image& font_bitmap = font.get_bitmap(); - font_bitmap.format(sizeof(std::byte), 1); - - // For each UTF-32 character code in the character set - for (char32_t code: charset) - { - // Skip missing glyphs - if (!typeface.has_glyph(code)) - continue; - - // Add glyph to font - type::bitmap_glyph& glyph = font[code]; - typeface.get_metrics(size, code, glyph.metrics); - typeface.get_bitmap(size, code, glyph.bitmap); - } - - // Pack glyph bitmaps into the font bitmap - font.pack(); - - // Create font texture from bitmap - gl::texture_2d* font_texture = new gl::texture_2d(font_bitmap.get_width(), font_bitmap.get_height(), gl::pixel_type::uint_8, gl::pixel_format::r, gl::color_space::linear, font_bitmap.get_pixels()); - font_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); - font_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); - - // Create font material - material.set_flags(MATERIAL_FLAG_TRANSLUCENT); - material.add_property("font_bitmap")->set_value(font_texture); - material.set_shader_program(shader_program); -} - -void load_fonts(game::context* ctx) -{ - // Load typefaces - if (auto it = ctx->strings->find("font_serif"); it != ctx->strings->end()) - ctx->typefaces["serif"] = ctx->resource_manager->load(it->second); - if (auto it = ctx->strings->find("font_sans_serif"); it != ctx->strings->end()) - ctx->typefaces["sans_serif"] = ctx->resource_manager->load(it->second); - if (auto it = ctx->strings->find("font_monospace"); it != ctx->strings->end()) - ctx->typefaces["monospace"] = ctx->resource_manager->load(it->second); - - // Build character set - std::unordered_set charset; - { - // Add all character codes from the basic Latin unicode block - for (char32_t code = type::unicode::block::basic_latin.first; code <= type::unicode::block::basic_latin.last; ++code) - charset.insert(code); - - // Add all character codes from game strings - for (auto it = ctx->strings->begin(); it != ctx->strings->end(); ++it) - { - // Convert UTF-8 string to UTF-32 - std::wstring_convert, char32_t> convert; - std::u32string u32 = convert.from_bytes(it->second); - - /// Insert each character code from the UTF-32 string into the character set - for (char32_t code: u32) - charset.insert(code); - } - } - - // Load bitmap font shader - gl::shader_program* bitmap_font_shader = ctx->resource_manager->load("bitmap-font.glsl"); - - // Build debug font - if (auto it = ctx->typefaces.find("monospace"); it != ctx->typefaces.end()) - { - build_bitmap_font(*it->second, 22.0f, charset, ctx->debug_font, ctx->debug_font_material, bitmap_font_shader); - } - - // Build menu font - if (auto it = ctx->typefaces.find("sans_serif"); it != ctx->typefaces.end()) - { - build_bitmap_font(*it->second, 28.0f, charset, ctx->menu_font, ctx->menu_font_material, bitmap_font_shader); - } - - // Build title font - if (auto it = ctx->typefaces.find("serif"); it != ctx->typefaces.end()) - { - build_bitmap_font(*it->second, 96.0f, charset, ctx->title_font, ctx->title_font_material, bitmap_font_shader); - } -} - void cosmogenesis(game::context* ctx) { // Init time diff --git a/src/game/states/main-menu.cpp b/src/game/states/main-menu.cpp index aadc60b..54be249 100644 --- a/src/game/states/main-menu.cpp +++ b/src/game/states/main-menu.cpp @@ -18,11 +18,16 @@ */ #include "game/states/main-menu.hpp" +#include "game/states/title.hpp" +#include "game/states/options-menu.hpp" #include "game/states/forage.hpp" #include "game/states/nuptial-flight.hpp" +#include "game/states/credits.hpp" #include "render/passes/clear-pass.hpp" #include "resources/resource-manager.hpp" #include "render/model.hpp" +#include "animation/animation.hpp" +#include "animation/animator.hpp" #include "animation/screen-transition.hpp" #include "animation/ease.hpp" #include "animation/timeline.hpp" @@ -33,8 +38,10 @@ namespace game { namespace state { namespace main_menu { -void enter(game::context* ctx) +void enter(game::context* ctx, int main_menu_index) { + ctx->main_menu_index = main_menu_index; + ctx->ui_clear_pass->set_cleared_buffers(true, true, false); // Construct main menu texts @@ -57,89 +64,174 @@ void enter(game::context* ctx) float offset_y = 0.0f; - for (scene::text* text: texts) + float menu_height = texts.size() * ctx->menu_font.get_font_metrics().linespace; + float menu_y = menu_height * 0.5f - ctx->menu_font.get_font_metrics().linespace * 0.5f; + + for (std::size_t i = 0; i < texts.size(); ++i) { + scene::text* text = texts[i]; + text->set_material(&ctx->menu_font_material); text->set_font(&ctx->menu_font); text->set_color({1.0f, 1.0f, 1.0f, 0.5f}); ctx->ui_scene->add_object(text); // Align text - const auto& text_aabb = static_cast&>(text->get_local_bounds()); - float title_w = text_aabb.max_point.x - text_aabb.min_point.x; - float title_h = text_aabb.max_point.y - text_aabb.min_point.y; - text->set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f) + offset_y, 0.0f}); - - offset_y -= ctx->menu_font.get_font_metrics().linespace * 1.5f; + const auto& bounds = static_cast&>(text->get_local_bounds()); + float w = bounds.max_point.x - bounds.min_point.x; + float x = -w * 0.5f; + float y = menu_y - ctx->menu_font.get_font_metrics().linespace * i; - // Update menu AABB + text->set_translation({std::round(x), std::round(y), 0.0f}); } - // Load pointer - ctx->ui_pointer = new scene::model_instance(); - ctx->ui_pointer->set_model(ctx->resource_manager->load("pointer.mdl")); - ctx->ui_scene->add_object(ctx->ui_pointer); - - // Scale and position pointer - float pointer_scale = (ctx->menu_font.get_font_metrics().ascent - ctx->menu_font.get_font_metrics().descent) * (1.0f/3.0f); - ctx->ui_pointer->set_scale({pointer_scale, pointer_scale, pointer_scale}); - float advance_x = ctx->menu_font.get_glyph_metrics(U' ').horizontal_advance * 2.0f; const auto& text_aabb = static_cast&>(ctx->main_menu_start_text->get_local_bounds()); const auto& text_translation = ctx->main_menu_start_text->get_translation(); - ctx->ui_pointer->set_translation(text_translation + float3{-advance_x, (text_aabb.max_point.y - text_aabb.min_point.y) * 0.5f, 0.0f}); ctx->main_menu_start_text->set_color({1.0f, 1.0f, 1.0f, 1.0f}); ctx->controls["menu_down"]->set_activated_callback ( [ctx]() { - ctx->ui_pointer->set_translation(ctx->ui_pointer->get_translation() - float3{0.0f, ctx->menu_font.get_font_metrics().linespace * 1.5f, 0.0f}); + ++ctx->main_menu_index; + if (ctx->main_menu_index > 3) + ctx->main_menu_index = 0; + + float4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; + float4 inactive_color{1.0f, 1.0f, 1.0f, 0.5f}; + + ctx->main_menu_start_text->set_color((ctx->main_menu_index == 0) ? active_color : inactive_color); + ctx->main_menu_options_text->set_color((ctx->main_menu_index == 1) ? active_color : inactive_color); + ctx->main_menu_credits_text->set_color((ctx->main_menu_index == 2) ? active_color : inactive_color); + ctx->main_menu_quit_text->set_color((ctx->main_menu_index == 3) ? active_color : inactive_color); } ); ctx->controls["menu_up"]->set_activated_callback ( [ctx]() { - ctx->ui_pointer->set_translation(ctx->ui_pointer->get_translation() + float3{0.0f, ctx->menu_font.get_font_metrics().linespace * 1.5f, 0.0f}); + --ctx->main_menu_index; + if (ctx->main_menu_index < 0) + ctx->main_menu_index = 3; + + float4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; + float4 inactive_color{1.0f, 1.0f, 1.0f, 0.5f}; + + ctx->main_menu_start_text->set_color((ctx->main_menu_index == 0) ? active_color : inactive_color); + ctx->main_menu_options_text->set_color((ctx->main_menu_index == 1) ? active_color : inactive_color); + ctx->main_menu_credits_text->set_color((ctx->main_menu_index == 2) ? active_color : inactive_color); + ctx->main_menu_quit_text->set_color((ctx->main_menu_index == 3) ? active_color : inactive_color); + } + ); + ctx->controls["menu_back"]->set_activated_callback + ( + [ctx]() + { + application::state next_state; + next_state.name = "title"; + next_state.enter = std::bind(game::state::title::enter, ctx); + next_state.exit = std::bind(game::state::title::exit, ctx); + ctx->app->change_state(next_state); } ); ctx->controls["menu_select"]->set_activated_callback ( [ctx]() { - // Disable menu controls - ctx->controls["menu_down"]->set_activated_callback(nullptr); - ctx->controls["menu_up"]->set_activated_callback(nullptr); - ctx->controls["menu_select"]->set_activated_callback(nullptr); - - // Create change state functions - auto change_state_nuptial_flight = [ctx]() + if (ctx->main_menu_index == 0) { - application::state next_state; - next_state.name = "nuptial_flight"; - next_state.enter = std::bind(game::state::nuptial_flight::enter, ctx); - next_state.exit = std::bind(game::state::nuptial_flight::exit, ctx); - ctx->app->change_state(next_state); - }; - - // Set up timing - const float fade_out_duration = 1.0f; - - // Schedule state change - timeline* timeline = ctx->timeline; - float t = timeline->get_position(); - timeline::sequence sequence = + // Disable menu controls + ctx->controls["menu_down"]->set_activated_callback(nullptr); + ctx->controls["menu_up"]->set_activated_callback(nullptr); + ctx->controls["menu_select"]->set_activated_callback(nullptr); + + // Create change state function + auto change_state_nuptial_flight = [ctx]() + { + application::state next_state; + next_state.name = "nuptial_flight"; + next_state.enter = std::bind(game::state::nuptial_flight::enter, ctx); + next_state.exit = std::bind(game::state::nuptial_flight::exit, ctx); + ctx->app->change_state(next_state); + }; + + // Set up timing + const float fade_out_duration = 1.0f; + + // Schedule state change + timeline* timeline = ctx->timeline; + float t = timeline->get_position(); + timeline->add_sequence({{t + fade_out_duration, change_state_nuptial_flight}}); + + // Start fade out to white + ctx->fade_transition_color->set_value({1, 1, 1}); + ctx->fade_transition->transition(fade_out_duration, false, ease::out_quad, false); + } + else if (ctx->main_menu_index == 1) { - {t + fade_out_duration, change_state_nuptial_flight} - }; - timeline->add_sequence(sequence); - - // Start fade out to white - ctx->fade_transition_color->set_value({1, 1, 1}); - ctx->fade_transition->transition(fade_out_duration, false, ease::out_quad, false); + // Disable menu controls + ctx->controls["menu_down"]->set_activated_callback(nullptr); + ctx->controls["menu_up"]->set_activated_callback(nullptr); + ctx->controls["menu_select"]->set_activated_callback(nullptr); + + // Create change state function + auto change_state_options = [ctx]() + { + application::state next_state; + next_state.name = "options_menu"; + next_state.enter = std::bind(game::state::options_menu::enter, ctx); + next_state.exit = std::bind(game::state::options_menu::exit, ctx); + ctx->app->change_state(next_state); + }; + + // Set up timing + const float fade_out_duration = 0.25f; + + // Schedule state change + timeline* timeline = ctx->timeline; + float t = timeline->get_position(); + timeline->add_sequence({{t + fade_out_duration, change_state_options}}); + + // Start fade out to black + ctx->fade_transition_color->set_value({0, 0, 0}); + ctx->fade_transition->transition(fade_out_duration, false, ease::out_quad); + } + else if (ctx->main_menu_index == 2) + { + // Disable menu controls + ctx->controls["menu_down"]->set_activated_callback(nullptr); + ctx->controls["menu_up"]->set_activated_callback(nullptr); + ctx->controls["menu_select"]->set_activated_callback(nullptr); + + // Create change state function + auto change_state_credits = [ctx]() + { + application::state next_state; + next_state.name = "credits"; + next_state.enter = std::bind(game::state::credits::enter, ctx); + next_state.exit = std::bind(game::state::credits::exit, ctx); + ctx->app->change_state(next_state); + }; + + // Set up timing + const float fade_out_duration = 0.5f; + + // Schedule state change + timeline* timeline = ctx->timeline; + float t = timeline->get_position(); + timeline->add_sequence({{t + fade_out_duration, change_state_credits}}); + + // Start fade out to black + ctx->fade_transition_color->set_value({0, 0, 0}); + ctx->fade_transition->transition(fade_out_duration, false, ease::out_quad); + } + else if (ctx->main_menu_index == 3) + { + ctx->app->close(EXIT_SUCCESS); + } } ); @@ -148,34 +240,56 @@ void enter(game::context* ctx) ctx->controls["menu_up"]->set_callbacks_enabled(false); ctx->controls["menu_select"]->set_callbacks_enabled(false); - // Start fade in from black - const float fade_in_duration = 0.5f; - ctx->fade_transition_color->set_value({0, 0, 0}); - ctx->fade_transition->transition(fade_in_duration, true, ease::in_quad); + // Build main menu fade in animation + ctx->main_menu_fade_animation = new animation(); + animation_channel* main_menu_opacity_channel = ctx->main_menu_fade_animation->add_channel(0); + ctx->main_menu_fade_animation->set_interpolator(ease::out_quad); + double main_menu_fade_in_duration = 0.0; + if (ctx->config->contains("main_menu_fade_in_duration")) + main_menu_fade_in_duration = (*ctx->config)["main_menu_fade_in_duration"].get(); + main_menu_opacity_channel->insert_keyframe({0.0, 0.0f}); + main_menu_opacity_channel->insert_keyframe({main_menu_fade_in_duration, 1.0f}); - // Schedule enabling of control callbacks - auto enable_control_callbacks = [ctx]() - { - ctx->controls["menu_down"]->set_callbacks_enabled(true); - ctx->controls["menu_up"]->set_callbacks_enabled(true); - ctx->controls["menu_select"]->set_callbacks_enabled(true); - }; - timeline* timeline = ctx->timeline; - float t = timeline->get_position(); - timeline::sequence sequence = - { - {t + fade_in_duration, enable_control_callbacks} - }; - timeline->add_sequence(sequence); + // Adjust main menu text opacity + ctx->main_menu_fade_animation->set_frame_callback + ( + [ctx](int channel, const float& opacity) + { + float4 active_color{1.0f, 1.0f, 1.0f, opacity}; + float4 inactive_color{1.0f, 1.0f, 1.0f, 0.5f * opacity}; + + ctx->main_menu_start_text->set_color((ctx->main_menu_index == 0) ? active_color : inactive_color); + ctx->main_menu_options_text->set_color((ctx->main_menu_index == 1) ? active_color : inactive_color); + ctx->main_menu_credits_text->set_color((ctx->main_menu_index == 2) ? active_color : inactive_color); + ctx->main_menu_quit_text->set_color((ctx->main_menu_index == 3) ? active_color : inactive_color); + + // Enable menu controls when visible + if (opacity > 0.0f) + { + ctx->controls["menu_down"]->set_callbacks_enabled(true); + ctx->controls["menu_up"]->set_callbacks_enabled(true); + ctx->controls["menu_select"]->set_callbacks_enabled(true); + } + } + ); + + ctx->animator->add_animation(ctx->main_menu_fade_animation); + ctx->main_menu_fade_animation->play(); } void exit(game::context* ctx) { - // Remove control callbacks + // Disable menu control callbacks ctx->controls["menu_down"]->set_activated_callback(nullptr); ctx->controls["menu_up"]->set_activated_callback(nullptr); + ctx->controls["menu_back"]->set_activated_callback(nullptr); ctx->controls["menu_select"]->set_activated_callback(nullptr); + // Destruct main menu animation + ctx->animator->remove_animation(ctx->main_menu_fade_animation); + delete ctx->main_menu_fade_animation; + ctx->main_menu_fade_animation = nullptr; + // Remove text objects from UI std::vector texts; texts.push_back(ctx->main_menu_start_text); @@ -189,11 +303,6 @@ void exit(game::context* ctx) text = nullptr; } - // Remove pointer from UI - ctx->ui_scene->remove_object(ctx->ui_pointer); - delete ctx->ui_pointer; - ctx->ui_pointer = nullptr; - ctx->ui_clear_pass->set_cleared_buffers(false, true, false); } diff --git a/src/game/states/main-menu.hpp b/src/game/states/main-menu.hpp index 4164422..73bce10 100644 --- a/src/game/states/main-menu.hpp +++ b/src/game/states/main-menu.hpp @@ -28,7 +28,7 @@ namespace state { /// Main menu screen game state functions. namespace main_menu { -void enter(game::context* ctx); +void enter(game::context* ctx, int main_menu_index); void exit(game::context* ctx); } // namespace main_menu diff --git a/src/game/states/options-menu.cpp b/src/game/states/options-menu.cpp new file mode 100644 index 0000000..f47f8da --- /dev/null +++ b/src/game/states/options-menu.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "game/states/options-menu.hpp" +#include "game/states/main-menu.hpp" +#include "game/states/language-menu.hpp" +#include "animation/ease.hpp" +#include "animation/animation.hpp" +#include "animation/animator.hpp" +#include "application.hpp" +#include "scene/text.hpp" +#include "render/passes/clear-pass.hpp" + +namespace game { +namespace state { +namespace options_menu { + +void enter(game::context* ctx) +{ + ctx->ui_clear_pass->set_cleared_buffers(true, true, false); + + ctx->options_menu_index = 0; + + // Construct options menu texts + ctx->options_menu_controls_text = new scene::text(); + ctx->options_menu_graphics_text = new scene::text(); + ctx->options_menu_sound_text = new scene::text(); + ctx->options_menu_language_text = new scene::text(); + ctx->options_menu_back_text = new scene::text(); + + // Build list of options menu texts + ctx->options_menu_texts.push_back(ctx->options_menu_controls_text); + ctx->options_menu_texts.push_back(ctx->options_menu_graphics_text); + ctx->options_menu_texts.push_back(ctx->options_menu_sound_text); + ctx->options_menu_texts.push_back(ctx->options_menu_language_text); + ctx->options_menu_texts.push_back(ctx->options_menu_back_text); + + // Construct options menu callbacks + auto menu_back_callback = [ctx]() + { + application::state next_state; + next_state.name = "main_menu"; + next_state.enter = std::bind(game::state::main_menu::enter, ctx, 1); + next_state.exit = std::bind(game::state::main_menu::exit, ctx); + ctx->app->change_state(next_state); + }; + auto change_state_language_menu = [ctx]() + { + application::state next_state; + next_state.name = "language_menu"; + next_state.enter = std::bind(game::state::language_menu::enter, ctx); + next_state.exit = std::bind(game::state::language_menu::exit, ctx); + ctx->app->change_state(next_state); + }; + + // Build list of options menu callbacks + ctx->options_menu_callbacks.push_back(nullptr); + ctx->options_menu_callbacks.push_back(nullptr); + ctx->options_menu_callbacks.push_back(nullptr); + ctx->options_menu_callbacks.push_back(change_state_language_menu); + ctx->options_menu_callbacks.push_back(menu_back_callback); + + // Set content of texts + ctx->options_menu_controls_text->set_content((*ctx->strings)["options_menu_controls"]); + ctx->options_menu_graphics_text->set_content((*ctx->strings)["options_menu_graphics"]); + ctx->options_menu_sound_text->set_content((*ctx->strings)["options_menu_sound"]); + ctx->options_menu_language_text->set_content((*ctx->strings)["options_menu_language"]); + ctx->options_menu_back_text->set_content((*ctx->strings)["back"]); + + float4 inactive_color = {1.0f, 1.0f, 1.0f, 0.5f}; + float4 active_color = {1.0f, 1.0f, 1.0f, 1.0f}; + float menu_width = 0.0f; + for (std::size_t i = 0; i < ctx->options_menu_texts.size(); ++i) + { + scene::text* text = ctx->options_menu_texts[i]; + + // Set text material and font + text->set_material(&ctx->menu_font_material); + text->set_font(&ctx->menu_font); + + // Set text color + if (i == ctx->options_menu_index) + text->set_color(active_color); + else + text->set_color(inactive_color); + + // Update menu width + const auto& bounds = static_cast&>(text->get_local_bounds()); + float width = bounds.max_point.x - bounds.min_point.x; + menu_width = std::max(menu_width, width); + + // Add text to UI + ctx->ui_scene->add_object(text); + } + + // Align texts + float menu_height = ctx->options_menu_texts.size() * ctx->menu_font.get_font_metrics().linespace; + float menu_x = -menu_width * 0.5f; + float menu_y = menu_height * 0.5f - ctx->menu_font.get_font_metrics().linespace; + for (std::size_t i = 0; i < ctx->options_menu_texts.size(); ++i) + { + scene::text* text = ctx->options_menu_texts[i]; + + float x = menu_x; + float y = menu_y - ctx->menu_font.get_font_metrics().linespace * i; + + text->set_translation({std::round(x), std::round(y), 0.0f}); + text->update_tweens(); + } + + ctx->controls["menu_down"]->set_activated_callback + ( + [ctx]() + { + ++ctx->options_menu_index; + if (ctx->options_menu_index >= ctx->options_menu_texts.size()) + ctx->options_menu_index = 0; + + float4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; + float4 inactive_color{1.0f, 1.0f, 1.0f, 0.5f}; + + for (std::size_t i = 0; i < ctx->options_menu_texts.size(); ++i) + { + scene::text* text = ctx->options_menu_texts[i]; + + if (i == ctx->options_menu_index) + text->set_color(active_color); + else + text->set_color(inactive_color); + } + } + ); + ctx->controls["menu_up"]->set_activated_callback + ( + [ctx]() + { + --ctx->options_menu_index; + if (ctx->options_menu_index < 0) + ctx->options_menu_index = ctx->options_menu_texts.size() - 1; + + float4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; + float4 inactive_color{1.0f, 1.0f, 1.0f, 0.5f}; + + for (std::size_t i = 0; i < ctx->options_menu_texts.size(); ++i) + { + scene::text* text = ctx->options_menu_texts[i]; + + if (i == ctx->options_menu_index) + text->set_color(active_color); + else + text->set_color(inactive_color); + } + } + ); + ctx->controls["menu_select"]->set_activated_callback + ( + [ctx]() + { + auto callback = ctx->options_menu_callbacks[ctx->options_menu_index]; + if (callback != nullptr) + callback(); + } + ); + ctx->controls["menu_back"]->set_activated_callback(menu_back_callback); + /* + ctx->controls["menu_back"]->set_activated_callback + ( + std::bind(&application::close, ctx->app, 0) + ); + */ +} + +void exit(game::context* ctx) +{ + // Clear control callbacks + ctx->controls["menu_down"]->set_activated_callback(nullptr); + ctx->controls["menu_up"]->set_activated_callback(nullptr); + ctx->controls["menu_select"]->set_activated_callback(nullptr); + ctx->controls["menu_back"]->set_activated_callback(nullptr); + + // Clear options menu callbacks + ctx->options_menu_callbacks.clear(); + + // Destruct options menu texts + for (scene::text* text: ctx->options_menu_texts) + { + ctx->ui_scene->remove_object(text); + delete text; + } + ctx->options_menu_texts.clear(); + + ctx->ui_clear_pass->set_cleared_buffers(false, true, false); +} + +} // namespace options_menu +} // namespace state +} // namespace game diff --git a/src/game/states/options-menu.hpp b/src/game/states/options-menu.hpp new file mode 100644 index 0000000..0ad2f0c --- /dev/null +++ b/src/game/states/options-menu.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GAME_STATE_OPTIONS_MENU_HPP +#define ANTKEEPER_GAME_STATE_OPTIONS_MENU_HPP + +#include "game/context.hpp" + +namespace game { +namespace state { + +/// Options menu screen game state functions. +namespace options_menu { + +void enter(game::context* ctx); +void exit(game::context* ctx); + +} // namespace options_menu + +} // namespace state +} // namespace game + +#endif // ANTKEEPER_GAME_STATE_OPTIONS_MENU_HPP diff --git a/src/game/states/splash.cpp b/src/game/states/splash.cpp index 0aa8151..4ada7a5 100644 --- a/src/game/states/splash.cpp +++ b/src/game/states/splash.cpp @@ -20,6 +20,8 @@ #include "game/states/splash.hpp" #include "game/states/title.hpp" #include "animation/screen-transition.hpp" +#include "animation/animation.hpp" +#include "animation/animator.hpp" #include "animation/ease.hpp" #include "animation/timeline.hpp" #include "application.hpp" @@ -33,43 +35,78 @@ void enter(game::context* ctx) { ctx->ui_clear_pass->set_cleared_buffers(true, true, false); - // Add splash billboard to UI scene - ctx->ui_scene->add_object(ctx->splash_billboard); + // Load animation timing configuration + double splash_fade_in_duration = 0.0; + double splash_duration = 0.0; + double splash_fade_out_duration = 0.0; + if (ctx->config->contains("splash_fade_in_duration")) + splash_fade_in_duration = (*ctx->config)["splash_fade_in_duration"].get(); + if (ctx->config->contains("splash_duration")) + splash_duration = (*ctx->config)["splash_duration"].get(); + if (ctx->config->contains("splash_fade_out_duration")) + splash_fade_out_duration = (*ctx->config)["splash_fade_out_duration"].get(); - // Setup timing - const float splash_fade_in_duration = 0.5f; - const float splash_hang_duration = 2.0f; - const float splash_fade_out_duration = 0.5f; + // Build splash fade in animation + ctx->splash_fade_in_animation = new animation(); + animation_channel* splash_fade_in_opacity_channel = ctx->splash_fade_in_animation->add_channel(0); + ctx->splash_fade_in_animation->set_interpolator(ease::in_quad); + splash_fade_in_opacity_channel->insert_keyframe({0.0, 0.0f}); + splash_fade_in_opacity_channel->insert_keyframe({splash_fade_in_duration, 1.0f}); + splash_fade_in_opacity_channel->insert_keyframe({splash_fade_in_duration + splash_duration, 1.0f}); - // Start fade in - ctx->fade_transition->transition(splash_fade_in_duration, true, ease::in_quad); + // Build splash fade out animation + ctx->splash_fade_out_animation = new animation(); + animation_channel* splash_fade_out_opacity_channel = ctx->splash_fade_out_animation->add_channel(0); + ctx->splash_fade_out_animation->set_interpolator(ease::out_quad); + splash_fade_out_opacity_channel->insert_keyframe({0.0, 1.0f}); + splash_fade_out_opacity_channel->insert_keyframe({splash_fade_out_duration, 0.0f}); - // Crate fade out function - auto fade_out = [ctx, splash_fade_out_duration]() + // Setup animation frame callbacks + auto set_splash_opacity = [ctx](int channel, const float& opacity) { - ctx->fade_transition->transition(splash_fade_out_duration, false, ease::out_quad); + static_cast*>(ctx->splash_billboard_material->get_property("tint"))->set_value(float4{1, 1, 1, opacity}); }; + ctx->splash_fade_in_animation->set_frame_callback(set_splash_opacity); + ctx->splash_fade_out_animation->set_frame_callback(set_splash_opacity); - // Create change state function - auto change_state = [ctx]() - { - application::state next_state; - next_state.name = "title"; - next_state.enter = std::bind(game::state::title::enter, ctx); - next_state.exit = std::bind(game::state::title::exit, ctx); - - ctx->app->change_state(next_state); - }; + // Reset splash color when animation starts + ctx->splash_fade_in_animation->set_start_callback + ( + [ctx]() + { + static_cast*>(ctx->splash_billboard_material->get_property("tint"))->set_value(float4{1, 1, 1, 0}); + ctx->splash_billboard_material->update_tweens(); + } + ); - // Schedule fade out and change state events - timeline* timeline = ctx->timeline; - float t = timeline->get_position(); - timeline::sequence splash_sequence = - { - {t + splash_fade_in_duration + splash_hang_duration, fade_out}, - {t + splash_fade_in_duration + splash_hang_duration + splash_fade_out_duration, change_state} - }; - timeline->add_sequence(splash_sequence); + // Trigger splash fade out animation when splash fade in animation ends + ctx->splash_fade_in_animation->set_end_callback + ( + [ctx]() + { + ctx->splash_fade_out_animation->play(); + } + ); + + // Trigger a state change when the splash fade out animation ends + ctx->splash_fade_out_animation->set_end_callback + ( + [ctx]() + { + application::state next_state; + next_state.name = "title"; + next_state.enter = std::bind(game::state::title::enter, ctx); + next_state.exit = std::bind(game::state::title::exit, ctx); + ctx->app->queue_state(next_state); + } + ); + + // Add splash fade animations to animator + ctx->animator->add_animation(ctx->splash_fade_in_animation); + ctx->animator->add_animation(ctx->splash_fade_out_animation); + + // Start splash fade in animation + ctx->splash_fade_in_animation->play(); // Set up splash skipper ctx->input_listener->set_callback @@ -79,32 +116,42 @@ void enter(game::context* ctx) auto id = event.get_event_type_id(); if (id != mouse_moved_event::event_type_id && id != mouse_wheel_scrolled_event::event_type_id && id != gamepad_axis_moved_event::event_type_id) { - ctx->timeline->clear(); - ctx->fade_transition->get_animation()->stop(); + // Black out screen ctx->rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f); ctx->rasterizer->clear_framebuffer(true, false, false); ctx->app->swap_buffers(); + // Change state application::state next_state; next_state.name = "title"; next_state.enter = std::bind(game::state::title::enter, ctx); next_state.exit = std::bind(game::state::title::exit, ctx); - ctx->app->change_state(next_state); } } ); ctx->input_listener->set_enabled(true); + + // Add splash billboard to UI scene + ctx->ui_scene->add_object(ctx->splash_billboard); } void exit(game::context* ctx) { + // Remove splash billboard from UI scene + ctx->ui_scene->remove_object(ctx->splash_billboard); + // Disable splash skipper ctx->input_listener->set_enabled(false); ctx->input_listener->set_callback(nullptr); - // Remove splash billboard from UI scene - ctx->ui_scene->remove_object(ctx->splash_billboard); + // Destruct splash fade animations + ctx->animator->remove_animation(ctx->splash_fade_in_animation); + ctx->animator->remove_animation(ctx->splash_fade_out_animation); + delete ctx->splash_fade_in_animation; + delete ctx->splash_fade_out_animation; + ctx->splash_fade_in_animation = nullptr; + ctx->splash_fade_out_animation = nullptr; ctx->ui_clear_pass->set_cleared_buffers(false, true, false); } diff --git a/src/game/states/title.cpp b/src/game/states/title.cpp index 9991e72..f8ac806 100644 --- a/src/game/states/title.cpp +++ b/src/game/states/title.cpp @@ -21,7 +21,8 @@ #include "game/states/main-menu.hpp" #include "animation/screen-transition.hpp" #include "animation/ease.hpp" -#include "animation/timeline.hpp" +#include "animation/animation.hpp" +#include "animation/animator.hpp" #include "application.hpp" #include "scene/text.hpp" #include "configuration.hpp" @@ -35,30 +36,116 @@ void enter(game::context* ctx) { ctx->ui_clear_pass->set_cleared_buffers(true, true, false); - // Setup timing - const float title_fade_in_duration = 0.5f; - const float title_fade_out_duration = 0.5f; + // Construct title text + ctx->title_text = new scene::text(); + ctx->title_text->set_material(&ctx->title_font_material); + ctx->title_text->set_font(&ctx->title_font); + ctx->title_text->set_color({1.0f, 1.0f, 1.0f, 0.0f}); + ctx->title_text->set_content((*ctx->strings)["title_antkeeper"]); - // Start fade in - ctx->fade_transition->transition(title_fade_in_duration, true, ease::in_quad); + // Construct "Press any key" text + ctx->title_press_any_key_text = new scene::text(); + ctx->title_press_any_key_text->set_material(&ctx->menu_font_material); + ctx->title_press_any_key_text->set_font(&ctx->menu_font); + ctx->title_press_any_key_text->set_color({1.0f, 1.0f, 1.0f, 0.0f}); + ctx->title_press_any_key_text->set_content((*ctx->strings)["title_press_any_key"]); - // Crate fade out function - auto fade_out = [ctx, title_fade_out_duration]() - { - ctx->fade_transition->transition(title_fade_out_duration, false, ease::out_quad); - }; + int window_height = std::get<1>(ctx->app->get_viewport_dimensions()); + + // Align title text + const auto& title_aabb = static_cast&>(ctx->title_text->get_local_bounds()); + float title_w = title_aabb.max_point.x - title_aabb.min_point.x; + float title_h = title_aabb.max_point.y - title_aabb.min_point.y; + ctx->title_text->set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f + (window_height / 3) / 2), 0.0f}); + + // Align "Press any key" text + const auto& any_key_aabb = static_cast&>(ctx->title_press_any_key_text->get_local_bounds()); + float any_key_w = any_key_aabb.max_point.x - any_key_aabb.min_point.x; + float any_key_h = any_key_aabb.max_point.y - any_key_aabb.min_point.y; + ctx->title_press_any_key_text->set_translation({std::round(-any_key_w * 0.5f), std::round(-any_key_h * 0.5f - (window_height / 3) / 2), 0.0f}); - // Create change state function - auto change_state = [ctx]() + // Load animation timing configuration + double title_fade_in_duration = 0.0; + double title_fade_out_duration = 0.0; + double title_press_any_key_duration = 0.0; + double title_press_any_key_delay = 0.0; + if (ctx->config->contains("title_fade_in_duration")) + title_fade_in_duration = (*ctx->config)["title_fade_in_duration"].get(); + if (ctx->config->contains("title_fade_out_duration")) + title_fade_out_duration = (*ctx->config)["title_fade_out_duration"].get(); + if (ctx->config->contains("title_press_any_key_duration")) + title_press_any_key_duration = (*ctx->config)["title_press_any_key_duration"].get(); + if (ctx->config->contains("title_press_any_key_delay")) + title_press_any_key_delay = (*ctx->config)["title_press_any_key_delay"].get(); + + auto set_title_opacity = [ctx](int channel, const float& opacity) { - application::state next_state; - next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx); - next_state.exit = std::bind(game::state::main_menu::exit, ctx); - - ctx->app->change_state(next_state); + ctx->title_text->set_color({1.0f, 1.0f, 1.0f, opacity}); }; + // Build title fade in animation + ctx->title_fade_in_animation = new animation(); + animation_channel* title_fade_in_opacity_channel = ctx->title_fade_in_animation->add_channel(0); + ctx->title_fade_in_animation->set_interpolator(ease::in_quad); + title_fade_in_opacity_channel->insert_keyframe({0.0, 0.0f}); + title_fade_in_opacity_channel->insert_keyframe({title_fade_in_duration, 1.0f}); + title_fade_in_opacity_channel->insert_keyframe({title_fade_in_duration + title_press_any_key_delay, 1.0f}); + ctx->title_fade_in_animation->set_frame_callback(set_title_opacity); + + // Trigger "Press any key" animation after title fade in animation ends + ctx->title_fade_in_animation->set_end_callback + ( + [ctx]() + { + ctx->title_press_any_key_animation->play(); + } + ); + + // Build title fade out animation + ctx->title_fade_out_animation = new animation(); + animation_channel* title_fade_out_opacity_channel = ctx->title_fade_out_animation->add_channel(0); + ctx->title_fade_out_animation->set_interpolator(ease::out_quad); + title_fade_out_opacity_channel->insert_keyframe({0.0, 1.0f}); + title_fade_out_opacity_channel->insert_keyframe({title_fade_out_duration, 0.0f}); + ctx->title_fade_out_animation->set_frame_callback(set_title_opacity); + + // Trigger a state change when the title fade out animation ends + ctx->title_fade_out_animation->set_end_callback + ( + [ctx]() + { + application::state next_state; + next_state.name = "main_menu"; + next_state.enter = std::bind(game::state::main_menu::enter, ctx, 0); + next_state.exit = std::bind(game::state::main_menu::exit, ctx); + ctx->app->queue_state(next_state); + } + ); + + // Build "Press any key" animation + ctx->title_press_any_key_animation = new animation(); + ctx->title_press_any_key_animation->loop(true); + animation_channel* title_press_any_key_opacity_channel = ctx->title_press_any_key_animation->add_channel(0); + ctx->title_press_any_key_animation->set_interpolator(math::lerp); + title_press_any_key_opacity_channel->insert_keyframe({0.0, 0.0f}); + title_press_any_key_opacity_channel->insert_keyframe({title_press_any_key_duration * 0.5, 1.0f}); + title_press_any_key_opacity_channel->insert_keyframe({title_press_any_key_duration, 0.0f}); + ctx->title_press_any_key_animation->set_frame_callback + ( + [ctx](int channel, const float& opacity) + { + ctx->title_press_any_key_text->set_color({1.0f, 1.0f, 1.0f, 0.5f * ease::out_cubic(0.0f, 1.0f, opacity)}); + } + ); + + // Add title fade animations to animator + ctx->animator->add_animation(ctx->title_fade_in_animation); + ctx->animator->add_animation(ctx->title_fade_out_animation); + ctx->animator->add_animation(ctx->title_press_any_key_animation); + + // Start title fade in animation + ctx->title_fade_in_animation->play(); + // Set up title skipper ctx->input_listener->set_callback ( @@ -67,69 +154,66 @@ void enter(game::context* ctx) auto id = event.get_event_type_id(); if (id != mouse_moved_event::event_type_id && id != mouse_wheel_scrolled_event::event_type_id && id != gamepad_axis_moved_event::event_type_id) { - ctx->timeline->clear(); - ctx->fade_transition->get_animation()->stop(); - ctx->fade_transition->get_billboard()->set_active(false); - ctx->rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f); - ctx->rasterizer->clear_framebuffer(true, false, false); - ctx->app->swap_buffers(); + /* + if (ctx->title_fade_in_animation->is_stopped()) + { + ctx->title_fade_out_animation->play(); + ctx->input_listener->set_enabled(false); + } + */ - application::state next_state; - next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx); - next_state.exit = std::bind(game::state::main_menu::exit, ctx); - - ctx->app->change_state(next_state); + if (ctx->title_text->get_color()[3] > 0.0f) + { + ctx->input_listener->set_enabled(false); + + // Black out screen + ctx->rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f); + ctx->rasterizer->clear_framebuffer(true, false, false); + ctx->app->swap_buffers(); + + // Change state + application::state next_state; + next_state.name = "main_menu"; + next_state.enter = std::bind(game::state::main_menu::enter, ctx, 0); + next_state.exit = std::bind(game::state::main_menu::exit, ctx); + ctx->app->change_state(next_state); + + + } } } ); ctx->input_listener->set_enabled(true); - // Construct title text - ctx->title_text = new scene::text(); - ctx->title_text->set_material(&ctx->title_font_material); - ctx->title_text->set_font(&ctx->title_font); - ctx->title_text->set_color({1.0f, 1.0f, 1.0f, 1.0f}); - ctx->title_text->set_content((*ctx->strings)["title"]); ctx->ui_scene->add_object(ctx->title_text); + ctx->title_text->update_tweens(); - // Construct version string text - ctx->title_version_text = new scene::text(); - ctx->title_version_text->set_material(&ctx->debug_font_material); - ctx->title_version_text->set_font(&ctx->debug_font); - ctx->title_version_text->set_color({1.0f, 1.0f, 1.0f, 1.0f}); - ctx->title_version_text->set_content(ANTKEEPER_VERSION_STRING); - ctx->ui_scene->add_object(ctx->title_version_text); - - // Align title text - const auto& title_aabb = static_cast&>(ctx->title_text->get_local_bounds()); - float title_w = title_aabb.max_point.x - title_aabb.min_point.x; - float title_h = title_aabb.max_point.y - title_aabb.min_point.y; - ctx->title_text->set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f), 0.0f}); - - // Align version string - const auto& version_aabb = static_cast&>(ctx->title_version_text->get_local_bounds()); - float version_w = version_aabb.max_point.x - version_aabb.min_point.x; - float version_h = version_aabb.max_point.y - version_aabb.min_point.y; - const float version_padding = 12.0f; - auto viewport = ctx->app->get_viewport_dimensions(); - ctx->title_version_text->set_translation({viewport[0] * 0.5f - version_w - version_padding, -viewport[1] * 0.5f + version_padding, 0.0f}); + ctx->ui_scene->add_object(ctx->title_press_any_key_text); + ctx->title_press_any_key_text->update_tweens(); } void exit(game::context* ctx) { - // Remove and destruct title and version text + // Remove title text ctx->ui_scene->remove_object(ctx->title_text); - ctx->ui_scene->remove_object(ctx->title_version_text); - delete ctx->title_text; - delete ctx->title_version_text; - ctx->title_text = nullptr; - ctx->title_version_text = nullptr; + ctx->ui_scene->remove_object(ctx->title_press_any_key_text); // Disable title skipper ctx->input_listener->set_enabled(false); ctx->input_listener->set_callback(nullptr); + // Destruct title animations + ctx->animator->remove_animation(ctx->title_fade_in_animation); + ctx->animator->remove_animation(ctx->title_fade_out_animation); + ctx->animator->remove_animation(ctx->title_press_any_key_animation); + + delete ctx->title_fade_in_animation; + delete ctx->title_fade_out_animation; + delete ctx->title_press_any_key_animation; + ctx->title_fade_in_animation = nullptr; + ctx->title_fade_out_animation = nullptr; + ctx->title_press_any_key_animation = nullptr; + ctx->ui_clear_pass->set_cleared_buffers(false, true, false); }