other files
This commit is contained in:
49
WEB_BUILD.md
Normal file
49
WEB_BUILD.md
Normal file
@@ -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
|
||||||
6
assets/bindings.ron
Normal file
6
assets/bindings.ron
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
(
|
||||||
|
axes: {},
|
||||||
|
actions: {
|
||||||
|
"flap": [[Key(Space)]],
|
||||||
|
},
|
||||||
|
)
|
||||||
4
assets/display_config.ron
Normal file
4
assets/display_config.ron
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
(
|
||||||
|
title: "test",
|
||||||
|
dimensions: Some((432, 768)),
|
||||||
|
)
|
||||||
BIN
assets/screenshot1.png
Normal file
BIN
assets/screenshot1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/screenshot2.png
Normal file
BIN
assets/screenshot2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/sprites/flappy.png
Normal file
BIN
assets/sprites/flappy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
169
assets/sprites/flappy.ron
Normal file
169
assets/sprites/flappy.ron
Normal file
@@ -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,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
))
|
||||||
BIN
assets/sprites/logo.png
Normal file
BIN
assets/sprites/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
24
assets/sprites/logo.ron
Normal file
24
assets/sprites/logo.ron
Normal file
@@ -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,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
25
build-web.sh
Executable file
25
build-web.sh
Executable file
@@ -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"
|
||||||
412
src/gameover_state.rs
Normal file
412
src/gameover_state.rs
Normal file
@@ -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<SpriteHandles>,
|
||||||
|
window_query: Query<&Window>,
|
||||||
|
score: Res<Score>,
|
||||||
|
mut high_score: ResMut<HighScore>,
|
||||||
|
) {
|
||||||
|
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<ButtonInput<KeyCode>>,
|
||||||
|
mouse: Res<ButtonInput<MouseButton>>,
|
||||||
|
touches: Res<Touches>,
|
||||||
|
mut next_state: ResMut<NextState<GameState>>,
|
||||||
|
) {
|
||||||
|
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<Time>,
|
||||||
|
mut query: Query<(&mut Transform, &ScoreCard)>,
|
||||||
|
) {
|
||||||
|
for (mut transform, card) in query.iter_mut() {
|
||||||
|
if transform.translation.y < card.target_y {
|
||||||
|
transform.translation.y += card.animation_speed * time.delta_secs();
|
||||||
|
// Clamp to target
|
||||||
|
if transform.translation.y > card.target_y {
|
||||||
|
transform.translation.y = card.target_y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_tiny_score(
|
||||||
|
mut commands: Commands,
|
||||||
|
score: Res<Score>,
|
||||||
|
sprite_handles: Res<SpriteHandles>,
|
||||||
|
window_query: Query<&Window>,
|
||||||
|
existing_digits: Query<Entity, With<TinyScoreDisplay>>,
|
||||||
|
card_query: Query<&Transform, With<ScoreCard>>,
|
||||||
|
) {
|
||||||
|
// Wait for card to finish animating before showing score
|
||||||
|
// Use iter().next() instead of single() since we have multiple ScoreCard entities
|
||||||
|
let Some(card_transform) = card_query.iter().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(window) = window_query.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only render once card is near its target position
|
||||||
|
if card_transform.translation.y < window.height() * 0.35 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update if score changed or digits don't exist
|
||||||
|
if !score.is_changed() && !existing_digits.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Despawn existing score digits
|
||||||
|
for entity in existing_digits.iter() {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = window.width();
|
||||||
|
let height = window.height();
|
||||||
|
let card_y = height * 0.4;
|
||||||
|
|
||||||
|
// Convert score to digits
|
||||||
|
let score_string = score.value.to_string();
|
||||||
|
let num_digits = score_string.len() as f32;
|
||||||
|
let digit_spacing = 12.0;
|
||||||
|
let total_width = num_digits * digit_spacing;
|
||||||
|
let start_x = width * 0.65 - total_width / 2.0 + 30.0 + 20.0;
|
||||||
|
|
||||||
|
// Spawn tiny digit sprites on the score card
|
||||||
|
for (i, digit_char) in score_string.chars().enumerate() {
|
||||||
|
let digit = digit_char.to_digit(10).unwrap() as usize;
|
||||||
|
let sprite_index = SPRITE_TINY_NUMBER_0 + digit;
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Sprite {
|
||||||
|
image: sprite_handles.texture.clone(),
|
||||||
|
texture_atlas: Some(TextureAtlas {
|
||||||
|
layout: sprite_handles.layout.clone(),
|
||||||
|
index: sprite_index,
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Transform {
|
||||||
|
translation: Vec3::new(start_x + (i as f32 * digit_spacing), card_y + 20.0, 0.35),
|
||||||
|
scale: Vec3::splat(3.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
TinyScoreDisplay,
|
||||||
|
GameOverScreen,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_tiny_high_score(
|
||||||
|
mut commands: Commands,
|
||||||
|
high_score: Res<HighScore>,
|
||||||
|
sprite_handles: Res<SpriteHandles>,
|
||||||
|
window_query: Query<&Window>,
|
||||||
|
existing_digits: Query<Entity, With<TinyHighScoreDisplay>>,
|
||||||
|
card_query: Query<&Transform, With<ScoreCard>>,
|
||||||
|
) {
|
||||||
|
// Wait for card to finish animating before showing high score
|
||||||
|
// Use iter().next() instead of single() since we have multiple ScoreCard entities
|
||||||
|
let Some(card_transform) = card_query.iter().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(window) = window_query.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only render once card is near its target position
|
||||||
|
if card_transform.translation.y < window.height() * 0.35 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update if high score changed or digits don't exist
|
||||||
|
if !high_score.is_changed() && !existing_digits.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Despawn existing high score digits
|
||||||
|
for entity in existing_digits.iter() {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = window.width();
|
||||||
|
let height = window.height();
|
||||||
|
let card_y = height * 0.4;
|
||||||
|
|
||||||
|
// Convert high score to digits
|
||||||
|
let high_score_string = high_score.value.to_string();
|
||||||
|
let num_digits = high_score_string.len() as f32;
|
||||||
|
let digit_spacing = 12.0;
|
||||||
|
let total_width = num_digits * digit_spacing;
|
||||||
|
let start_x = width * 0.65 - total_width / 2.0 + 30.0 + 20.0;
|
||||||
|
|
||||||
|
// Spawn tiny digit sprites on the score card below the current score
|
||||||
|
for (i, digit_char) in high_score_string.chars().enumerate() {
|
||||||
|
let digit = digit_char.to_digit(10).unwrap() as usize;
|
||||||
|
let sprite_index = SPRITE_TINY_NUMBER_0 + digit;
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Sprite {
|
||||||
|
image: sprite_handles.texture.clone(),
|
||||||
|
texture_atlas: Some(TextureAtlas {
|
||||||
|
layout: sprite_handles.layout.clone(),
|
||||||
|
index: sprite_index,
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Transform {
|
||||||
|
translation: Vec3::new(start_x + (i as f32 * digit_spacing), card_y - 50.0, 0.35),
|
||||||
|
scale: Vec3::splat(3.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
TinyHighScoreDisplay,
|
||||||
|
GameOverScreen,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_scorecard_debug(
|
||||||
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut debug: ResMut<DebugScoreCard>,
|
||||||
|
) {
|
||||||
|
if keyboard.just_pressed(KeyCode::KeyD) {
|
||||||
|
debug.enabled = !debug.enabled;
|
||||||
|
println!("Score card debug: {}", if debug.enabled { "ON" } else { "OFF" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_scorecard_debug(
|
||||||
|
mut commands: Commands,
|
||||||
|
debug: Res<DebugScoreCard>,
|
||||||
|
card_query: Query<&Transform, With<ScoreCard>>,
|
||||||
|
existing_debug: Query<Entity, With<ScoreCardDebugSprite>>,
|
||||||
|
window_query: Query<&Window>,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||||
|
) {
|
||||||
|
// Clean up existing debug sprites
|
||||||
|
for entity in existing_debug.iter() {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !debug.enabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(window) = window_query.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(card_transform) = card_query.iter().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = window.width();
|
||||||
|
let height = window.height();
|
||||||
|
let card_y = card_transform.translation.y;
|
||||||
|
|
||||||
|
// Draw horizontal reference lines at different Y positions
|
||||||
|
let reference_ys = vec![
|
||||||
|
(card_y + 20.0, "Score line (current)"),
|
||||||
|
(card_y, "Card center"),
|
||||||
|
(card_y - 20.0, "Medal line (suggested)"),
|
||||||
|
(card_y - 50.0, "High score line (current)"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (y, label) in reference_ys {
|
||||||
|
// Horizontal line
|
||||||
|
let mesh = Mesh::from(Rectangle::new(width, 2.0));
|
||||||
|
commands.spawn((
|
||||||
|
Mesh2d::from(meshes.add(mesh)),
|
||||||
|
MeshMaterial2d(materials.add(ColorMaterial::from(Color::srgba(1.0, 0.0, 0.0, 0.5)))),
|
||||||
|
Transform {
|
||||||
|
translation: Vec3::new(width / 2.0, y, 0.9),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ScoreCardDebugSprite,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Add tick marks every 10 pixels along the horizontal line
|
||||||
|
for tick_x in (0..(width as i32)).step_by(10) {
|
||||||
|
let tick_mesh = Mesh::from(Rectangle::new(1.0, 8.0));
|
||||||
|
commands.spawn((
|
||||||
|
Mesh2d::from(meshes.add(tick_mesh)),
|
||||||
|
MeshMaterial2d(materials.add(ColorMaterial::from(Color::srgba(1.0, 0.0, 0.0, 0.7)))),
|
||||||
|
Transform {
|
||||||
|
translation: Vec3::new(tick_x as f32, y, 0.91),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ScoreCardDebugSprite,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}: y = {}", label, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical reference lines for X positions
|
||||||
|
let reference_xs = vec![
|
||||||
|
(width * 0.35, "Medal X (current)"),
|
||||||
|
(width * 0.5, "Center"),
|
||||||
|
(width * 0.65, "Score area center"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (x, label) in reference_xs {
|
||||||
|
let mesh = Mesh::from(Rectangle::new(2.0, height));
|
||||||
|
commands.spawn((
|
||||||
|
Mesh2d::from(meshes.add(mesh)),
|
||||||
|
MeshMaterial2d(materials.add(ColorMaterial::from(Color::srgba(0.0, 1.0, 0.0, 0.5)))),
|
||||||
|
Transform {
|
||||||
|
translation: Vec3::new(x, height / 2.0, 0.9),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ScoreCardDebugSprite,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Add tick marks every 10 pixels along the vertical line
|
||||||
|
for tick_y in (0..(height as i32)).step_by(10) {
|
||||||
|
let tick_mesh = Mesh::from(Rectangle::new(8.0, 1.0));
|
||||||
|
commands.spawn((
|
||||||
|
Mesh2d::from(meshes.add(tick_mesh)),
|
||||||
|
MeshMaterial2d(materials.add(ColorMaterial::from(Color::srgba(0.0, 1.0, 0.0, 0.7)))),
|
||||||
|
Transform {
|
||||||
|
translation: Vec3::new(x, tick_y as f32, 0.91),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ScoreCardDebugSprite,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}: x = {}", label, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanup_gameover(
|
||||||
|
mut commands: Commands,
|
||||||
|
query: Query<Entity, With<GameOverScreen>>,
|
||||||
|
) {
|
||||||
|
for entity in query.iter() {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user