Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul dialog visuals #153

Merged
merged 6 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ polyanya = "0.2.0"
bevy_pathmesh = "0.3.0"
bitflags = "1.3.2"
iyes_progress = "0.7.1"
unicode-segmentation = "1.10.1"

# keep the following in sync with Bevy's dependencies
winit = { version = "0.27", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions assets/dialogs/follower.dlg.ron
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
),
"page:exit": (
text: "Goodbye.\n*The creature\'s gaze shifts ever so slightly. It now looks just past you into the void.*",
talking_speed: 2.,
next_page: Exit,
),
"page:main-choice-unnest": (
Expand Down
51 changes: 51 additions & 0 deletions src/ingame_menu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::player_control::actions::{Actions, ActionsFrozen};
use crate::GameState;
use bevy::prelude::*;
use bevy_egui::{egui, EguiContext};

/// Handles the pause menu accessed while playing the game via ESC.
pub struct IngameMenuPlugin;

impl Plugin for IngameMenuPlugin {
fn build(&self, app: &mut App) {
{
app.add_system_set(SystemSet::on_update(GameState::Playing).with_system(handle_pause));
}
}
}

fn handle_pause(
mut time: ResMut<Time>,
actions: Res<Actions>,
mut actions_frozen: ResMut<ActionsFrozen>,
mut egui_context: ResMut<EguiContext>,
mut paused: Local<bool>,
) {
let toggled = actions.ui.toggle_pause;
if *paused {
if toggled {
*paused = false;
time.unpause();
actions_frozen.unfreeze();
} else {
egui::CentralPanel::default()
.frame(egui::Frame {
fill: egui::Color32::from_black_alpha(240),
..default()
})
.show(egui_context.ctx_mut(), |ui| {
ui.vertical_centered_justified(|ui| {
ui.visuals_mut().override_text_color = Some(egui::Color32::WHITE);
ui.add_space(100.0);
ui.heading("Game Paused");
ui.separator();
ui.label("Press ESC to resume");
});
});
}
} else if toggled {
*paused = true;
time.pause();
actions_frozen.freeze();
}
}
6 changes: 5 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod bevy_config;
#[cfg(feature = "dev")]
pub mod dev;
pub mod file_system_interaction;
pub mod ingame_menu;
pub mod level_instantiation;
pub mod menu;
pub mod movement;
Expand All @@ -27,6 +28,7 @@ pub use crate::bevy_config::BevyConfigPlugin;
#[cfg(feature = "dev")]
use crate::dev::DevPlugin;
use crate::file_system_interaction::FileSystemInteractionPlugin;
use crate::ingame_menu::IngameMenuPlugin;
use crate::level_instantiation::LevelInstantiationPlugin;
use crate::menu::MenuPlugin;
use crate::movement::MovementPlugin;
Expand Down Expand Up @@ -57,6 +59,7 @@ enum GameState {
/// - [`FileSystemInteractionPlugin`]: Handles the loading and saving of games.
/// - [`ShaderPlugin`]: Handles the shaders.
/// - [`DevPlugin`]: Handles the dev tools.
/// - [`IngameMenuPlugin`]: Handles the ingame menu accessed via ESC.
pub struct GamePlugin;

impl Plugin for GamePlugin {
Expand All @@ -69,7 +72,8 @@ impl Plugin for GamePlugin {
.add_plugin(WorldInteractionPlugin)
.add_plugin(LevelInstantiationPlugin)
.add_plugin(FileSystemInteractionPlugin)
.add_plugin(ShaderPlugin);
.add_plugin(ShaderPlugin)
.add_plugin(IngameMenuPlugin);
#[cfg(feature = "dev")]
app.add_plugin(DevPlugin);
}
Expand Down
3 changes: 3 additions & 0 deletions src/player_control/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ pub struct CameraActions {
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect, Default, Serialize, Deserialize)]
#[reflect(Serialize, Deserialize)]
pub struct UiActions {
#[cfg(feature = "dev")]
pub toggle_editor: bool,
pub toggle_pause: bool,
pub numbered_choice: [bool; 10],
}

Expand All @@ -85,6 +87,7 @@ pub fn set_actions(
) {
*actions = default();
actions.ui.toggle_editor = GameControl::ToggleEditor.just_pressed(&keyboard_input);
actions.ui.toggle_pause = GameControl::TogglePause.just_pressed(&keyboard_input);
for i in 0..=9 {
actions.ui.numbered_choice[i] =
GameControl::NumberedChoice(i as u16).just_pressed(&keyboard_input);
Expand Down
3 changes: 3 additions & 0 deletions src/player_control/actions/game_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub enum GameControl {
Sprint,
Jump,
ToggleEditor,
TogglePause,
Interact,
NumberedChoice(u16),
}
Expand Down Expand Up @@ -149,6 +150,8 @@ generate_bindings! {
#[cfg(target_os = "linux")] 0x10,
#[cfg(target_arch = "wasm32")] 0x10,
),
],
GameControl::TogglePause => [
// Esc
ScanCode(
#[cfg(target_os = "macos")] 53,
Expand Down
98 changes: 72 additions & 26 deletions src/world_interaction/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ use crate::file_system_interaction::asset_loading::DialogAssets;
use crate::player_control::actions::{Actions, ActionsFrozen, UiActions};
use crate::util::log_error::log_errors;
use crate::world_interaction::condition::{ActiveConditions, ConditionAddEvent, ConditionId};
use crate::world_interaction::dialog::resources::Page;
pub use crate::world_interaction::dialog::resources::{
CurrentDialog, Dialog, DialogEvent, DialogId, InitialPage, NextPage,
};
use crate::GameState;
use anyhow::{Context, Result};
use bevy::prelude::*;
use bevy_egui::egui::Color32;
use bevy_egui::{egui, EguiContext, EguiPlugin};
use serde::{Deserialize, Serialize};
use unicode_segmentation::UnicodeSegmentation;

mod resources;

Expand Down Expand Up @@ -85,6 +86,7 @@ fn set_current_dialog(
Ok(())
}

#[allow(clippy::too_many_arguments)]
fn show_dialog(
mut commands: Commands,
current_dialog: Option<ResMut<CurrentDialog>>,
Expand All @@ -93,39 +95,62 @@ fn show_dialog(
mut egui_context: ResMut<EguiContext>,
mut actions_frozen: ResMut<ActionsFrozen>,
actions: Res<Actions>,
time: Res<Time>,
mut elapsed_time: Local<f32>,
) -> Result<()> {
let mut current_dialog = match current_dialog {
Some(current_dialog) => current_dialog,
None => return Ok(()),
None => {
*elapsed_time = 0.0;
return Ok(());
}
};
let height = 150.;
let current_page = current_dialog.fetch_current_page()?;
egui::TopBottomPanel::bottom("Dialog")
let dialog_text = create_dialog_rich_text(&current_page, *elapsed_time);

let dialog_size = egui::Vec2::new(500., 150.);
egui::Window::new("Dialog")
.resizable(true)
.anchor(egui::Align2::CENTER_BOTTOM, egui::Vec2::new(0., -30.))
.collapsible(false)
.resizable(false)
.exact_height(height)
.title_bar(false)
.frame(egui::Frame {
fill: Color32::from_black_alpha(250),
fill: egui::Color32::from_black_alpha(220),
inner_margin: egui::style::Margin::same(25.),
rounding: egui::Rounding::same(30.0),
..default()
})
.show(egui_context.ctx_mut(), |ui| {
ui.vertical_centered_justified(|ui| {
ui.add_space(5.);
ui.label(current_page.text.clone());
ui.add_space(8.);
present_choices(
ui,
&mut commands,
&mut current_dialog,
&active_conditions,
&mut condition_writer,
&mut actions_frozen,
&actions.ui,
current_page.next_page,
)
.expect("Failed to present dialog choices");
ui.set_width(dialog_size.x);
ui.set_height(dialog_size.y);

let style = ui.style_mut();
style.visuals.button_frame = false;
style.visuals.override_text_color = Some(egui::Color32::WHITE);
ui.vertical(|ui| {
ui.add_space(5.);
ui.label(dialog_text.clone());
if dialog_text.text() == current_page.text {
ui.add_space(3.);
ui.separator();
ui.add_space(8.);
present_choices(
ui,
&mut commands,
&mut current_dialog,
&active_conditions,
&mut condition_writer,
&mut actions_frozen,
&actions.ui,
current_page.next_page,
&mut elapsed_time,
)
.expect("Failed to present dialog choices");
}
});
});
*elapsed_time += time.delta_seconds();
Ok(())
}

Expand All @@ -139,11 +164,14 @@ fn present_choices(
actions_frozen: &mut ActionsFrozen,
actions: &UiActions,
next_page: NextPage,
elapsed_time: &mut f32,
) -> Result<()> {
match next_page {
NextPage::Continue(next_page_id) => {
if ui.button("1. Continue").clicked() || actions.numbered_choice[1] {
let text = create_choice_rich_text(0, "Continue");
if ui.button(text).clicked() || actions.numbered_choice[1] {
current_dialog.current_page = next_page_id;
*elapsed_time = 0.0;
}
}
NextPage::Choice(choices) => {
Expand All @@ -156,16 +184,16 @@ fn present_choices(
})
.enumerate()
{
let index = index + 1;
let text = format!("{}. {}", index, choice.text);
if ui.button(text).clicked() || actions.numbered_choice[index] {
let text = create_choice_rich_text(index, &choice.text);
if ui.button(text).clicked() || actions.numbered_choice[index + 1] {
picked_choice = Some((choice_id.clone(), choice.clone()));
}
}
if let Some((choice_id, choice)) = picked_choice {
condition_writer.send(ConditionAddEvent(choice_id.clone()));
current_dialog.last_choice = Some(choice_id);
current_dialog.current_page = choice.next_page_id;
*elapsed_time = 0.0;
}
}
NextPage::SameAs(other_page_id) => {
Expand All @@ -179,10 +207,12 @@ fn present_choices(
actions_frozen,
actions,
next_page,
elapsed_time,
)?;
}
NextPage::Exit => {
if ui.button("1. Exit").clicked() || actions.numbered_choice[1] {
let text = create_choice_rich_text(0, "Exit");
if ui.button(text).clicked() || actions.numbered_choice[1] {
commands.remove_resource::<CurrentDialog>();
actions_frozen.unfreeze();
}
Expand All @@ -191,6 +221,22 @@ fn present_choices(
Ok(())
}

fn create_dialog_rich_text(page: &Page, elapsed_time: f32) -> egui::RichText {
const BASE_LETTERS_PER_SECOND: f32 = 60.;
let letters_to_display = (BASE_LETTERS_PER_SECOND * page.talking_speed * elapsed_time) as usize;
let text: String = page.text.graphemes(true).take(letters_to_display).collect();
egui::RichText::new(text)
.color(egui::Color32::from_gray(250))
.size(16.)
}

fn create_choice_rich_text(index: usize, text: &str) -> egui::RichText {
let text = format!("{}. {}", index + 1, text);
egui::RichText::new(text)
.color(egui::Color32::from_gray(220))
.size(14.)
}

fn was_just_picked(current_dialog: &CurrentDialog, choice_id: &ConditionId) -> bool {
current_dialog
.last_choice
Expand Down
22 changes: 19 additions & 3 deletions src/world_interaction/dialog/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct DialogEvent {
pub page: Option<PageId>,
}

#[derive(Debug, Clone, Eq, PartialEq, Resource, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Resource, Serialize, Deserialize)]
pub struct CurrentDialog {
pub source: Entity,
pub id: DialogId,
Expand All @@ -35,7 +35,7 @@ impl CurrentDialog {
}
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, TypeUuid, Default)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TypeUuid, Default)]
#[uuid = "f7c10043-7196-4ead-a4dd-040c33798a62"]
pub struct Dialog {
pub initial_page: Vec<InitialPage>,
Expand All @@ -59,12 +59,28 @@ impl InitialPage {
}
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Page {
pub text: String,
#[serde(default = "get_default_talking_speed")]
pub talking_speed: f32,
pub next_page: NextPage,
}

fn get_default_talking_speed() -> f32 {
1.
}

impl Default for Page {
fn default() -> Self {
Self {
text: default(),
talking_speed: get_default_talking_speed(),
next_page: default(),
}
}
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum NextPage {
/// There is only one automatic option for the next page
Expand Down