Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64ab2cbaae | ||
|
|
e7441dac5c | ||
|
|
ad0f6bad59 | ||
|
|
afd2647b17 |
@@ -0,0 +1,8 @@
|
|||||||
|
# for Linux
|
||||||
|
[target.x86_64-unknown-linux-gnu]
|
||||||
|
linker = "clang"
|
||||||
|
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
||||||
|
|
||||||
|
# for Windows
|
||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
linker = "rust-lld.exe"
|
||||||
Generated
+1121
-1236
File diff suppressed because it is too large
Load Diff
+12
-3
@@ -4,6 +4,15 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
avian2d = "0.5.0"
|
avian2d = "0.6.1"
|
||||||
bevy = "0.18.0"
|
bevy = { version = "0.18.0", features = ["dynamic_linking"] }
|
||||||
bevy_egui = "0.39.1"
|
|
||||||
|
|
||||||
|
# Enable a small amount of optimization in the dev profile.
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 1
|
||||||
|
|
||||||
|
# Enable a large amount of optimization in the dev profile for dependencies.
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -1 +0,0 @@
|
|||||||
/home/mitchell/Pictures/player.png
|
|
||||||
|
Before Width: | Height: | Size: 34 B After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 34 B After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
+35
-20
@@ -1,6 +1,9 @@
|
|||||||
use avian2d::{math::*, prelude::*};
|
use avian2d::{math::*, prelude::*};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
|
const PLAYER_SPEED: f32 = 300.0;
|
||||||
|
|
||||||
pub struct CharacterControllerPlugin;
|
pub struct CharacterControllerPlugin;
|
||||||
|
|
||||||
impl Plugin for CharacterControllerPlugin {
|
impl Plugin for CharacterControllerPlugin {
|
||||||
@@ -26,6 +29,10 @@ pub struct MaxSpeed(Scalar);
|
|||||||
#[derive(Component, Default, Copy, Clone)]
|
#[derive(Component, Default, Copy, Clone)]
|
||||||
pub struct MaxAcceleration(Scalar);
|
pub struct MaxAcceleration(Scalar);
|
||||||
|
|
||||||
|
/// Whether inputs are enabled for a CharacterController.
|
||||||
|
#[derive(Component, Default, Copy, Clone)]
|
||||||
|
pub struct InputEnabled(pub bool);
|
||||||
|
|
||||||
/// A bundle that contains the components needed for a basic
|
/// A bundle that contains the components needed for a basic
|
||||||
/// kinematic character controller.
|
/// kinematic character controller.
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
@@ -35,16 +42,18 @@ pub struct CharacterControllerBundle {
|
|||||||
collider: Collider,
|
collider: Collider,
|
||||||
speed: MaxSpeed,
|
speed: MaxSpeed,
|
||||||
acceleration: MaxAcceleration,
|
acceleration: MaxAcceleration,
|
||||||
|
enabled: InputEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterControllerBundle {
|
impl CharacterControllerBundle {
|
||||||
pub fn new(collider: Collider) -> Self {
|
pub fn new(collider: Collider) -> Self {
|
||||||
Self {
|
Self {
|
||||||
character_controller: CharacterController,
|
character_controller: CharacterController,
|
||||||
body: RigidBody::Kinematic,
|
body: RigidBody::Dynamic,
|
||||||
collider,
|
collider,
|
||||||
speed: MaxSpeed(300.),
|
speed: MaxSpeed(PLAYER_SPEED),
|
||||||
acceleration: MaxAcceleration(5000.),
|
acceleration: MaxAcceleration(5000.),
|
||||||
|
enabled: InputEnabled(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,31 +102,37 @@ fn gamepad_input(mut movement_writer: MessageWriter<MovementAction>, gamepads: Q
|
|||||||
fn movement(
|
fn movement(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut movement_reader: MessageReader<MovementAction>,
|
mut movement_reader: MessageReader<MovementAction>,
|
||||||
mut controllers: Query<(&MaxSpeed, &MaxAcceleration, &mut LinearVelocity)>,
|
mut controllers: Query<(&MaxSpeed, &MaxAcceleration, &mut LinearVelocity, &InputEnabled)>,
|
||||||
) {
|
) {
|
||||||
// Precision is adjusted so that the example works with
|
// Precision is adjusted so that the example works with
|
||||||
// both the `f32` and `f64` features. Otherwise you don't need this.
|
// both the `f32` and `f64` features. Otherwise you don't need this.
|
||||||
let delta_time = time.delta_secs_f64().adjust_precision();
|
let delta_time = time.delta_secs_f64().adjust_precision();
|
||||||
|
|
||||||
for (max_speed, max_acceleration, mut linear_velocity) in &mut controllers {
|
for (max_speed, max_acceleration, mut linear_velocity, enabled) in &mut controllers {
|
||||||
while movement_reader.len() > 1 {
|
if enabled.0 {
|
||||||
warn!("Extra movement message. Ignoring");
|
// let len = movement_reader.len();
|
||||||
movement_reader.read();
|
// while len > 1 {
|
||||||
}
|
// warn!("Extra movement message. Ignoring");
|
||||||
|
// println!("{}", len);
|
||||||
|
// let _ = movement_reader.read().take(len - 1).count();
|
||||||
|
// }
|
||||||
|
|
||||||
let target = movement_reader
|
let target = movement_reader
|
||||||
.read()
|
.read()
|
||||||
.next()
|
.last()
|
||||||
.map(|ma| ma.0)
|
.map(|ma| ma.0)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
* max_speed.0;
|
* max_speed.0;
|
||||||
|
|
||||||
let mut delta = target - **linear_velocity;
|
let mut delta = target - **linear_velocity;
|
||||||
let delta_len = delta.length();
|
let delta_len = delta.length();
|
||||||
let max_acceleration = max_acceleration.0 * delta_time;
|
let max_acceleration = max_acceleration.0 * delta_time;
|
||||||
if delta_len > max_acceleration {
|
if delta_len > max_acceleration {
|
||||||
delta = delta.normalize() * max_acceleration;
|
delta = delta.normalize() * max_acceleration;
|
||||||
|
}
|
||||||
|
**linear_velocity += delta;
|
||||||
|
} else {
|
||||||
|
**linear_velocity = Vec2::ZERO;
|
||||||
}
|
}
|
||||||
**linear_velocity += delta;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-22
@@ -1,44 +1,51 @@
|
|||||||
use crate::avian::{CharacterControllerBundle, CharacterControllerPlugin};
|
use crate::avian::{CharacterControllerBundle, CharacterControllerPlugin};
|
||||||
|
use crate::world::{MainCamera, Player, WorldPlugin};
|
||||||
|
use avian2d::parry::simba::simd::SimdComplexField;
|
||||||
use avian2d::{
|
use avian2d::{
|
||||||
|
prelude::*,
|
||||||
PhysicsPlugins,
|
PhysicsPlugins,
|
||||||
math::Vector,
|
|
||||||
prelude::{Collider, Gravity, RigidBody},
|
|
||||||
};
|
};
|
||||||
|
use bevy::tasks::futures_lite::StreamExt;
|
||||||
use bevy::{camera::ScalingMode, color::palettes::css::GREEN, prelude::*};
|
use bevy::{camera::ScalingMode, color::palettes::css::GREEN, prelude::*};
|
||||||
use bevy_egui::{EguiContexts, EguiPlugin, EguiPrimaryContextPass, egui};
|
|
||||||
|
|
||||||
pub mod avian;
|
pub mod avian;
|
||||||
|
pub mod world;
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
DefaultPlugins,
|
// DefaultPlugins,
|
||||||
EguiPlugin::default(),
|
DefaultPlugins.set(ImagePlugin::default_nearest()),
|
||||||
// Add physics plugins and specify a units-per-meter scaling factor, 1 meter = 20 pixels.
|
// Add physics plugins and specify a units-per-meter scaling factor, 1 meter = 20 pixels.
|
||||||
// The unit allows the engine to tune its parameters for the scale of the world, improving stability.
|
// The unit allows the engine to tune its parameters for the scale of the world, improving stability.
|
||||||
PhysicsPlugins::default().with_length_unit(20.0),
|
PhysicsPlugins::default().with_length_unit(20.0),
|
||||||
CharacterControllerPlugin,
|
CharacterControllerPlugin,
|
||||||
|
WorldPlugin,
|
||||||
))
|
))
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.add_systems(Update, debug_border)
|
.add_systems(Update, debug_border)
|
||||||
.add_systems(EguiPrimaryContextPass, connection_ui)
|
|
||||||
.insert_resource(Gravity(Vector::ZERO))
|
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
let mut projection = OrthographicProjection::default_2d();
|
let mut projection = OrthographicProjection::default_2d();
|
||||||
projection.scaling_mode = ScalingMode::AutoMin {
|
projection.scaling_mode = ScalingMode::AutoMin {
|
||||||
min_width: 1920.,
|
min_width: 1920.,
|
||||||
min_height: 1080.,
|
min_height: 1080.,
|
||||||
};
|
};
|
||||||
commands.spawn((Camera2d, Projection::Orthographic(projection)));
|
commands.spawn((Camera2d, MainCamera, Projection::Orthographic(projection)));
|
||||||
|
|
||||||
// player
|
// player
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
Sprite::from_image(asset_server.load("player.png")),
|
Sprite::from_image(asset_server.load("south.png")),
|
||||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
|
||||||
CharacterControllerBundle::new(Collider::capsule(15., 27.5)),
|
Player,
|
||||||
|
Transform::from_xyz(0.0, 0.0, 2.0).with_scale(Vec3::splat(4.0)),
|
||||||
|
CharacterControllerBundle::new(Collider::rectangle(14.0, 30.0)),
|
||||||
|
LockedAxes::ROTATION_LOCKED,
|
||||||
));
|
));
|
||||||
|
|
||||||
// A cube to move around
|
// A cube to move around
|
||||||
@@ -48,21 +55,16 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
custom_size: Some(Vec2::new(30.0, 30.0)),
|
custom_size: Some(Vec2::new(30.0, 30.0)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
Transform::from_xyz(50.0, -100.0, 0.0),
|
Transform::from_xyz(50.0, -100.0, 1.0),
|
||||||
RigidBody::Dynamic,
|
RigidBody::Dynamic,
|
||||||
Collider::rectangle(30.0, 30.0),
|
Collider::rectangle(30.0, 30.0),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_border(mut gizmos: Gizmos) {
|
fn debug_border(mut gizmos: Gizmos) {
|
||||||
gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::new(1920., 1080.), GREEN);
|
gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::new(1920., 1080.), GREEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connection_ui(mut contexts: EguiContexts) -> Result {
|
|
||||||
let mut text = String::from("hello");
|
|
||||||
egui::Window::new("Connection").show(contexts.ctx_mut()?, |ui| {
|
|
||||||
ui.label("connection:");
|
|
||||||
ui.text_edit_singleline(&mut text)
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
+368
@@ -0,0 +1,368 @@
|
|||||||
|
use avian2d::math::Vector;
|
||||||
|
use avian2d::prelude::{Collider, CollidingEntities, CollisionEventsEnabled, CollisionStart, Gravity, RigidBody, Sensor};
|
||||||
|
use bevy::camera::Viewport;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::window::PrimaryWindow;
|
||||||
|
use crate::avian::InputEnabled;
|
||||||
|
|
||||||
|
|
||||||
|
const ROOM_WIDTH: f32 = 1920.0;
|
||||||
|
const ROOM_HEIGHT: f32 = 1080.0;
|
||||||
|
const WALL_THICKNESS: f32 = 20.0;
|
||||||
|
const DOOR_WIDTH: f32 = 120.0;
|
||||||
|
const DOOR_DEPTH: f32 = 40.0;
|
||||||
|
const SPAWN_DISTANCE: f32 = 80.0;
|
||||||
|
|
||||||
|
const FADE_DURATION: f32 = 0.35;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const TARGET_ASPECT: f32 = ROOM_WIDTH / ROOM_HEIGHT;
|
||||||
|
const HALF_W: f32 = ROOM_WIDTH / 2.0;
|
||||||
|
const HALF_H: f32 = ROOM_HEIGHT / 2.0;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct WorldPlugin;
|
||||||
|
|
||||||
|
impl Plugin for WorldPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
door_detection.run_if(in_state(GameState::Playing)),
|
||||||
|
transition_system.run_if(in_state(GameState::Transitioning)),
|
||||||
|
update_viewport,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.init_state::<GameState>()
|
||||||
|
.insert_resource(CurrentRoom(IVec2::ZERO))
|
||||||
|
.insert_resource(Gravity(Vector::ZERO))
|
||||||
|
.insert_resource(ClearColor(Color::BLACK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
spawn_room(&mut commands, IVec2::new(0, 0), vec![Direction::North, Direction::East], &asset_server);
|
||||||
|
spawn_room(&mut commands, IVec2::new(1, 0), vec![Direction::West], &asset_server);
|
||||||
|
spawn_room(&mut commands, IVec2::new(0, 1), vec![Direction::South], &asset_server);
|
||||||
|
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.0)),
|
||||||
|
FadeOverlay,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(States, Default, Clone, Eq, PartialEq, Hash, Debug)]
|
||||||
|
enum GameState {
|
||||||
|
#[default]
|
||||||
|
Playing,
|
||||||
|
Transitioning,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct CurrentRoom(IVec2);
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct RoomTransition {
|
||||||
|
target_room: IVec2,
|
||||||
|
target_player_position: Vec2,
|
||||||
|
timer: Timer,
|
||||||
|
phase: TransitionPhase,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
enum TransitionPhase {
|
||||||
|
FadeOut,
|
||||||
|
FadeIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Player;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct MainCamera;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct FadeOverlay;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Door {
|
||||||
|
room: IVec2,
|
||||||
|
travel_direction: Direction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
enum Direction {
|
||||||
|
North,
|
||||||
|
South,
|
||||||
|
East,
|
||||||
|
West,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fn spawn_room(commands: &mut Commands, room: IVec2, door_directions: Vec<Direction>, asset_server: &Res<AssetServer>) {
|
||||||
|
let center = room_center(room);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Sprite {
|
||||||
|
image: asset_server.load("img.png"),
|
||||||
|
custom_size: Some(Vec2::new(ROOM_WIDTH, ROOM_HEIGHT)),
|
||||||
|
image_mode: SpriteImageMode::Tiled {
|
||||||
|
tile_x: true,
|
||||||
|
tile_y: true,
|
||||||
|
stretch_value: 1.0
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sprite::from_color(color, Vec2::new(ROOM_WIDTH, ROOM_HEIGHT)),
|
||||||
|
Transform::from_translation(center),
|
||||||
|
));
|
||||||
|
|
||||||
|
spawn_doors(commands, room, door_directions);
|
||||||
|
spawn_room_walls(commands, center);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_room_walls(commands: &mut Commands, center: Vec3) {
|
||||||
|
|
||||||
|
// LEFT
|
||||||
|
commands.spawn((
|
||||||
|
RigidBody::Static,
|
||||||
|
Sprite::from_color(Color::srgb(0.5, 0.5, 0.5), Vec2::new(WALL_THICKNESS, ROOM_HEIGHT)),
|
||||||
|
Collider::rectangle(WALL_THICKNESS, ROOM_HEIGHT),
|
||||||
|
Transform::from_xyz(
|
||||||
|
center.x - HALF_W + WALL_THICKNESS / 2.0,
|
||||||
|
center.y,
|
||||||
|
10.0,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
// RIGHT
|
||||||
|
commands.spawn((
|
||||||
|
RigidBody::Static,
|
||||||
|
Sprite::from_color(Color::srgb(0.5, 0.5, 0.5), Vec2::new(WALL_THICKNESS, ROOM_HEIGHT)),
|
||||||
|
Collider::rectangle(WALL_THICKNESS, ROOM_HEIGHT),
|
||||||
|
Transform::from_xyz(
|
||||||
|
center.x + HALF_W - WALL_THICKNESS / 2.0,
|
||||||
|
center.y,
|
||||||
|
10.0,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
// TOP
|
||||||
|
commands.spawn((
|
||||||
|
RigidBody::Static,
|
||||||
|
Sprite::from_color(Color::srgb(0.5, 0.5, 0.5), Vec2::new(ROOM_WIDTH, WALL_THICKNESS)),
|
||||||
|
Collider::rectangle(ROOM_WIDTH, WALL_THICKNESS),
|
||||||
|
Transform::from_xyz(
|
||||||
|
center.x,
|
||||||
|
center.y + HALF_H - WALL_THICKNESS / 2.0,
|
||||||
|
10.0,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
// BOTTOM
|
||||||
|
commands.spawn((
|
||||||
|
RigidBody::Static,
|
||||||
|
Sprite::from_color(Color::srgb(0.5, 0.5, 0.5), Vec2::new(ROOM_WIDTH, WALL_THICKNESS)),
|
||||||
|
Collider::rectangle(ROOM_WIDTH, WALL_THICKNESS),
|
||||||
|
Transform::from_xyz(
|
||||||
|
center.x,
|
||||||
|
center.y - HALF_H + WALL_THICKNESS / 2.0,
|
||||||
|
10.0,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_doors(commands: &mut Commands, room: IVec2, door_directions: Vec<Direction>) {
|
||||||
|
let room_center = room_center(room);
|
||||||
|
const HALF_DEPTH: f32 = DOOR_DEPTH / 2.0;
|
||||||
|
|
||||||
|
for direction in door_directions {
|
||||||
|
let transform: Transform;
|
||||||
|
let size: Vec2;
|
||||||
|
match direction {
|
||||||
|
Direction::North => {
|
||||||
|
transform = Transform::from_xyz(room_center.x, room_center.y + HALF_H - HALF_DEPTH, 1.0);
|
||||||
|
size = Vec2::new(DOOR_WIDTH, DOOR_DEPTH);
|
||||||
|
},
|
||||||
|
Direction::East => {
|
||||||
|
transform = Transform::from_xyz(room_center.x + HALF_W - HALF_DEPTH, room_center.y, 1.0);
|
||||||
|
size = Vec2::new(DOOR_DEPTH, DOOR_WIDTH);
|
||||||
|
},
|
||||||
|
Direction::South => {
|
||||||
|
transform = Transform::from_xyz(room_center.x, room_center.y - HALF_H + HALF_DEPTH, 1.0);
|
||||||
|
size = Vec2::new(DOOR_WIDTH, DOOR_DEPTH);
|
||||||
|
},
|
||||||
|
Direction::West => {
|
||||||
|
transform = Transform::from_xyz(room_center.x - HALF_W + HALF_DEPTH, room_center.y, 1.0);
|
||||||
|
size = Vec2::new(DOOR_DEPTH, DOOR_WIDTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Sprite::from_color(Color::srgb(0.1, 0.1, 0.1), size),
|
||||||
|
Collider::rectangle(size.x, size.y),
|
||||||
|
Sensor,
|
||||||
|
CollisionEventsEnabled,
|
||||||
|
CollidingEntities::default(),
|
||||||
|
Door {
|
||||||
|
room: IVec2::from(room),
|
||||||
|
travel_direction: direction,
|
||||||
|
},
|
||||||
|
transform,
|
||||||
|
GlobalTransform::default(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn door_detection(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut next_state: ResMut<NextState<GameState>>,
|
||||||
|
player: Single<(Entity, &Transform), With<Player>>,
|
||||||
|
doors: Query<&Door>,
|
||||||
|
mut started: MessageReader<CollisionStart>,
|
||||||
|
) {
|
||||||
|
|
||||||
|
for event in started.read() {
|
||||||
|
println!("CollisionStart: {event:?}");
|
||||||
|
let door = doors.get(event.collider2);
|
||||||
|
if event.collider1 == player.0 && door.is_ok() {
|
||||||
|
commands.insert_resource(RoomTransition {
|
||||||
|
target_room: door_target_room(door.unwrap()),
|
||||||
|
target_player_position: door_target_position(door.unwrap()),
|
||||||
|
timer: Timer::from_seconds(
|
||||||
|
FADE_DURATION,
|
||||||
|
TimerMode::Once,
|
||||||
|
),
|
||||||
|
phase: TransitionPhase::FadeOut,
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.entity(player.0).insert(InputEnabled(false));
|
||||||
|
next_state.set(GameState::Transitioning);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn door_target_room(door: &Door) -> IVec2 {
|
||||||
|
match door.travel_direction {
|
||||||
|
Direction::North => IVec2::new(door.room.x, door.room.y + 1),
|
||||||
|
Direction::East => IVec2::new(door.room.x + 1, door.room.y),
|
||||||
|
Direction::South => IVec2::new(door.room.x, door.room.y - 1),
|
||||||
|
Direction::West => IVec2::new(door.room.x - 1, door.room.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn door_target_position(door: &Door) -> Vec2 {
|
||||||
|
match door.travel_direction {
|
||||||
|
Direction::North => Vec2::new(0.0, -HALF_H + WALL_THICKNESS + DOOR_DEPTH + SPAWN_DISTANCE),
|
||||||
|
Direction::East => Vec2::new(-HALF_W + WALL_THICKNESS + DOOR_DEPTH + SPAWN_DISTANCE, 0.0),
|
||||||
|
Direction::South => Vec2::new(0.0, HALF_H - WALL_THICKNESS - DOOR_DEPTH - SPAWN_DISTANCE),
|
||||||
|
Direction::West => Vec2::new(HALF_W - WALL_THICKNESS - DOOR_DEPTH - SPAWN_DISTANCE, 0.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transition_system(
|
||||||
|
mut commands: Commands,
|
||||||
|
time: Res<Time>,
|
||||||
|
mut transition: ResMut<RoomTransition>,
|
||||||
|
mut next_state: ResMut<NextState<GameState>>,
|
||||||
|
mut current_room: ResMut<CurrentRoom>,
|
||||||
|
mut player: Single<(Entity, &mut Transform), With<Player>>,
|
||||||
|
mut camera: Query<&mut Transform, (With<MainCamera>, Without<Player>)>,
|
||||||
|
mut overlay: Query<&mut BackgroundColor, With<FadeOverlay>>,
|
||||||
|
) {
|
||||||
|
transition.timer.tick(time.delta());
|
||||||
|
|
||||||
|
let mut overlay_color = overlay.single_mut().unwrap();
|
||||||
|
|
||||||
|
match transition.phase {
|
||||||
|
TransitionPhase::FadeOut => {
|
||||||
|
let alpha = transition.timer.fraction();
|
||||||
|
|
||||||
|
overlay_color.0 = Color::srgba(0.0, 0.0, 0.0, alpha);
|
||||||
|
|
||||||
|
if transition.timer.is_finished() {
|
||||||
|
current_room.0 = transition.target_room;
|
||||||
|
|
||||||
|
let room_center =
|
||||||
|
room_center(transition.target_room);
|
||||||
|
|
||||||
|
player.1.translation =
|
||||||
|
room_center
|
||||||
|
+ transition.target_player_position.extend(10.0);
|
||||||
|
|
||||||
|
camera.single_mut().unwrap().translation =
|
||||||
|
room_center + Vec3::new(0.0, 0.0, 999.0);
|
||||||
|
|
||||||
|
transition.phase = TransitionPhase::FadeIn;
|
||||||
|
transition.timer.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TransitionPhase::FadeIn => {
|
||||||
|
let alpha = 1.0 - transition.timer.fraction();
|
||||||
|
|
||||||
|
overlay_color.0 = Color::srgba(0.0, 0.0, 0.0, alpha);
|
||||||
|
|
||||||
|
if transition.timer.is_finished() {
|
||||||
|
commands.entity(player.0).insert(InputEnabled(true));
|
||||||
|
next_state.set(GameState::Playing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_viewport(
|
||||||
|
window: Single<&Window, With<PrimaryWindow>>,
|
||||||
|
mut camera: Single<&mut Camera>,
|
||||||
|
) {
|
||||||
|
let window_width = window.physical_width();
|
||||||
|
let window_height = window.physical_height();
|
||||||
|
|
||||||
|
let window_aspect = window_width as f32 / window_height as f32;
|
||||||
|
|
||||||
|
let (viewport_width, viewport_height) = if window_aspect > TARGET_ASPECT {
|
||||||
|
// Ultrawide: black bars left/right
|
||||||
|
let h = window_height;
|
||||||
|
let w = (h as f32 * TARGET_ASPECT) as u32;
|
||||||
|
(w, h)
|
||||||
|
} else {
|
||||||
|
// Too tall: black bars top/bottom
|
||||||
|
let w = window_width;
|
||||||
|
let h = (w as f32 / TARGET_ASPECT) as u32;
|
||||||
|
(w, h)
|
||||||
|
};
|
||||||
|
|
||||||
|
let x = (window_width - viewport_width) / 2;
|
||||||
|
let y = (window_height - viewport_height) / 2;
|
||||||
|
|
||||||
|
camera.viewport = Some(Viewport {
|
||||||
|
physical_position: UVec2::new(x, y),
|
||||||
|
physical_size: UVec2::new(viewport_width, viewport_height),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn room_center(room: IVec2) -> Vec3 {
|
||||||
|
Vec3::new(
|
||||||
|
room.x as f32 * ROOM_WIDTH,
|
||||||
|
room.y as f32 * ROOM_HEIGHT,
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user