// 35_game_development.ruchy - Game development patterns and systems
import std::game
import std::physics
import std::graphics
fn main() {
println("=== Game Development ===\n")
// Game state management
println("=== Game State ===")
enum GameState {
Menu,
Playing,
Paused,
GameOver
}
struct Game {
state: GameState = GameState::Menu,
score: int = 0,
lives: int = 3,
level: int = 1,
entities: list = [],
systems: list = [],
input_handler: InputHandler,
renderer: Renderer
}
impl Game {
fn update(mut self, delta_time) {
match self.state {
GameState::Menu => self.update_menu(delta_time),
GameState::Playing => self.update_playing(delta_time),
GameState::Paused => self.update_paused(delta_time),
GameState::GameOver => self.update_game_over(delta_time)
}
}
fn update_playing(mut self, delta_time) {
// Update all systems
for system in self.systems {
system.update(self.entities, delta_time)
}
// Check game conditions
if self.lives <= 0 {
self.state = GameState::GameOver
}
}
fn render(self) {
self.renderer.clear()
match self.state {
GameState::Menu => self.render_menu(),
GameState::Playing => self.render_game(),
GameState::Paused => {
self.render_game()
self.render_pause_overlay()
},
GameState::GameOver => self.render_game_over()
}
self.renderer.present()
}
}
// Entity Component System (ECS)
println("\n=== Entity Component System ===")
struct Entity {
id: int,
components: map = {}
}
impl Entity {
fn add_component(mut self, name, component) {
self.components[name] = component
self
}
fn get_component(self, name) {
self.components.get(name)
}
fn has_component(self, name) {
name in self.components
}
}
// Components
struct Transform {
position: Vec3,
rotation: Quaternion,
scale: Vec3 = Vec3(1, 1, 1)
}
struct Velocity {
linear: Vec3,
angular: Vec3
}
struct Sprite {
texture: Texture,
color: Color = Color::white(),
flip_x: bool = false,
flip_y: bool = false
}
struct Collider {
shape: Shape,
is_trigger: bool = false,
layer: int = 0
}
struct Health {
current: int,
max: int
}
// Systems
struct MovementSystem {}
impl MovementSystem {
fn update(self, entities, delta_time) {
for entity in entities {
if let Some(transform) = entity.get_component("transform") {
if let Some(velocity) = entity.get_component("velocity") {
transform.position += velocity.linear * delta_time
transform.rotation *= Quaternion::from_euler(
velocity.angular * delta_time
)
}
}
}
}
}
struct CollisionSystem {
spatial_hash: SpatialHash
}
impl CollisionSystem {
fn update(self, entities, delta_time) {
// Update spatial hash
self.spatial_hash.clear()
for entity in entities {
if let Some(collider) = entity.get_component("collider") {
let transform = entity.get_component("transform")
self.spatial_hash.insert(entity, transform.position)
}
}
// Check collisions
for entity in entities {
if !entity.has_component("collider") { continue }
let nearby = self.spatial_hash.query(entity.position, radius: 100)
for other in nearby {
if entity.id == other.id { continue }
if self.check_collision(entity, other) {
self.handle_collision(entity, other)
}
}
}
}
fn check_collision(self, a, b) {
let collider_a = a.get_component("collider")
let collider_b = b.get_component("collider")
let transform_a = a.get_component("transform")
let transform_b = b.get_component("transform")
physics::intersects(
collider_a.shape, transform_a,
collider_b.shape, transform_b
)
}
}
// Input handling
println("\n=== Input Handling ===")
struct InputHandler {
key_states: map = {},
mouse_position: Vec2 = Vec2::zero(),
gamepad_states: map = {}
}
impl InputHandler {
fn is_key_down(self, key) {
self.key_states.get(key, false)
}
fn is_key_pressed(self, key) {
// True only on the frame the key was pressed
self.key_states.get(f"{key}_pressed", false)
}
fn get_axis(self, negative_key, positive_key) {
let mut value = 0.0
if self.is_key_down(negative_key) { value -= 1.0 }
if self.is_key_down(positive_key) { value += 1.0 }
value
}
fn get_vector(self) {
Vec2(
self.get_axis("left", "right"),
self.get_axis("up", "down")
)
}
fn update(mut self) {
// Clear pressed states
for key in self.key_states.keys() {
if key.ends_with("_pressed") {
self.key_states[key] = false
}
}
// Process new input events
for event in game::poll_events() {
match event {
KeyDown(key) => {
self.key_states[key] = true
self.key_states[f"{key}_pressed"] = true
},
KeyUp(key) => {
self.key_states[key] = false
},
MouseMove(x, y) => {
self.mouse_position = Vec2(x, y)
}
}
}
}
}
// Player controller
println("\n=== Player Controller ===")
struct PlayerController {
speed: float = 200.0,
jump_force: float = 500.0,
is_grounded: bool = false
}
impl PlayerController {
fn update(self, entity, input, delta_time) {
let transform = entity.get_component("transform")
let velocity = entity.get_component("velocity")
// Horizontal movement
let horizontal = input.get_axis("a", "d")
velocity.linear.x = horizontal * self.speed
// Jumping
if self.is_grounded && input.is_key_pressed("space") {
velocity.linear.y = self.jump_force
self.is_grounded = false
}
// Apply gravity
if !self.is_grounded {
velocity.linear.y -= physics::GRAVITY * delta_time
}
}
fn on_collision_enter(mut self, collision) {
// Check if landed on ground
if collision.normal.y > 0.7 {
self.is_grounded = true
}
}
}
// Animation system
println("\n=== Animation System ===")
struct AnimationClip {
name: string,
frames: list,
duration: float,
loop: bool = true
}
struct Animator {
clips: map,
current_clip: Option<AnimationClip> = None,
current_time: float = 0.0,
current_frame: int = 0
}
impl Animator {
fn play(mut self, clip_name) {
if let Some(clip) = self.clips.get(clip_name) {
self.current_clip = Some(clip)
self.current_time = 0.0
self.current_frame = 0
}
}
fn update(mut self, delta_time) {
if let Some(clip) = self.current_clip {
self.current_time += delta_time
let frame_duration = clip.duration / clip.frames.len()
let new_frame = (self.current_time / frame_duration).floor()
if new_frame != self.current_frame {
if new_frame >= clip.frames.len() {
if clip.loop {
self.current_time = 0.0
self.current_frame = 0
} else {
self.current_frame = clip.frames.len() - 1
}
} else {
self.current_frame = new_frame
}
}
}
}
fn get_current_frame(self) {
if let Some(clip) = self.current_clip {
clip.frames[self.current_frame]
} else {
None
}
}
}
// Particle system
println("\n=== Particle System ===")
struct Particle {
position: Vec3,
velocity: Vec3,
color: Color,
size: float,
lifetime: float,
age: float = 0.0
}
struct ParticleEmitter {
position: Vec3,
emission_rate: float,
particle_lifetime: float,
initial_velocity: Vec3,
velocity_variance: Vec3,
color: Color,
color_variance: Color,
size: float,
size_variance: float,
gravity: Vec3 = Vec3(0, -9.8, 0),
particles: list = [],
time_since_emission: float = 0.0
}
impl ParticleEmitter {
fn update(mut self, delta_time) {
// Emit new particles
self.time_since_emission += delta_time
let particles_to_emit = (self.time_since_emission * self.emission_rate).floor()
for _ in 0..particles_to_emit {
self.emit_particle()
}
self.time_since_emission %= (1.0 / self.emission_rate)
// Update existing particles
self.particles = self.particles
.filter(|p| p.age < p.lifetime)
.map(|p| {
p.age += delta_time
p.position += p.velocity * delta_time
p.velocity += self.gravity * delta_time
p.color.a = 1.0 - (p.age / p.lifetime) // Fade out
p
})
}
fn emit_particle(mut self) {
let particle = Particle {
position: self.position,
velocity: self.initial_velocity + random_variance(self.velocity_variance),
color: self.color + random_variance(self.color_variance),
size: self.size + random::range(-self.size_variance, self.size_variance),
lifetime: self.particle_lifetime
}
self.particles.append(particle)
}
}
// Audio system
println("\n=== Audio System ===")
struct AudioManager {
sound_effects: map,
music_tracks: map,
current_music: Option<AudioSource> = None,
master_volume: float = 1.0,
sfx_volume: float = 1.0,
music_volume: float = 0.7
}
impl AudioManager {
fn play_sfx(self, name, position = None) {
if let Some(sound) = self.sound_effects.get(name) {
let source = AudioSource::new(sound)
.set_volume(self.sfx_volume * self.master_volume)
if position {
source.set_position(position) // 3D sound
}
source.play()
}
}
fn play_music(mut self, name, loop = true) {
if self.current_music {
self.current_music.stop()
}
if let Some(track) = self.music_tracks.get(name) {
self.current_music = Some(
AudioSource::new(track)
.set_volume(self.music_volume * self.master_volume)
.set_loop(loop)
)
self.current_music.play()
}
}
fn crossfade_music(self, to_track, duration) {
spawn async {
let fade_steps = 60 // 60 FPS
let step_duration = duration / fade_steps
for i in 0..fade_steps {
let t = i / fade_steps
if self.current_music {
self.current_music.set_volume((1.0 - t) * self.music_volume)
}
sleep_ms(step_duration * 1000)
}
self.play_music(to_track)
}
}
}
// Game loop
println("\n=== Main Game Loop ===")
let mut game = Game {
state: GameState::Playing,
input_handler: InputHandler {},
renderer: Renderer::new(800, 600),
systems: [
MovementSystem {},
CollisionSystem { spatial_hash: SpatialHash::new() },
AnimationSystem {},
ParticleSystem {}
]
}
// Create player entity
let player = Entity { id: 1 }
.add_component("transform", Transform {
position: Vec3(400, 300, 0)
})
.add_component("velocity", Velocity {
linear: Vec3::zero(),
angular: Vec3::zero()
})
.add_component("sprite", Sprite {
texture: Texture::load("player.png")
})
.add_component("collider", Collider {
shape: Shape::Rectangle(32, 32)
})
.add_component("health", Health {
current: 100,
max: 100
})
game.entities.append(player)
// Main loop
let target_fps = 60
let target_frame_time = 1.0 / target_fps
loop {
let frame_start = game::time()
// Handle input
game.input_handler.update()
if game.input_handler.is_key_pressed("escape") {
match game.state {
GameState::Playing => game.state = GameState::Paused,
GameState::Paused => game.state = GameState::Playing,
_ => {}
}
}
// Update game
game.update(target_frame_time)
// Render
game.render()
// Frame rate limiting
let frame_time = game::time() - frame_start
if frame_time < target_frame_time {
sleep_ms((target_frame_time - frame_time) * 1000)
}
}
}