use crate::time::Time;
use crate::input::InputState;
use crate::camera::Camera;
use crate::color::Color;
use crate::math::Vec2;
#[cfg(feature = "audio")]
use crate::audio::AudioManager;
#[cfg(feature = "ui")]
use crate::ui::{UiState, UiTheme};
#[cfg(feature = "particles")]
use crate::particles::ParticleEmitter;
#[cfg(feature = "tween")]
use crate::tween::TweenManager;
#[derive(Debug, Clone)]
pub struct WindowConfig {
pub title: String,
pub width: u32,
pub height: u32,
pub resizable: bool,
pub fullscreen: bool,
pub visible: bool,
pub min_size: Option<(u32, u32)>,
pub max_size: Option<(u32, u32)>,
pub vsync: bool,
pub msaa_samples: u32,
pub high_dpi: bool,
pub icon_path: Option<String>,
pub target_fps: u32,
pub clear_color: Color,
}
impl Default for WindowConfig {
fn default() -> Self {
Self {
title: "game-gem".to_string(),
width: 800,
height: 600,
resizable: true,
fullscreen: false,
visible: true,
min_size: None,
max_size: None,
vsync: true,
msaa_samples: 0,
high_dpi: true,
icon_path: None,
target_fps: 0,
clear_color: Color::from_hex("#1A1A2E").unwrap(),
}
}
}
#[derive(Debug, Clone)]
pub enum DrawCommand {
Clear { color: Color },
DrawCircle { x: f32, y: f32, radius: f32, color: Color },
DrawRect { x: f32, y: f32, w: f32, h: f32, color: Color },
DrawLine { x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, color: Color },
DrawText { text: String, x: f32, y: f32, size: f32, color: Color },
SetCamera { camera: Camera },
DrawTriangle { x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, color: Color },
DrawPoly { points: Vec<Vec2>, color: Color },
DrawEllipse { x: f32, y: f32, rx: f32, ry: f32, color: Color },
DrawRing { x: f32, y: f32, inner_radius: f32, outer_radius: f32, color: Color },
DrawArc { x: f32, y: f32, radius: f32, start_angle: f32, end_angle: f32, color: Color },
}
pub struct GraphicsContext {
commands: Vec<DrawCommand>,
pub camera: Camera,
default_camera: Camera,
pub screen_size: Vec2,
pub dpi_scale: f32,
}
impl GraphicsContext {
fn new(width: u32, height: u32) -> Self {
let screen_size = Vec2::new(width as f32, height as f32);
let default_camera = Camera::centered();
Self {
commands: Vec::with_capacity(1024),
camera: default_camera.clone(),
default_camera,
screen_size,
dpi_scale: 1.0,
}
}
pub fn clear(&mut self, color: Color) {
self.commands.push(DrawCommand::Clear { color });
}
pub fn draw_circle(&mut self, x: f32, y: f32, radius: f32, color: Color) {
self.commands.push(DrawCommand::DrawCircle { x, y, radius, color });
}
pub fn draw_rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color) {
self.commands.push(DrawCommand::DrawRect { x, y, w, h, color });
}
pub fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, color: Color) {
self.commands.push(DrawCommand::DrawLine { x1, y1, x2, y2, thickness, color });
}
pub fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: Color) {
self.commands.push(DrawCommand::DrawText {
text: text.to_string(),
x, y, size, color,
});
}
pub fn draw_triangle(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32, color: Color) {
self.commands.push(DrawCommand::DrawTriangle { x1, y1, x2, y2, x3, y3, color });
}
pub fn draw_poly(&mut self, points: Vec<Vec2>, color: Color) {
self.commands.push(DrawCommand::DrawPoly { points, color });
}
pub fn draw_ellipse(&mut self, x: f32, y: f32, rx: f32, ry: f32, color: Color) {
self.commands.push(DrawCommand::DrawEllipse { x, y, rx, ry, color });
}
pub fn draw_ring(&mut self, x: f32, y: f32, inner_radius: f32, outer_radius: f32, color: Color) {
self.commands.push(DrawCommand::DrawRing { x, y, inner_radius, outer_radius, color });
}
pub fn draw_arc(&mut self, x: f32, y: f32, radius: f32, start_angle: f32, end_angle: f32, color: Color) {
self.commands.push(DrawCommand::DrawArc { x, y, radius, start_angle, end_angle, color });
}
pub fn set_camera(&mut self, camera: Camera) {
self.commands.push(DrawCommand::SetCamera { camera });
}
pub fn reset_camera(&mut self) {
self.commands.push(DrawCommand::SetCamera {
camera: self.default_camera.clone(),
});
}
fn flush(&mut self) -> Vec<DrawCommand> {
std::mem::take(&mut self.commands)
}
}
pub struct Context {
pub time: Time,
pub input: InputState,
pub graphics: GraphicsContext,
#[cfg(feature = "audio")]
pub audio: AudioManager,
#[cfg(feature = "ui")]
pub ui_state: UiState,
#[cfg(feature = "ui")]
pub ui_theme: UiTheme,
#[cfg(feature = "particles")]
pub particles: Vec<ParticleEmitter>,
#[cfg(feature = "tween")]
pub tweens: TweenManager,
pub window: WindowConfig,
should_quit: bool,
paused: bool,
}
impl Default for Context {
fn default() -> Self {
Self::new(WindowConfig::default())
}
}
impl Context {
pub(crate) fn new(config: WindowConfig) -> Self {
let graphics = GraphicsContext::new(config.width, config.height);
Self {
time: Time::new(),
input: InputState::default(),
graphics,
#[cfg(feature = "audio")]
audio: AudioManager::new(),
#[cfg(feature = "ui")]
ui_state: UiState::default(),
#[cfg(feature = "ui")]
ui_theme: UiTheme::default(),
#[cfg(feature = "particles")]
particles: Vec::new(),
#[cfg(feature = "tween")]
tweens: TweenManager::new(),
window: config,
should_quit: false,
paused: false,
}
}
pub fn quit(&mut self) {
self.should_quit = true;
}
pub fn is_quitting(&self) -> bool {
self.should_quit
}
pub fn pause(&mut self) {
self.paused = true;
}
pub fn resume(&mut self) {
self.paused = false;
}
pub fn is_paused(&self) -> bool {
self.paused
}
pub fn toggle_pause(&mut self) {
self.paused = !self.paused;
}
pub fn screen_width(&self) -> f32 {
self.graphics.screen_size.x
}
pub fn screen_height(&self) -> f32 {
self.graphics.screen_size.y
}
pub fn screen_size(&self) -> Vec2 {
self.graphics.screen_size
}
pub fn handle_resize(&mut self, width: u32, height: u32) {
self.graphics.screen_size = Vec2::new(width as f32, height as f32);
self.window.width = width;
self.window.height = height;
}
}
pub trait GameState {
fn on_enter(&mut self, _ctx: &mut Context) {}
fn on_exit(&mut self, _ctx: &mut Context) {}
fn fixed_update(&mut self, _ctx: &mut Context) {}
fn update(&mut self, ctx: &mut Context);
fn render(&mut self, ctx: &mut Context);
}
pub struct Game {
config: WindowConfig,
}
impl Game {
pub fn new() -> Self {
Self {
config: WindowConfig::default(),
}
}
pub fn window_title(mut self, title: &str) -> Self {
self.config.title = title.to_string();
self
}
pub fn window_size(mut self, width: u32, height: u32) -> Self {
self.config.width = width;
self.config.height = height;
self
}
pub fn resizable(mut self, resizable: bool) -> Self {
self.config.resizable = resizable;
self
}
pub fn fullscreen(mut self, fullscreen: bool) -> Self {
self.config.fullscreen = fullscreen;
self
}
pub fn vsync(mut self, vsync: bool) -> Self {
self.config.vsync = vsync;
self
}
pub fn msaa(mut self, samples: u32) -> Self {
self.config.msaa_samples = samples;
self
}
pub fn clear_color(mut self, color: Color) -> Self {
self.config.clear_color = color;
self
}
pub fn target_fps(mut self, fps: u32) -> Self {
self.config.target_fps = fps;
self
}
pub fn min_size(mut self, w: u32, h: u32) -> Self {
self.config.min_size = Some((w, h));
self
}
pub fn icon(mut self, path: &str) -> Self {
self.config.icon_path = Some(path.to_string());
self
}
pub fn run<S: GameState>(self, mut state: S) {
let mut ctx = Context::new(self.config);
state.on_enter(&mut ctx);
#[cfg(feature = "audio")]
{
}
loop {
ctx.time.tick();
ctx.input.keyboard.pressed_this_frame.clear();
ctx.input.keyboard.released_this_frame.clear();
ctx.input.mouse.pressed_this_frame.clear();
ctx.input.mouse.released_this_frame.clear();
ctx.input.mouse.scroll = Vec2::ZERO;
ctx.input.mouse.delta = Vec2::ZERO;
let fixed_steps = ctx.time.fixed_step_count();
for _ in 0..fixed_steps {
state.fixed_update(&mut ctx);
}
if !ctx.paused {
state.update(&mut ctx);
}
#[cfg(feature = "audio")]
ctx.audio.update(ctx.time.delta() as f32);
#[cfg(feature = "particles")]
for emitter in &mut ctx.particles {
emitter.update(ctx.time.delta() as f32);
}
#[cfg(feature = "tween")]
{ let _ = ctx.tweens.update(ctx.time.delta() as f32); }
#[cfg(feature = "ui")]
ctx.ui_state.update_animations(
ctx.time.delta() as f32,
ctx.ui_theme.animation_speed,
);
ctx.graphics.camera.update(ctx.time.delta() as f32);
state.render(&mut ctx);
let _commands = ctx.graphics.flush();
if ctx.should_quit {
break;
}
}
state.on_exit(&mut ctx);
}
}
impl Default for Game {
fn default() -> Self {
Self::new()
}
}