diff --git a/WEB_BUILD.md b/WEB_BUILD.md new file mode 100644 index 0000000..74a0158 --- /dev/null +++ b/WEB_BUILD.md @@ -0,0 +1,49 @@ +# Building Flappy Bird for Web + +## Prerequisites + +1. Install the WebAssembly target: +```bash +rustup target add wasm32-unknown-unknown +``` + +2. Install wasm-bindgen-cli (will be done automatically by build script): +```bash +cargo install wasm-bindgen-cli +``` + +## Building + +Run the build script: +```bash +./build-web.sh +``` + +This will: +- Build the WASM binary +- Generate JavaScript bindings +- Copy assets to the `web/` directory +- Copy the HTML file + +## Running Locally + +Serve the web directory with any HTTP server: + +**Python:** +```bash +python3 -m http.server --directory web 8080 +``` + +**Alternative (if you have basic-http-server):** +```bash +cargo install basic-http-server +basic-http-server web +``` + +Then open http://localhost:8080 in your browser! + +## Notes + +- The game uses WebGL2 for rendering +- All assets are included in the build +- Image sampling is set to nearest neighbor for crisp pixel art diff --git a/assets/bindings.ron b/assets/bindings.ron new file mode 100644 index 0000000..414c217 --- /dev/null +++ b/assets/bindings.ron @@ -0,0 +1,6 @@ +( + axes: {}, + actions: { + "flap": [[Key(Space)]], + }, +) diff --git a/assets/display_config.ron b/assets/display_config.ron new file mode 100644 index 0000000..3f50ce8 --- /dev/null +++ b/assets/display_config.ron @@ -0,0 +1,4 @@ +( + title: "test", + dimensions: Some((432, 768)), +) diff --git a/assets/screenshot1.png b/assets/screenshot1.png new file mode 100644 index 0000000..90c8881 Binary files /dev/null and b/assets/screenshot1.png differ diff --git a/assets/screenshot2.png b/assets/screenshot2.png new file mode 100644 index 0000000..b7a024e Binary files /dev/null and b/assets/screenshot2.png differ diff --git a/assets/sprites/flappy.png b/assets/sprites/flappy.png new file mode 100644 index 0000000..71ab80b Binary files /dev/null and b/assets/sprites/flappy.png differ diff --git a/assets/sprites/flappy.ron b/assets/sprites/flappy.ron new file mode 100644 index 0000000..d8f7053 --- /dev/null +++ b/assets/sprites/flappy.ron @@ -0,0 +1,169 @@ + +List(( + texture_width: 512, + texture_height: 512, + sprites: [ + ( // Daytime background + x: 0, + y: 0, + width: 144, + height: 256, + ), + ( // Nighttime background + x: 146, + y: 0, + width: 144, + height: 256, + ), + ( // Down Pipe + x: 56, + y: 323, + width: 26, + height: 160, + ), + ( // Up Pipe + x: 84, + y: 323, + width: 26, + height: 160, + ), + ( // Ground + x: 292, + y: 0, + width: 168, + height: 56, + ), + ( // Floppy + x: 3, + y: 490, + width: 17, + height: 13, + ), + ( // Tap Tap Dialogue + x: 292, + y: 91, + width: 56, + height: 48, + ), + ( // Play Button + x: 354, + y: 118, + width: 52, + height: 29, + ), + ( // Leaderboard button + x: 414, + y: 118, + width: 52, + height: 29, + ), + ( // Get Ready + x: 295, + y: 59, + width: 91, + height: 24, + ), + ( // Flappy Bird Text + x: 351, + y: 91, + width: 90, + height: 25, + ), + ( // Game Over + x: 395, + y: 58, + width: 100, + height: 22, + ), + ( // Number 0 + x: 314, + y: 198, + width: 20, + height: 28, + ), + ( // Number 1 + x: 135, + y: 455, + width: 20, + height: 28, + ), + ( // Number 2 + x: 292, + y: 138, + width: 20, + height: 28, + ), + ( // Number 3 + x: 314, + y: 138, + width: 20, + height: 28, + ), + ( // Number 4 + x: 336, + y: 138, + width: 20, + height: 28, + ), + ( // Number 5 + x: 358, + y: 138, + width: 20, + height: 28, + ), + ( // Number 6 + x: 292, + y: 168, + width: 20, + height: 28, + ), + ( // Number 7 + x: 314, + y: 168, + width: 20, + height: 28, + ), + ( // Number 8 + x: 336, + y: 168, + width: 20, + height: 28, + ), + ( // Number 9 + x: 358, + y: 168, + width: 20, + height: 28, + ), + ( // Menu Button (index 6) + x: 462, + y: 26, + width: 40, + height: 14, + ), + ( // OK Button (index 8) + x: 462, + y: 42, + width: 40, + height: 14, + ), + ( // Bird Animation 1 (index 67) + x: 3, + y: 491, + width: 17, + height: 12, + ), + ( // Bird Animation 2 (index 68) + x: 31, + y: 491, + width: 17, + height: 12, + ), + ( // Bird Animation 3 (index 69) + x: 59, + y: 491, + width: 17, + height: 12, + ) + ] +)) \ No newline at end of file diff --git a/assets/sprites/logo.png b/assets/sprites/logo.png new file mode 100644 index 0000000..159d8cb Binary files /dev/null and b/assets/sprites/logo.png differ diff --git a/assets/sprites/logo.ron b/assets/sprites/logo.ron new file mode 100644 index 0000000..c8bd048 --- /dev/null +++ b/assets/sprites/logo.ron @@ -0,0 +1,24 @@ +( + texture_width: 690, + texture_height: 230, + sprites: [ + ( + x: 0, + y: 0, + width: 230, + height: 230, + ), + ( + x: 230, + y: 0, + width: 230, + height: 230, + ), + ( + x: 460, + y: 0, + width: 230, + height: 230, + ) + ] +) \ No newline at end of file diff --git a/build-web.sh b/build-web.sh new file mode 100755 index 0000000..166dc7f --- /dev/null +++ b/build-web.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -e + +echo "Building for WebAssembly..." + +# Install wasm-bindgen-cli if not present +if ! command -v wasm-bindgen &> /dev/null; then + echo "Installing wasm-bindgen-cli..." + cargo install wasm-bindgen-cli +fi + +# Build the project +cargo build --release --target wasm32-unknown-unknown + +# Generate JS bindings +wasm-bindgen --out-dir ./web --target web ./target/wasm32-unknown-unknown/release/flappy-bird-rust.wasm + +# Copy assets and HTML +mkdir -p web/assets +cp -r assets/* web/assets/ +cp index.html web/ + +echo "Build complete! Files are in ./web" +echo "To serve locally, run: python3 -m http.server --directory web 8080" +echo "Then open http://localhost:8080" diff --git a/src/gameover_state.rs b/src/gameover_state.rs new file mode 100644 index 0000000..eff8856 --- /dev/null +++ b/src/gameover_state.rs @@ -0,0 +1,412 @@ +use bevy::prelude::*; +use crate::splash_state::*; +use crate::components::*; + +#[derive(Component)] +pub struct GameOverScreen; + +#[derive(Component)] +pub struct ScoreCard { + pub target_y: f32, + pub animation_speed: f32, +} + +#[derive(Component)] +pub struct TinyScoreDisplay; + +#[derive(Component)] +pub struct TinyHighScoreDisplay; + +#[derive(Resource, Default)] +pub struct DebugScoreCard { + pub enabled: bool, +} + +#[derive(Component)] +pub struct ScoreCardDebugSprite; + +pub fn setup_gameover( + mut commands: Commands, + sprite_handles: Res, + window_query: Query<&Window>, + score: Res, + mut high_score: ResMut, +) { + let Ok(window) = window_query.single() else { + return; + }; + let width = window.width(); + let height = window.height(); + + // Update high score if current score is higher + if score.value > high_score.value { + high_score.value = score.value; + } + + // Game Over sprite - positioned at 3/4 height + let target_y = height * 0.75; + + commands.spawn(( + Sprite { + image: sprite_handles.texture.clone(), + texture_atlas: Some(TextureAtlas { + layout: sprite_handles.layout.clone(), + index: SPRITE_GAME_OVER, + }), + ..default() + }, + Transform { + translation: Vec3::new(width * 0.5, target_y, 0.3), + scale: Vec3::splat(3.0), + ..default() + }, + GameOverScreen, + )); + + // Score card - starts below screen and animates up + let card_target_y = height * 0.4; + commands.spawn(( + Sprite { + image: sprite_handles.texture.clone(), + texture_atlas: Some(TextureAtlas { + layout: sprite_handles.layout.clone(), + index: SPRITE_SCORE_CARD, + }), + ..default() + }, + Transform { + translation: Vec3::new(width * 0.5, -200.0, 0.2), + scale: Vec3::splat(3.0), + ..default() + }, + ScoreCard { + target_y: card_target_y, + animation_speed: 400.0, + }, + GameOverScreen, + )); + + // Determine medal based on score + let medal_index = if score.value >= 40 { + SPRITE_MEDAL_PLATINUM + } else if score.value >= 30 { + SPRITE_MEDAL_GOLD + } else if score.value >= 20 { + SPRITE_MEDAL_SILVER + } else { + // Everyone gets at least bronze + SPRITE_MEDAL_BRONZE + }; + + // Spawn medal - attached to card animation + commands.spawn(( + Sprite { + image: sprite_handles.texture.clone(), + texture_atlas: Some(TextureAtlas { + layout: sprite_handles.layout.clone(), + index: medal_index, + }), + ..default() + }, + Transform { + translation: Vec3::new(width * 0.35 - 33.0, -200.0, 0.3), + scale: Vec3::splat(3.0), + ..default() + }, + ScoreCard { + target_y: card_target_y - 10.0, + animation_speed: 400.0, + }, + GameOverScreen, + )); +} + +pub fn gameover_input( + keyboard: Res>, + mouse: Res>, + touches: Res, + mut next_state: ResMut>, +) { + if keyboard.just_pressed(KeyCode::Space) + || mouse.just_pressed(MouseButton::Left) + || touches.any_just_pressed() { + next_state.set(GameState::Ready); + } +} + +pub fn animate_score_card( + time: Res