Skip to content

Commit

Permalink
Config file for game settings (#17)
Browse files Browse the repository at this point in the history
* Initial config file implementation

* Log unexpected key and use systems

Uses only ECS systems now to tidy it up. Also will log a warning if
there is an unexpected key in the config file.

* Add clippy lints

Add `semicolon_if_nothing_returned` and `uninlined_format_args` for more
consistent code style

* Update README.md

* Add `shell.nix`

* Add instructions and feature for wayland

* Remove config plugin

We don't need to pretend that we can change the config while the game is
running (yet).
  • Loading branch information
Piturnah authored Jan 8, 2025
1 parent cfb4fad commit 23fe763
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 11 deletions.
45 changes: 45 additions & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ bevy_ninepatch = "0.9"
iyes_loopless = "0.9"
leafwing-input-manager = "0.7"

directories = "4.0"
itertools = "0.10"
noise = { git = "https://github.com/bsurmanski/noise-rs", rev = "5abdde1b819eccc47e74969c15e1b56ae5a055d6" }
rand = { version = "0.8", default_features = false, features = ["std", "small_rng"] }
serde = "1.0"
serde_derive = "1.0"
serde_ignored = "0.1.6"
toml = "0.5"

[dependencies.bevy]
version = "0.9"
Expand Down
23 changes: 19 additions & 4 deletions src/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ use bevy_kira_audio::prelude::*;
use iyes_loopless::prelude::*;
use rand::prelude::*;

use crate::{GameRng, GameState};
use crate::{config::Config, GameRng, GameState};

pub struct AudioPlugin;

impl Plugin for AudioPlugin {
fn build(&self, app: &mut App) {
app.add_event::<PlaySfx>()
.add_audio_channel::<BgmChannel>()
.add_audio_channel::<SfxChannel>()
.add_startup_system(load_audio)
.add_startup_system(set_volume)
.add_enter_system(GameState::MainMenu, start_title_bgm)
.add_enter_system(GameState::InGame, start_fight_bgm)
.add_exit_system(GameState::MainMenu, stop_bgm)
Expand All @@ -25,11 +27,14 @@ impl Plugin for AudioPlugin {
}
}

/// Resource for the background music channel. Exists so in future a user may change BGM volume
/// independently of SFX.
/// Resource for the background music channel.
#[derive(Resource)]
struct BgmChannel;

/// Resource for the sound effects channel.
#[derive(Resource)]
struct SfxChannel;

/// Event for SFX.
#[derive(Debug)]
pub enum PlaySfx {
Expand All @@ -38,6 +43,16 @@ pub enum PlaySfx {
BombFuse,
}

/// Update the channel volumes based on values in the [`Config`] resource.
fn set_volume(
bgm_channel: Res<AudioChannel<BgmChannel>>,
sfx_channel: Res<AudioChannel<SfxChannel>>,
config: Res<Config>,
) {
bgm_channel.set_volume(config.bgm_volume);
sfx_channel.set_volume(config.sfx_volume);
}

fn start_title_bgm(audio: Res<AudioChannel<BgmChannel>>, bgm: Res<Bgm>) {
audio.play(bgm.title_screen.clone()).looped();
}
Expand All @@ -60,7 +75,7 @@ fn stop_bgm(audio: Res<AudioChannel<BgmChannel>>) {
}

fn play_sfx(
audio: Res<Audio>,
audio: Res<AudioChannel<SfxChannel>>,
sfx: Res<Sfx>,
mut rng: ResMut<GameRng>,
mut ev_sfx: EventReader<PlaySfx>,
Expand Down
75 changes: 75 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Load the settings file for the game. This will be under the config folder by OS convention, for
//! example:
//!
//! Linux: `~/.config/bomby/config.toml`
//!
//! Currently, the config is loaded at startup and cannot be changed from inside the game. So, this
//! module does not export a bevy plugin (yet).
use bevy::prelude::*;

use directories::ProjectDirs;
use serde_derive::{Deserialize, Serialize};

use std::fs;

const DEFAULT_ASPECT_RATIO: f32 = 16.0 / 9.0;
const DEFAULT_WINDOW_HEIGHT: f32 = 900.0;
const DEFAULT_WINDOW_WIDTH: f32 = DEFAULT_WINDOW_HEIGHT * DEFAULT_ASPECT_RATIO;

/// Config resource containing runtime settings for the game.
#[derive(Resource, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
pub window_resizable: bool,
pub window_width: f32,
pub window_height: f32,
pub bgm_volume: f64,
pub sfx_volume: f64,
}

impl Default for Config {
fn default() -> Self {
Self {
window_resizable: true,
window_width: DEFAULT_WINDOW_WIDTH,
window_height: DEFAULT_WINDOW_HEIGHT,
bgm_volume: 1.0,
sfx_volume: 1.0,
}
}
}

/// Load the [`Config`] or generate a new one and insert it as a resource.
pub fn load_config() -> Config {
let dirs = ProjectDirs::from("com", "Spicy Lobster", "Bomby");
let mut config = dirs
.map(|dirs| {
let mut path = dirs.config_dir().to_path_buf();
path.push("config.toml");
let config_str = fs::read_to_string(&path).unwrap_or_else(|_| "".to_string());
let mut de = toml::de::Deserializer::new(&config_str);
let mut unused_keys = Vec::new();
let config =
serde_ignored::deserialize(&mut de, |path| unused_keys.push(path.to_string()))
.unwrap_or_else(|e| {
warn!("failed to parse config file {path:?}: {e}");
Config::default()
});

for key in unused_keys {
warn!("unrecognised config setting: {key}");
}
config
})
.unwrap_or_else(|| {
warn!("failed to get config path");
Config::default()
});

// Ensure sensible bounds.
config.bgm_volume = config.bgm_volume.clamp(0.0, 1.0);
config.sfx_volume = config.sfx_volume.clamp(0.0, 1.0);

config
}
15 changes: 8 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ use rand::{rngs::SmallRng, SeedableRng};
mod audio;
mod bomb;
mod camera;
mod config;
mod debug;
mod ldtk;
mod player;
mod ui;
mod z_sort;

const RESOLUTION: f32 = 16.0 / 9.0;
const WINDOW_HEIGHT: f32 = 900.0;
const WINDOW_WIDTH: f32 = WINDOW_HEIGHT * RESOLUTION;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GameState {
MainMenu,
Expand All @@ -29,22 +26,26 @@ pub enum GameState {
pub struct GameRng(SmallRng);

fn main() {
let config = config::load_config();
info!("Initialised config: {config:?}");

App::new()
.add_loopless_state(GameState::MainMenu)
.add_plugins(
DefaultPlugins
.set(ImagePlugin::default_nearest())
.set(WindowPlugin {
window: WindowDescriptor {
width: WINDOW_WIDTH,
height: WINDOW_HEIGHT,
width: config.window_width,
height: config.window_height,
title: "Bomby!".to_string(),
resizable: false,
resizable: config.window_resizable,
..default()
},
..default()
}),
)
.insert_resource(config)
.add_plugin(bevy_kira_audio::AudioPlugin)
.add_plugin(audio::AudioPlugin)
.add_plugin(debug::DebugPlugin)
Expand Down

0 comments on commit 23fe763

Please sign in to comment.