mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-12-06 12:24:47 +00:00
more precise and less jittery game loop timing
This commit is contained in:
parent
4b9abc5f1e
commit
72992eb4e4
|
|
@ -36,6 +36,7 @@ use crate::ggez::{Context, GameError, GameResult};
|
||||||
use crate::ggez::conf;
|
use crate::ggez::conf;
|
||||||
use crate::ggez::vfs::{self, VFS};
|
use crate::ggez::vfs::{self, VFS};
|
||||||
pub use crate::ggez::vfs::OpenOptions;
|
pub use crate::ggez::vfs::OpenOptions;
|
||||||
|
use directories::ProjectDirs;
|
||||||
|
|
||||||
const CONFIG_NAME: &str = "/conf.toml";
|
const CONFIG_NAME: &str = "/conf.toml";
|
||||||
|
|
||||||
|
|
@ -43,9 +44,9 @@ const CONFIG_NAME: &str = "/conf.toml";
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Filesystem {
|
pub struct Filesystem {
|
||||||
vfs: vfs::OverlayFS,
|
vfs: vfs::OverlayFS,
|
||||||
/*resources_path: path::PathBuf,
|
//resources_path: path::PathBuf,
|
||||||
user_config_path: path::PathBuf,
|
user_config_path: path::PathBuf,
|
||||||
user_data_path: path::PathBuf,*/
|
user_data_path: path::PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a file, either in the filesystem, or in the resources zip file,
|
/// Represents a file, either in the filesystem, or in the resources zip file,
|
||||||
|
|
@ -112,12 +113,12 @@ impl Filesystem {
|
||||||
// Set up VFS to merge resource path, root path, and zip path.
|
// Set up VFS to merge resource path, root path, and zip path.
|
||||||
let mut overlay = vfs::OverlayFS::new();
|
let mut overlay = vfs::OverlayFS::new();
|
||||||
|
|
||||||
/*let mut resources_path;
|
|
||||||
let mut resources_zip_path;
|
|
||||||
let user_data_path;
|
let user_data_path;
|
||||||
let user_config_path;
|
let user_config_path;
|
||||||
|
// let mut resources_path;
|
||||||
|
// let mut resources_zip_path;
|
||||||
|
|
||||||
let project_dirs = match ProjectDirs::from("", author, id) {
|
let project_dirs = match ProjectDirs::from("", "", id) {
|
||||||
Some(dirs) => dirs,
|
Some(dirs) => dirs,
|
||||||
None => {
|
None => {
|
||||||
return Err(GameError::FilesystemError(String::from(
|
return Err(GameError::FilesystemError(String::from(
|
||||||
|
|
@ -128,7 +129,7 @@ impl Filesystem {
|
||||||
|
|
||||||
|
|
||||||
// <game exe root>/resources/
|
// <game exe root>/resources/
|
||||||
{
|
/*{
|
||||||
resources_path = root_path.clone();
|
resources_path = root_path.clone();
|
||||||
resources_path.push("resources");
|
resources_path.push("resources");
|
||||||
trace!("Resources path: {:?}", resources_path);
|
trace!("Resources path: {:?}", resources_path);
|
||||||
|
|
@ -147,13 +148,13 @@ impl Filesystem {
|
||||||
} else {
|
} else {
|
||||||
trace!("No resources zip file found");
|
trace!("No resources zip file found");
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Per-user data dir,
|
// Per-user data dir,
|
||||||
// ~/.local/share/whatever/
|
// ~/.local/share/whatever/
|
||||||
{
|
{
|
||||||
user_data_path = project_dirs.data_local_dir();
|
user_data_path = project_dirs.data_local_dir();
|
||||||
trace!("User-local data path: {:?}", user_data_path);
|
log::trace!("User-local data path: {:?}", user_data_path);
|
||||||
let physfs = vfs::PhysicalFS::new(&user_data_path, true);
|
let physfs = vfs::PhysicalFS::new(&user_data_path, true);
|
||||||
overlay.push_back(Box::new(physfs));
|
overlay.push_back(Box::new(physfs));
|
||||||
}
|
}
|
||||||
|
|
@ -162,15 +163,15 @@ impl Filesystem {
|
||||||
// Save game dir is read-write
|
// Save game dir is read-write
|
||||||
{
|
{
|
||||||
user_config_path = project_dirs.config_dir();
|
user_config_path = project_dirs.config_dir();
|
||||||
trace!("User-local configuration path: {:?}", user_config_path);
|
log::trace!("User-local configuration path: {:?}", user_config_path);
|
||||||
let physfs = vfs::PhysicalFS::new(&user_config_path, false);
|
let physfs = vfs::PhysicalFS::new(&user_config_path, false);
|
||||||
overlay.push_back(Box::new(physfs));
|
overlay.push_back(Box::new(physfs));
|
||||||
}*/
|
}
|
||||||
|
|
||||||
let fs = Filesystem {
|
let fs = Filesystem {
|
||||||
vfs: overlay,
|
vfs: overlay,
|
||||||
//user_config_path: user_config_path.to_path_buf(),
|
user_config_path: user_config_path.to_path_buf(),
|
||||||
//user_data_path: user_data_path.to_path_buf(),
|
user_data_path: user_data_path.to_path_buf(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(fs)
|
Ok(fs)
|
||||||
|
|
@ -473,6 +474,8 @@ mod tests {
|
||||||
ofs.push_front(Box::new(physfs));
|
ofs.push_front(Box::new(physfs));
|
||||||
Filesystem {
|
Filesystem {
|
||||||
vfs: ofs,
|
vfs: ofs,
|
||||||
|
user_config_path: path,
|
||||||
|
user_data_path: path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
32
src/lib.rs
32
src/lib.rs
|
|
@ -18,11 +18,11 @@ use std::time::Instant;
|
||||||
|
|
||||||
use log::*;
|
use log::*;
|
||||||
use pretty_env_logger::env_logger::Env;
|
use pretty_env_logger::env_logger::Env;
|
||||||
use winit::event::{ElementState, Event, KeyboardInput, WindowEvent, TouchPhase};
|
use winit::event::{ElementState, Event, KeyboardInput, TouchPhase, WindowEvent};
|
||||||
use winit::event_loop::ControlFlow;
|
use winit::event_loop::ControlFlow;
|
||||||
|
|
||||||
use crate::builtin_fs::BuiltinFS;
|
use crate::builtin_fs::BuiltinFS;
|
||||||
use crate::ggez::{Context, ContextBuilder, filesystem, GameResult, GameError};
|
use crate::ggez::{Context, ContextBuilder, filesystem, GameError, GameResult};
|
||||||
use crate::ggez::conf::{Backend, WindowMode, WindowSetup};
|
use crate::ggez::conf::{Backend, WindowMode, WindowSetup};
|
||||||
use crate::ggez::event::{KeyCode, KeyMods};
|
use crate::ggez::event::{KeyCode, KeyMods};
|
||||||
use crate::ggez::graphics;
|
use crate::ggez::graphics;
|
||||||
|
|
@ -73,7 +73,7 @@ struct Game {
|
||||||
ui: UI,
|
ui: UI,
|
||||||
def_matrix: ColumnMatrix4<f32>,
|
def_matrix: ColumnMatrix4<f32>,
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
next_tick: u64,
|
next_tick: u128,
|
||||||
loops: u64,
|
loops: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,23 +96,21 @@ impl Game {
|
||||||
if let Some(scene) = self.scene.as_mut() {
|
if let Some(scene) = self.scene.as_mut() {
|
||||||
match self.state.timing_mode {
|
match self.state.timing_mode {
|
||||||
TimingMode::_50Hz | TimingMode::_60Hz => {
|
TimingMode::_50Hz | TimingMode::_60Hz => {
|
||||||
while self.start_time.elapsed().as_millis() as u64 > self.next_tick && self.loops < 3 {
|
while self.start_time.elapsed().as_nanos() >= self.next_tick && self.loops < 3 {
|
||||||
self.next_tick += self.state.timing_mode.get_delta() as u64;
|
if (self.state.settings.speed - 1.0).abs() < 0.01 {
|
||||||
|
self.next_tick += self.state.timing_mode.get_delta() as u128;
|
||||||
|
} else {
|
||||||
|
self.next_tick += (self.state.timing_mode.get_delta() as f64 / self.state.settings.speed) as u128;
|
||||||
|
}
|
||||||
self.loops += 1;
|
self.loops += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..self.loops {
|
for _ in 0..self.loops {
|
||||||
scene.tick(&mut self.state, ctx)?;
|
scene.tick(&mut self.state, ctx)?;
|
||||||
if self.state.settings.speed_hack {
|
|
||||||
scene.tick(&mut self.state, ctx)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TimingMode::FrameSynchronized => {
|
TimingMode::FrameSynchronized => {
|
||||||
scene.tick(&mut self.state, ctx)?;
|
scene.tick(&mut self.state, ctx)?;
|
||||||
if self.state.settings.speed_hack {
|
|
||||||
scene.tick(&mut self.state, ctx)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,9 +154,19 @@ impl Game {
|
||||||
KeyCode::X => { state.key_state.set_fire(true) }
|
KeyCode::X => { state.key_state.set_fire(true) }
|
||||||
KeyCode::A => { state.key_state.set_weapon_prev(true) }
|
KeyCode::A => { state.key_state.set_weapon_prev(true) }
|
||||||
KeyCode::S => { state.key_state.set_weapon_next(true) }
|
KeyCode::S => { state.key_state.set_weapon_next(true) }
|
||||||
|
KeyCode::F7 => { state.set_speed(1.0) }
|
||||||
|
KeyCode::F8 => {
|
||||||
|
if state.settings.speed > 0.5 {
|
||||||
|
state.set_speed(state.settings.speed - 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::F9 => {
|
||||||
|
if state.settings.speed < 3.0 {
|
||||||
|
state.set_speed(state.settings.speed + 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
KeyCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines }
|
KeyCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines }
|
||||||
KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode }
|
KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode }
|
||||||
KeyCode::F12 => { state.set_speed_hack(!state.settings.speed_hack) }
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,9 +75,9 @@ impl TitleScene {
|
||||||
static ENGINE_VERSION: &str = "doukutsu-rs 0.1.0";
|
static ENGINE_VERSION: &str = "doukutsu-rs 0.1.0";
|
||||||
// asset copyright for freeware version
|
// asset copyright for freeware version
|
||||||
static COPYRIGHT_PIXEL: &str = "2004.12 Studio Pixel";
|
static COPYRIGHT_PIXEL: &str = "2004.12 Studio Pixel";
|
||||||
// asset copyright for Nicalis, why they've even replaced © with @?
|
// asset copyright for Nicalis
|
||||||
static COPYRIGHT_NICALIS: &str = "@2011 NICALIS INC.";
|
static COPYRIGHT_NICALIS: &str = "@2011 NICALIS INC.";
|
||||||
static COPYRIGHT_NICALIS_SWITCH: &str = "@2017 NICALIS INC."; // untested?
|
static COPYRIGHT_NICALIS_SWITCH: &str = "@2017 NICALIS INC.";
|
||||||
|
|
||||||
static DISCORD_LINK: &str = "https://discord.gg/fbRsNNB";
|
static DISCORD_LINK: &str = "https://discord.gg/fbRsNNB";
|
||||||
|
|
||||||
|
|
@ -100,7 +100,6 @@ impl Scene for TitleScene {
|
||||||
} else {
|
} else {
|
||||||
self.option_menu.push_entry(MenuEntry::Disabled("Original textures".to_string()));
|
self.option_menu.push_entry(MenuEntry::Disabled("Original textures".to_string()));
|
||||||
}
|
}
|
||||||
self.option_menu.push_entry(MenuEntry::Toggle("2x Speed hack".to_string(), state.settings.speed_hack));
|
|
||||||
self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string()));
|
self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string()));
|
||||||
self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned()));
|
self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned()));
|
||||||
self.option_menu.push_entry(MenuEntry::Active("Back".to_string()));
|
self.option_menu.push_entry(MenuEntry::Active("Back".to_string()));
|
||||||
|
|
@ -180,19 +179,12 @@ impl Scene for TitleScene {
|
||||||
*value = state.settings.original_textures;
|
*value = state.settings.original_textures;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuSelectionResult::Selected(4, toggle) => {
|
MenuSelectionResult::Selected(4, _) => {
|
||||||
if let MenuEntry::Toggle(_, value) = toggle {
|
|
||||||
state.set_speed_hack(!state.settings.speed_hack);
|
|
||||||
|
|
||||||
*value = state.settings.speed_hack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuSelectionResult::Selected(5, _) => {
|
|
||||||
if let Err(e) = webbrowser::open(DISCORD_LINK) {
|
if let Err(e) = webbrowser::open(DISCORD_LINK) {
|
||||||
log::warn!("Error opening web browser: {}", e);
|
log::warn!("Error opening web browser: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuSelectionResult::Selected(7, _) | MenuSelectionResult::Canceled => {
|
MenuSelectionResult::Selected(6, _) | MenuSelectionResult::Canceled => {
|
||||||
self.current_menu = CurrentMenu::MainMenu;
|
self.current_menu = CurrentMenu::MainMenu;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ pub enum TimingMode {
|
||||||
impl TimingMode {
|
impl TimingMode {
|
||||||
pub fn get_delta(self) -> usize {
|
pub fn get_delta(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
TimingMode::_50Hz => { 1000 / 50 }
|
TimingMode::_50Hz => { 1000000000 / 50 }
|
||||||
TimingMode::_60Hz => { 1000 / 60 }
|
TimingMode::_60Hz => { 1000000000 / 60 }
|
||||||
TimingMode::FrameSynchronized => { 0 }
|
TimingMode::FrameSynchronized => { 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -40,7 +40,7 @@ impl TimingMode {
|
||||||
|
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub god_mode: bool,
|
pub god_mode: bool,
|
||||||
pub speed_hack: bool,
|
pub speed: f64,
|
||||||
pub original_textures: bool,
|
pub original_textures: bool,
|
||||||
pub enhanced_graphics: bool,
|
pub enhanced_graphics: bool,
|
||||||
pub debug_outlines: bool,
|
pub debug_outlines: bool,
|
||||||
|
|
@ -131,7 +131,7 @@ impl SharedGameState {
|
||||||
sound_manager: SoundManager::new(ctx)?,
|
sound_manager: SoundManager::new(ctx)?,
|
||||||
settings: Settings {
|
settings: Settings {
|
||||||
god_mode: false,
|
god_mode: false,
|
||||||
speed_hack: false,
|
speed: 1.0,
|
||||||
original_textures: false,
|
original_textures: false,
|
||||||
enhanced_graphics: true,
|
enhanced_graphics: true,
|
||||||
debug_outlines: false,
|
debug_outlines: false,
|
||||||
|
|
@ -230,10 +230,10 @@ impl SharedGameState {
|
||||||
self.carets.push(Caret::new(x, y, ctype, direct, &self.constants));
|
self.carets.push(Caret::new(x, y, ctype, direct, &self.constants));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_speed_hack(&mut self, toggle: bool) {
|
pub fn set_speed(&mut self, value: f64) {
|
||||||
self.settings.speed_hack = toggle;
|
self.settings.speed = value;
|
||||||
|
|
||||||
if let Err(err) = self.sound_manager.set_speed(if toggle { 2.0 } else { 1.0 }) {
|
if let Err(err) = self.sound_manager.set_speed(value as f32) {
|
||||||
log::error!("Error while sending a message to sound manager: {}", err);
|
log::error!("Error while sending a message to sound manager: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue