1
use bevy::{prelude::*, window::WindowResolution};
2
use bevy_rapier2d::prelude::*;
3
use std::f32::consts::PI;
4
5
mod physics;
6
mod player;
7
mod screen_edge;
8
mod sound;
9
mod ui;
10
11
use screen_edge::{EdgeBehaviour, ScreenEdge};
12
13
const WINDOW_WIDTH: f32 = 1920.;
14
const WINDOW_HEIGHT: f32 = 1200.;
15
const WINDOW_WRAP_OVERSHOOT: f32 = player::PLAYER_LENGTH.max(player::PLAYER_WIDTH) / 2.;
16
17
const GAMEPAD_STICK_DEADZONE: f32 = 0.1; // consider using the built-in support
18
const GAMEPAD_TRIGGER_DEADZONE: f32 = 0.1;
19
20
const ENEMY_STARTING_POSITION: Vec3 = Vec3::new(600.0, 350.0, 1.0);
21
const ENEMY_STARTING_ANGLE_R: f32 = PI + 0.6;
22
const ENEMY_WIDTH: f32 = 60.;
23
const ENEMY_ACCELERATION: f32 = 400.;
24
const ENEMY_STARTING_SPEED: f32 = 200.;
25
const ENEMY_STARTING_FIRE_RATE_PS: f32 = 1.;
26
const ENEMY_DAMPING: f32 = 1.5;
27
28
const BACKGROUND_COLOR: Color = Color::srgb(0.02, 0.018, 0.025);
29
const ENEMY_COLOR: Color = Color::srgb(1.0, 0.5, 0.5);
30
31
fn main() {
32
let sound_enabled = std::env::args_os().skip(1).next().is_some();
33
34
let window_plugin = WindowPlugin {
35
primary_window: Some(Window {
36
title: "tähetriiv".to_string(),
37
resolution: WindowResolution::new(WINDOW_WIDTH, WINDOW_HEIGHT),
38
..default()
39
}),
40
..default()
41
};
42
43
let mut app = App::new();
44
app.add_plugins(DefaultPlugins.set(window_plugin));
45
app.add_plugins(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(100.0));
46
app.add_plugins(RapierDebugRenderPlugin::default());
47
app.insert_resource(ClearColor(BACKGROUND_COLOR));
48
app.init_resource::<player::PlayerBulletAssets>();
49
app.add_event::<sound::SoundEvent>();
50
app.add_systems(Startup, (setup, player::setup, ui::setup, physics::setup));
51
app.add_systems(
52
FixedUpdate,
53
(
54
screen_edge::check_edges,
55
physics::check_bullet_collisions,
56
sound::play_sound,
57
)
58
.chain(),
59
);
60
app.add_systems(
61
Update,
62
(
63
player::handle_player_movement,
64
player::handle_player_fire,
65
ui::update_player_health_ui,
66
),
67
);
68
if sound_enabled {
69
app.add_systems(Startup, sound::setup);
70
}
71
72
app.run();
73
}
74
75
#[derive(Component)]
76
struct Health {
77
current: f32,
78
max: f32,
79
}
80
81
impl Health {
82
fn new(value: f32) -> Self {
83
Self {
84
current: value,
85
max: value,
86
}
87
}
88
}
89
90
#[derive(Component)]
91
struct FireRate(f32);
92
93
#[derive(Component)]
94
struct FireCooldown(Timer);
95
96
#[derive(Component)]
97
struct Enemy;
98
99
fn setup(
100
mut commands: Commands,
101
mut meshes: ResMut<Assets<Mesh>>,
102
mut materials: ResMut<Assets<ColorMaterial>>,
103
) {
104
commands.spawn(Camera2d);
105
commands
106
.spawn(Enemy)
107
.insert(Mesh2d(meshes.add(Triangle2d::default())))
108
.insert(MeshMaterial2d(materials.add(ENEMY_COLOR)))
109
.insert(
110
Transform::from_translation(ENEMY_STARTING_POSITION)
111
.with_scale(Vec2::splat(ENEMY_WIDTH).extend(1.))
112
.with_rotation(Quat::from_rotation_z(ENEMY_STARTING_ANGLE_R - PI / 2.)),
113
)
114
.insert(Velocity {
115
linvel: Vec2::from_angle(ENEMY_STARTING_ANGLE_R) * ENEMY_STARTING_SPEED,
116
angvel: 0.0,
117
})
118
.insert(RigidBody::Dynamic)
119
.insert(Sleeping::disabled())
120
.insert(Ccd::enabled())
121
.insert(Collider::triangle(
122
Vec2::Y * 0.5,
123
Vec2::new(-0.5, -0.5),
124
Vec2::new(0.5, -0.5),
125
))
126
.insert(CollisionGroups::new(
127
Group::GROUP_1,
128
Group::GROUP_10 | Group::GROUP_11,
129
))
130
.insert(ActiveEvents::COLLISION_EVENTS)
131
.insert(LockedAxes::ROTATION_LOCKED)
132
.insert(Damping {
133
linear_damping: ENEMY_DAMPING,
134
angular_damping: 0.0,
135
})
136
.insert(ScreenEdge(EdgeBehaviour::Wraps))
137
.insert(FireRate(ENEMY_STARTING_FIRE_RATE_PS))
138
.insert(FireCooldown(Timer::from_seconds(10., TimerMode::Once)))
139
.insert(Health::new(500.));
140
}
141