1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-03-25 03:19:27 +00:00

Add frame cap configuration

This commit is contained in:
Alula 2022-04-15 02:51:48 +02:00
parent 339f822a80
commit ca1d7a8642
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
16 changed files with 237 additions and 48 deletions

View file

@ -56,7 +56,20 @@
"subpixel_scrolling": "Subpixel scrolling:",
"original_textures": "Original textures:",
"seasonal_textures": "Seasonal textures:",
"renderer": "Renderer:"
"renderer": "Renderer:",
"vsync_mode": {
"entry": "V-Sync:",
"uncapped": "Uncapped",
"uncapped_desc": "No V-Sync, game runs at uncapped frame rate.",
"vsync": "Enabled",
"vsync_desc": "Frame rate is synchronized to V-Sync interval.",
"vrr_1x": "Variable Refresh Rate - 1x game tick",
"vrr_1x_desc": "Synced to 1*TPS, uses (G-/Free)Sync if available.",
"vrr_2x": "Variable Refresh Rate - 2x game tick",
"vrr_2x_desc": "Synced to 2*TPS, uses (G-/Free)Sync if available.",
"vrr_3x": "Variable Refresh Rate - 3x game tick",
"vrr_3x_desc": "Synced to 3*TPS, uses (G-/Free)Sync if available."
}
},
"sound": "Sound...",

View file

@ -7,7 +7,6 @@
"on": "オン",
"off": "オフ"
},
"menus": {
"main_menu": {
"start": "ゲームスタート",
@ -17,7 +16,6 @@
"jukebox": "ジュークボックス",
"quit": "辞める"
},
"pause_menu": {
"resume": "再開",
"retry": "リトライ",
@ -27,26 +25,22 @@
"quit": "辞める",
"quit_confirm": "辞める?"
},
"save_menu": {
"new": "新しいデータ",
"delete_info": "右矢印キーで削除",
"delete_confirm": "消去?"
},
"difficulty_menu": {
"title": "難易度選択",
"easy": "簡単",
"normal": "普通",
"hard": "難しい"
},
"challenge_menu": {
"start": "スタート",
"no_replay": "ノーリプレイ",
"replay_best": "ベストプレイを再生"
},
"options_menu": {
"graphics": "グラフィック",
"graphics_menu": {
@ -56,9 +50,21 @@
"subpixel_scrolling": "サブピクセルスクロール:",
"original_textures": "オリジナルテクスチャ:",
"seasonal_textures": "季節ものテクスチャ:",
"renderer": "レンダラ:"
"renderer": "レンダラ:",
"vsync_mode": {
"entry": "V-Sync:",
"uncapped": "Uncapped",
"uncapped_desc": "No V-Sync, game runs at uncapped frame rate.",
"vsync": "Enabled",
"vsync_desc": "Frame rate is synchronized to V-Sync interval.",
"vrr_1x": "Variable Refresh Rate - 1x game tick",
"vrr_1x_desc": "Synced to 1*TPS, uses (G-/Free)Sync if available.",
"vrr_2x": "Variable Refresh Rate - 2x game tick",
"vrr_2x_desc": "Synced to 2*TPS, uses (G-/Free)Sync if available.",
"vrr_3x": "Variable Refresh Rate - 3x game tick",
"vrr_3x_desc": "Synced to 3*TPS, uses (G-/Free)Sync if available."
}
},
"sound": "サウンド",
"sound_menu": {
"music_volume": "BGM音量",
@ -78,9 +84,7 @@
},
"soundtrack": "サウンドトラック: {soundtrack}"
},
"language": "言語",
"game_timing": {
"entry": "ゲームのタイミング:",
"50tps": "50tps (freeware)",
@ -88,14 +92,12 @@
}
}
},
"soundtrack": {
"organya": "オルガーニャ",
"remastered": "リマスター",
"new": "新",
"famitracks": "ファミトラック"
},
"game": {
"cutscene_skip": "{key} を押し続け、カットシーンをスキップ"
}

View file

@ -6,6 +6,7 @@ use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::BlendMode;
use crate::Game;
use crate::graphics::VSyncMode;
#[repr(C)]
#[derive(Copy, Clone)]
@ -30,7 +31,7 @@ pub trait Backend {
pub trait BackendEventLoop {
fn run(&mut self, game: &mut Game, ctx: &mut Context);
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>>;
fn new_renderer(&self, ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>>;
}
pub trait BackendRenderer {
@ -40,6 +41,8 @@ pub trait BackendRenderer {
fn present(&mut self) -> GameResult;
fn set_vsync_mode(&mut self, _mode: VSyncMode) -> GameResult { Ok(()) }
fn prepare_draw(&mut self, _width: f32, _height: f32) -> GameResult {
Ok(())
}

View file

@ -319,7 +319,7 @@ impl BackendEventLoop for GlutinEventLoop {
});
}
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
fn new_renderer(&self, _ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> {
let mut imgui = imgui::Context::create();
imgui.io_mut().display_size = [640.0, 480.0];

View file

@ -57,7 +57,7 @@ impl BackendEventLoop for NullEventLoop {
}
}
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
fn new_renderer(&self, _ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> {
let mut imgui = imgui::Context::create();
imgui.io_mut().display_size = [640.0, 480.0];
imgui.fonts().build_alpha8_texture();

View file

@ -5,7 +5,7 @@ use std::ffi::c_void;
use std::ops::Deref;
use std::ptr::{null, null_mut};
use std::rc::Rc;
use std::time::Duration;
use std::time::{Duration, Instant};
use imgui::internal::RawWrapper;
use imgui::{ConfigFlags, DrawCmd, DrawData, DrawIdx, DrawVert, Key, MouseCursor, TextureId, Ui};
@ -32,6 +32,7 @@ use crate::framework::ui::init_imgui;
use crate::Game;
use crate::GameError::RenderError;
use crate::GAME_SUSPENDED;
use crate::graphics::VSyncMode;
pub struct SDL2Backend {
context: Sdl,
@ -315,7 +316,7 @@ impl BackendEventLoop for SDL2EventLoop {
}
}
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
fn new_renderer(&self, ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> {
#[cfg(feature = "render-opengl")]
{
let mut refs = self.refs.borrow_mut();
@ -364,7 +365,7 @@ impl BackendEventLoop for SDL2EventLoop {
*user_data = Rc::into_raw(refs) as *mut c_void;
}
let gl_context = GLContext { gles2_mode: false, get_proc_address, swap_buffers, user_data };
let gl_context = GLContext { gles2_mode: false, is_sdl: true, get_proc_address, swap_buffers, user_data, ctx };
return Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui))));
} else {

View file

@ -3,6 +3,7 @@ use crate::framework::error::GameResult;
use crate::framework::filesystem::Filesystem;
use crate::framework::keyboard::KeyboardContext;
use crate::Game;
use crate::graphics::VSyncMode;
pub struct Context {
pub headless: bool,
@ -13,6 +14,7 @@ pub struct Context {
pub(crate) real_screen_size: (u32, u32),
pub(crate) screen_size: (f32, f32),
pub(crate) screen_insets: (f32, f32, f32, f32),
pub(crate) vsync_mode: VSyncMode,
}
impl Context {
@ -26,13 +28,14 @@ impl Context {
real_screen_size: (320, 240),
screen_size: (320.0, 240.0),
screen_insets: (0.0, 0.0, 0.0, 0.0),
vsync_mode: VSyncMode::Uncapped,
}
}
pub fn run(&mut self, game: &mut Game) -> GameResult {
let backend = init_backend(self.headless, self.size_hint)?;
let mut event_loop = backend.create_event_loop()?;
self.renderer = Some(event_loop.new_renderer()?);
self.renderer = Some(event_loop.new_renderer(self as *mut Context)?);
event_loop.run(game, self);

View file

@ -23,6 +23,20 @@ pub enum BlendMode {
Multiply,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum VSyncMode {
/// No V-Sync - uncapped frame rate
Uncapped,
/// Synchronized to V-Sync
VSync,
/// Variable Refresh Rate - Synchronized to game tick interval
VRRTickSync1x,
/// Variable Refresh Rate - Synchronized to 2 * game tick interval
VRRTickSync2x,
/// Variable Refresh Rate - Synchronized to 3 * game tick interval
VRRTickSync3x,
}
pub fn clear(ctx: &mut Context, color: Color) {
if let Some(renderer) = &mut ctx.renderer {
renderer.clear(color)
@ -30,20 +44,29 @@ pub fn clear(ctx: &mut Context, color: Color) {
}
pub fn present(ctx: &mut Context) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
renderer.present()?;
}
Ok(())
}
pub fn set_vsync_mode(ctx: &mut Context, mode: VSyncMode) -> GameResult {
if let Some(renderer) = &mut ctx.renderer {
ctx.vsync_mode = mode;
renderer.set_vsync_mode(mode);
}
Ok(())
}
#[allow(unused)]
pub fn renderer_initialized(ctx: &mut Context) -> bool {
ctx.renderer.is_some()
}
pub fn create_texture_mutable(ctx: &mut Context, width: u16, height: u16) -> GameResult<Box<dyn BackendTexture>> {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.create_texture_mutable(width, height);
}
@ -51,7 +74,7 @@ pub fn create_texture_mutable(ctx: &mut Context, width: u16, height: u16) -> Gam
}
pub fn create_texture(ctx: &mut Context, width: u16, height: u16, data: &[u8]) -> GameResult<Box<dyn BackendTexture>> {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.create_texture(width, height, data);
}
@ -72,7 +95,7 @@ pub fn screen_insets_scaled(ctx: &mut Context, scale: f32) -> (f32, f32, f32, f3
}
pub fn set_render_target(ctx: &mut Context, texture: Option<&Box<dyn BackendTexture>>) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.set_render_target(texture);
}
@ -80,7 +103,7 @@ pub fn set_render_target(ctx: &mut Context, texture: Option<&Box<dyn BackendText
}
pub fn set_blend_mode(ctx: &mut Context, blend: BlendMode) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.set_blend_mode(blend);
}
@ -88,7 +111,7 @@ pub fn set_blend_mode(ctx: &mut Context, blend: BlendMode) -> GameResult {
}
pub fn draw_rect(ctx: &mut Context, rect: Rect, color: Color) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.draw_rect(rect, color);
}
@ -97,7 +120,7 @@ pub fn draw_rect(ctx: &mut Context, rect: Rect, color: Color) -> GameResult {
#[allow(unused)]
pub fn draw_outline_rect(ctx: &mut Context, rect: Rect, line_width: usize, color: Color) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.draw_outline_rect(rect, line_width, color);
}
@ -105,7 +128,7 @@ pub fn draw_outline_rect(ctx: &mut Context, rect: Rect, line_width: usize, color
}
pub fn set_clip_rect(ctx: &mut Context, rect: Option<Rect>) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.set_clip_rect(rect);
}
@ -130,7 +153,7 @@ pub fn imgui_texture_id(ctx: &Context, texture: &Box<dyn BackendTexture>) -> Gam
}
pub fn prepare_imgui(ctx: &mut Context, ui: &imgui::Ui) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.prepare_imgui(ui);
}
@ -138,7 +161,7 @@ pub fn prepare_imgui(ctx: &mut Context, ui: &imgui::Ui) -> GameResult {
}
pub fn render_imgui(ctx: &mut Context, draw_data: &imgui::DrawData) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.render_imgui(draw_data);
}
@ -146,7 +169,7 @@ pub fn render_imgui(ctx: &mut Context, draw_data: &imgui::DrawData) -> GameResul
}
pub fn prepare_draw(ctx: &mut Context) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.prepare_draw(ctx.screen_size.0, ctx.screen_size.1);
}
@ -167,7 +190,7 @@ pub fn draw_triangle_list(
texture: Option<&Box<dyn BackendTexture>>,
shader: BackendShader,
) -> GameResult {
if let Some(renderer) = ctx.renderer.as_mut() {
if let Some(renderer) = &mut ctx.renderer {
return renderer.draw_triangle_list(vertices, texture, shader);
}

View file

@ -1,6 +1,7 @@
use std::any::Any;
use std::cell::{RefCell, UnsafeCell};
use std::ffi::{c_void, CStr};
use std::hint::unreachable_unchecked;
use std::mem;
use std::mem::MaybeUninit;
use std::ptr::null;
@ -10,19 +11,23 @@ use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert, TextureId, Ui};
use crate::common::{Color, Rect};
use crate::framework::backend::{BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData};
use crate::framework::context::Context;
use crate::framework::error::GameError;
use crate::framework::error::GameError::RenderError;
use crate::framework::error::GameResult;
use crate::framework::gl;
use crate::framework::gl::types::*;
use crate::framework::graphics::BlendMode;
use crate::framework::util::{field_offset, return_param};
use crate::GameError;
use crate::graphics::VSyncMode;
pub struct GLContext {
pub gles2_mode: bool,
pub is_sdl: bool,
pub get_proc_address: unsafe fn(user_data: &mut *mut c_void, name: &str) -> *const c_void,
pub swap_buffers: unsafe fn(user_data: &mut *mut c_void),
pub user_data: *mut c_void,
pub ctx: *mut Context,
}
pub struct OpenGLTexture {
@ -410,7 +415,17 @@ struct RenderShader {
impl Default for RenderShader {
fn default() -> Self {
Self { program_id: 0, texture: 0, proj_mtx: 0, scale: 0, time: 0, frame_offset: 0, position: 0, uv: 0, color: 0 }
Self {
program_id: 0,
texture: 0,
proj_mtx: 0,
scale: 0,
time: 0,
frame_offset: 0,
position: 0,
uv: 0,
color: 0,
}
}
}
@ -760,6 +775,34 @@ impl BackendRenderer for OpenGLRenderer {
Ok(())
}
fn set_vsync_mode(&mut self, mode: VSyncMode) -> GameResult {
if !self.refs.is_sdl {
return Ok(());
}
#[cfg(feature = "backend-sdl")]
unsafe {
let ctx = &mut *self.refs.ctx;
match mode {
VSyncMode::Uncapped => {
sdl2_sys::SDL_GL_SetSwapInterval(0);
},
VSyncMode::VSync => {
sdl2_sys::SDL_GL_SetSwapInterval(1);
}
_ => {
if sdl2_sys::SDL_GL_SetSwapInterval(-1) == -1 {
log::warn!("Failed to enable variable refresh rate, falling back to non-V-Sync.");
sdl2_sys::SDL_GL_SetSwapInterval(0);
}
}
}
}
Ok(())
}
fn prepare_draw(&mut self, width: f32, height: f32) -> GameResult {
if let Some((_, gl)) = self.get_context() {
unsafe {

View file

@ -8,7 +8,7 @@ use std::cell::UnsafeCell;
use std::env;
use std::path::PathBuf;
use std::sync::Mutex;
use std::time::Instant;
use std::time::{Duration, Instant};
use directories::ProjectDirs;
use lazy_static::lazy_static;
@ -18,6 +18,7 @@ use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult};
use crate::framework::filesystem::{mount_user_vfs, mount_vfs};
use crate::framework::graphics;
use crate::framework::graphics::VSyncMode;
use crate::framework::ui::UI;
use crate::framework::vfs::PhysicalFS;
use crate::scene::loading_scene::LoadingScene;
@ -84,7 +85,9 @@ pub struct Game {
start_time: Instant,
last_tick: u128,
next_tick: u128,
loops: u64,
loops: u32,
next_tick_draw: u128,
present: bool,
fps: Fps,
}
@ -98,6 +101,8 @@ impl Game {
last_tick: 0,
next_tick: 0,
loops: 0,
next_tick_draw: 0,
present: true,
fps: Fps::new(),
};
@ -144,6 +149,7 @@ impl Game {
for _ in 0..self.loops {
scene.tick(state_ref, ctx)?;
}
self.fps.tick_count = self.fps.tick_count.saturating_add(self.loops as u32);
}
TimingMode::FrameSynchronized => {
scene.tick(state_ref, ctx)?;
@ -156,6 +162,40 @@ impl Game {
fn draw(&mut self, ctx: &mut Context) -> GameResult {
let state_ref = unsafe { &mut *self.state.get() };
match ctx.vsync_mode {
VSyncMode::Uncapped | VSyncMode::VSync => {
self.present = true;
}
_ => unsafe {
self.present = false;
let divisor = match ctx.vsync_mode {
VSyncMode::VRRTickSync1x => 1,
VSyncMode::VRRTickSync2x => 2,
VSyncMode::VRRTickSync3x => 3,
_ => std::hint::unreachable_unchecked(),
};
let delta = (state_ref.settings.timing_mode.get_delta() / divisor) as u64;
let now = self.start_time.elapsed().as_nanos();
if now > self.next_tick_draw + delta as u128 * 4 {
self.next_tick_draw = now;
}
while self.start_time.elapsed().as_nanos() >= self.next_tick_draw {
self.next_tick_draw += delta as u128;
self.present = true;
}
},
}
if !self.present {
std::thread::sleep(Duration::from_millis(2));
self.loops = 0;
return Ok(());
}
if ctx.headless {
self.loops = 0;
state_ref.frame_time = 1.0;
@ -197,7 +237,7 @@ impl Game {
}
if state_ref.settings.fps_counter {
self.fps.act(state_ref, ctx, self.last_tick)?;
self.fps.act(state_ref, ctx, self.start_time.elapsed().as_nanos())?;
}
self.ui.draw(state_ref, ctx, scene)?;

View file

@ -37,7 +37,7 @@ impl MenuEntry {
MenuEntry::Disabled(_) => 16.0,
MenuEntry::Toggle(_, _) => 16.0,
MenuEntry::Options(_, _, _) => 16.0,
MenuEntry::DescriptiveOptions(_, _, _, _) => 16.0,
MenuEntry::DescriptiveOptions(_, _, _, _) => 32.0,
MenuEntry::OptionsBar(_, _) => 16.0,
MenuEntry::SaveData(_) => 32.0,
MenuEntry::SaveDataSingle(_) => 32.0,

View file

@ -158,7 +158,8 @@ impl SaveSelectMenu {
filesystem::user_open(ctx, state.get_save_filename(state.save_slot).unwrap_or("".to_string()))
{
if let MenuEntry::SaveData(save) = self.save_menu.entries[slot] {
self.save_detailed.entries[0] = MenuEntry::SaveDataSingle(save);
self.save_detailed.entries.clear();
self.save_detailed.push_entry(MenuEntry::SaveDataSingle(save));
}
self.current_menu = CurrentMenu::LoadConfirm;
self.load_confirm.selected = 0;

View file

@ -11,6 +11,7 @@ use crate::menu::{Menu, MenuSelectionResult};
use crate::scene::title_scene::TitleScene;
use crate::shared_game_state::{Language, SharedGameState, TimingMode};
use crate::sound::InterpolationMode;
use crate::{graphics, VSyncMode};
#[derive(PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
@ -47,6 +48,24 @@ impl SettingsMenu {
}
pub fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
self.graphics.push_entry(MenuEntry::DescriptiveOptions(
state.t("menus.options_menu.graphics_menu.vsync_mode.entry"),
state.settings.vsync_mode as usize,
vec![
state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vsync"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x"),
],
vec![
state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped_desc"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vsync_desc"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x_desc"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x_desc"),
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x_desc"),
],
));
self.graphics.push_entry(MenuEntry::Toggle(
state.t("menus.options_menu.graphics_menu.lighting_effects"),
state.settings.shader_effects,
@ -265,6 +284,23 @@ impl SettingsMenu {
},
CurrentMenu::GraphicsMenu => match self.graphics.tick(controller, state) {
MenuSelectionResult::Selected(0, toggle) => {
if let MenuEntry::DescriptiveOptions(_, value, _, _) = toggle {
let (new_mode, new_value) = match *value {
0 => (VSyncMode::VSync, 1),
1 => (VSyncMode::VRRTickSync1x, 2),
2 => (VSyncMode::VRRTickSync2x, 3),
3 => (VSyncMode::VRRTickSync3x, 4),
_ => (VSyncMode::Uncapped, 0),
};
*value = new_value;
state.settings.vsync_mode = new_mode;
graphics::set_vsync_mode(ctx, new_mode);
let _ = state.settings.save(ctx);
}
}
MenuSelectionResult::Selected(1, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
state.settings.shader_effects = !state.settings.shader_effects;
let _ = state.settings.save(ctx);
@ -272,7 +308,7 @@ impl SettingsMenu {
*value = state.settings.shader_effects;
}
}
MenuSelectionResult::Selected(1, toggle) => {
MenuSelectionResult::Selected(2, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
state.settings.light_cone = !state.settings.light_cone;
let _ = state.settings.save(ctx);
@ -280,7 +316,7 @@ impl SettingsMenu {
*value = state.settings.light_cone;
}
}
MenuSelectionResult::Selected(2, toggle) => {
MenuSelectionResult::Selected(3, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
state.settings.motion_interpolation = !state.settings.motion_interpolation;
let _ = state.settings.save(ctx);
@ -288,7 +324,7 @@ impl SettingsMenu {
*value = state.settings.motion_interpolation;
}
}
MenuSelectionResult::Selected(3, toggle) => {
MenuSelectionResult::Selected(4, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
state.settings.subpixel_coords = !state.settings.subpixel_coords;
let _ = state.settings.save(ctx);
@ -296,7 +332,7 @@ impl SettingsMenu {
*value = state.settings.subpixel_coords;
}
}
MenuSelectionResult::Selected(4, toggle) => {
MenuSelectionResult::Selected(5, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
state.settings.original_textures = !state.settings.original_textures;
if self.on_title {
@ -309,7 +345,7 @@ impl SettingsMenu {
*value = state.settings.original_textures;
}
}
MenuSelectionResult::Selected(5, toggle) => {
MenuSelectionResult::Selected(6, toggle) => {
if let MenuEntry::Toggle(_, value) = toggle {
state.settings.seasonal_textures = !state.settings.seasonal_textures;
state.reload_graphics();
@ -318,7 +354,7 @@ impl SettingsMenu {
*value = state.settings.seasonal_textures;
}
}
MenuSelectionResult::Selected(7, _) | MenuSelectionResult::Canceled => {
MenuSelectionResult::Selected(8, _) | MenuSelectionResult::Canceled => {
self.current = CurrentMenu::MainMenu
}
_ => (),
@ -366,6 +402,8 @@ impl SettingsMenu {
if let MenuEntry::Active(soundtrack) = entry {
if soundtrack == &state.settings.soundtrack {
active_soundtrack_index = idx;
let _ = state.settings.save(ctx);
break;
}
}
}

View file

@ -1,5 +1,6 @@
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::graphics;
use crate::scene::no_data_scene::NoDataScene;
use crate::scene::Scene;
use crate::shared_game_state::SharedGameState;
@ -43,6 +44,8 @@ impl Scene for LoadingScene {
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
graphics::set_vsync_mode(ctx, state.settings.vsync_mode)?;
match state.texture_set.get_or_load_batch(ctx, &state.constants, "Loading") {
Ok(batch) => {
batch.add(

View file

@ -2,6 +2,7 @@ use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem::{user_create, user_open};
use crate::framework::keyboard::ScanCode;
use crate::graphics::VSyncMode;
use crate::input::keyboard_player_controller::KeyboardController;
use crate::input::player_controller::PlayerController;
use crate::input::touch_player_controller::TouchPlayerController;
@ -47,6 +48,8 @@ pub struct Settings {
pub debug_outlines: bool,
pub fps_counter: bool,
pub locale: Language,
#[serde(default = "default_vsync")]
pub vsync_mode: VSyncMode,
}
fn default_true() -> bool {
@ -55,7 +58,7 @@ fn default_true() -> bool {
#[inline(always)]
fn current_version() -> u32 {
7
8
}
#[inline(always)]
@ -83,6 +86,11 @@ fn default_locale() -> Language {
Language::English
}
#[inline(always)]
fn default_vsync() -> VSyncMode {
VSyncMode::VSync
}
impl Settings {
pub fn load(ctx: &Context) -> GameResult<Settings> {
if let Ok(file) = user_open(ctx, "/settings.json") {
@ -125,6 +133,11 @@ impl Settings {
self.locale = default_locale();
}
if self.version == 7 {
self.version = 8;
self.vsync_mode = default_vsync();
}
if self.version != initial_version {
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
}
@ -176,6 +189,7 @@ impl Default for Settings {
debug_outlines: false,
fps_counter: false,
locale: Language::English,
vsync_mode: VSyncMode::VSync,
}
}
}

View file

@ -137,23 +137,28 @@ impl FontData {
pub struct Fps {
pub frame_count: u32,
pub fps: u32,
pub tick_count: u32,
pub tps: u32,
last_capture: u128,
}
impl Fps {
pub fn new() -> Fps {
Fps { frame_count: 0, fps: 0, last_capture: 0 }
Fps { frame_count: 0, fps: 0, tick_count: 0, tps: 0, last_capture: 0 }
}
pub fn act(&mut self, state: &mut SharedGameState, ctx: &mut Context, time: u128) -> GameResult {
if time - self.last_capture > 1000000000 {
self.fps = self.frame_count;
self.frame_count = 0;
self.tps = self.tick_count;
self.tick_count = 0;
self.last_capture = time;
} else {
self.frame_count += 1;
self.frame_count = self.frame_count.saturating_add(1);
}
draw_number(state.canvas_size.0 - 8.0, 8.0, self.fps as usize, Alignment::Right, state, ctx)?;
draw_number(state.canvas_size.0 - 8.0, 16.0, self.tps as usize, Alignment::Right, state, ctx)?;
Ok(())
}
}