Skip to main content

phichain-game

The phichain-game crate is a Bevy-based game engine for rendering and playing Phigros charts. It handles rendering, animations, hit effects, scoring, and user interface.

Installation

Add this to your Cargo.toml:
[dependencies]
phichain-game = "1.0.0-beta.5"
phichain-chart = { version = "1.0.0-beta.5", features = ["bevy"] }
phichain-assets = "1.0.0-beta.5"
bevy = "0.16.1"

Dependencies

The crate depends on:
  • bevy 0.16.1 (game engine)
  • phichain-chart (chart data structures)
  • phichain-assets (asset management)
  • bevy_prototype_lyon (for rendering shapes)
  • bevy_kira_audio 0.23.0 (audio playback)
  • image 0.25.2 (image processing)

Core Plugin

GamePlugin
struct
The main Phigros game plugin.This plugin provides:
  • Updating translations for entities with Lines and Notes
  • Multi-highlight support for notes
  • Hit effects with animations and particles
  • Curve note generation based on CurveNoteTracks
  • Score calculation and display
  • Game UI rendering
use bevy::prelude::*;
use phichain_game::GamePlugin;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(GamePlugin)
        .run();
}

Resources

GameConfig

GameConfig
struct
Configuration for game behavior and rendering.
use phichain_game::GameConfig;
use bevy::prelude::*;

fn setup(mut commands: Commands) {
    commands.insert_resource(GameConfig {
        note_scale: 1.2,              // Scale notes by 120%
        fc_ap_indicator: true,        // Show FC/AP indicators
        multi_highlight: true,        // Enable multi-note highlights
        hide_hit_effect: false,       // Show hit effects
        name: "Test Song".to_string(),
        level: "IN Lv.15".to_string(),
        hit_effect_follow_game_time: false,
    });
}
  • note_scale: f32 - Scale multiplier for note rendering (default: 1.0)
  • fc_ap_indicator: bool - Show Full Combo/All Perfect indicators (default: true)
  • multi_highlight: bool - Highlight multiple simultaneous notes (default: true)
  • hide_hit_effect: bool - Hide hit effects when notes are hit (default: false)
  • name: String - Song name displayed in UI
  • level: String - Difficulty level displayed in UI
  • hit_effect_follow_game_time: bool - Use chart time for hit effects (useful for rendering)

GameViewport

GameViewport
struct
Defines the game rendering viewport.
use phichain_game::GameViewport;
use bevy::prelude::*;

fn setup_viewport(mut commands: Commands) {
    commands.insert_resource(GameViewport(
        Rect::from_corners(
            Vec2::new(-450.0, -800.0),
            Vec2::new(450.0, 800.0),
        )
    ));
}

ChartTime

ChartTime
struct
Current time position in the chart (in seconds).
use phichain_game::ChartTime;
use bevy::prelude::*;

fn update_time(mut chart_time: ResMut<ChartTime>, time: Res<Time>) {
    chart_time.0 += time.delta_secs();
}

Paused

Paused
struct
Indicates whether the chart is paused.
use phichain_game::Paused;
use bevy::prelude::*;

fn toggle_pause(mut paused: ResMut<Paused>, keyboard: Res<ButtonInput<KeyCode>>) {
    if keyboard.just_pressed(KeyCode::Space) {
        paused.0 = !paused.0;
    }
}

Loading Charts

The loader module provides functions to load charts into the game world.
load_project
function
Load a complete project into the Bevy world.
use phichain_game::loader::load_project;
use phichain_chart::project::Project;
use bevy::prelude::*;
use std::path::PathBuf;

fn load_chart(mut commands: Commands) {
    let project = Project::open(PathBuf::from("./my_chart"))
        .expect("Failed to open project");
    
    load_project(&project, &mut commands)
        .expect("Failed to load project");
}
The loader automatically:
  • Spawns line entities with Line components
  • Spawns note entities as children of lines
  • Spawns event entities linked to lines
  • Loads and spawns illustration images
  • Inserts Offset and BpmList resources

Audio System

The audio module handles music and sound effects.
use phichain_game::audio::*;
use bevy::prelude::*;
use bevy_kira_audio::prelude::*;

fn play_music(
    audio: Res<Audio>,
    music_handle: Res<Handle<AudioSource>>,
) {
    audio.play(music_handle.clone())
        .with_volume(0.8)
        .looped();
}

Rendering

Line Rendering

Lines are automatically rendered based on their components:
  • LinePosition - Position in screen coordinates
  • LineRotation - Rotation angle in degrees
  • LineOpacity - Alpha transparency (0.0 to 1.0)

Note Rendering

Notes are rendered with different sprites based on their NoteKind:
  • Tap notes: Circular sprite
  • Drag notes: Drag sprite
  • Hold notes: Head, body, and tail sprites
  • Flick notes: Flick sprite with indicator

Hit Effects

Hit effects are automatically spawned when notes are hit, including:
  • Particle animations
  • Expanding circles
  • Fade-out effects

Scoring

The scoring system tracks:
  • Perfect hits (32ms window)
  • Good hits (160ms window)
  • Bad hits (180ms window)
  • Misses
  • Combo count
  • Score calculation
  • FC (Full Combo) and AP (All Perfect) status

User Interface

The ui module provides game UI components:
  • Score display
  • Combo counter
  • Song name and difficulty
  • FC/AP indicators
  • Progress bar

Complete Example

use bevy::prelude::*;
use phichain_game::{GamePlugin, GameConfig, GameViewport, ChartTime, Paused};
use phichain_assets::{AssetsPlugin, setup_assets};
use phichain_chart::project::Project;
use std::path::PathBuf;

fn main() {
    // Setup asset paths
    setup_assets();
    
    App::new()
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                title: "Phichain Game".to_string(),
                resolution: (900.0, 1600.0).into(),
                ..default()
            }),
            ..default()
        }))
        .add_plugins(AssetsPlugin)
        .add_plugins(GamePlugin)
        .add_systems(Startup, setup)
        .add_systems(Update, (update_time, handle_input))
        .run();
}

fn setup(mut commands: Commands) {
    // Setup camera
    commands.spawn(Camera2d);
    
    // Configure game
    commands.insert_resource(GameConfig {
        note_scale: 1.0,
        fc_ap_indicator: true,
        multi_highlight: true,
        hide_hit_effect: false,
        name: "My Song".to_string(),
        level: "HD Lv.14".to_string(),
        hit_effect_follow_game_time: false,
    });
    
    // Setup viewport
    commands.insert_resource(GameViewport(
        Rect::from_corners(
            Vec2::new(-450.0, -800.0),
            Vec2::new(450.0, 800.0),
        )
    ));
    
    // Load project
    let project = Project::open(PathBuf::from("./charts/example"))
        .expect("Failed to open project");
    
    phichain_game::loader::load_project(&project, &mut commands)
        .expect("Failed to load project");
}

fn update_time(
    mut chart_time: ResMut<ChartTime>,
    paused: Res<Paused>,
    time: Res<Time>,
) {
    if !paused.0 {
        chart_time.0 += time.delta_secs();
    }
}

fn handle_input(
    mut paused: ResMut<Paused>,
    keyboard: Res<ButtonInput<KeyCode>>,
) {
    if keyboard.just_pressed(KeyCode::Space) {
        paused.0 = !paused.0;
    }
}

System Sets

GameSet
SystemSet
System set for all game-related systems. Use this to control system ordering.
use bevy::prelude::*;
use phichain_game::GameSet;

fn my_plugin(app: &mut App) {
    app.add_systems(Update, my_system.after(GameSet));
}

fn my_system() {
    // This runs after all game systems
}

Non-blocking Loader

For loading charts asynchronously without blocking the main thread:
use phichain_game::loader::nonblocking::NonblockingLoaderPlugin;
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(NonblockingLoaderPlugin)
        .run();
}

See Also