1
use std::f32::consts::PI;
2
3
use bevy::{prelude::*, window::WindowResolution};
4
use screen_edge::{EdgeBehaviour, ScreenEdge};
5
6
mod player;
7
mod screen_edge;
8
mod sound;
9
mod ui;
10
11
const WINDOW_WIDTH: f32 = 1920.;
12
const WINDOW_HEIGHT: f32 = 1200.;
13
const WINDOW_WRAP_OVERSHOOT: f32 = player::PLAYER_LENGTH.max(player::PLAYER_WIDTH) / 2.;
14
15
const UNIVERSAL_FRICTION: f32 = 1.1;
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
27
const BACKGROUND_COLOR: Color = Color::srgb(0.02, 0.018, 0.025);
28
const ENEMY_COLOR: Color = Color::srgb(1.0, 0.5, 0.5);
29
30
fn main() {
31
let window_plugin = WindowPlugin {
32
primary_window: Some(Window {
33
title: "tähetriiv".to_string(),
34
resolution: WindowResolution::new(WINDOW_WIDTH, WINDOW_HEIGHT),
35
..default()
36
}),
37
..default()
38
};
39
40
App::new()
41
.add_plugins(DefaultPlugins.set(window_plugin))
42
.insert_resource(ClearColor(BACKGROUND_COLOR))
43
.init_resource::<player::PlayerBulletAssets>()
44
.add_event::<sound::SoundEvent>()
45
.add_systems(Startup, (setup, player::setup, ui::setup, sound::setup))
46
.add_systems(
47
FixedUpdate,
48
(
49
apply_velocity,
50
apply_friction,
51
screen_edge::check_edges,
52
check_collisions,
53
sound::play_sound,
54
)
55
.chain(),
56
)
57
.add_systems(
58
Update,
59
(
60
player::handle_player_movement,
61
player::handle_player_fire,
62
ui::update_player_health_ui,
63
),
64
)
65
.run();
66
}
67
68
#[derive(Component)]
69
struct Health {
70
current: f32,
71
max: f32,
72
}
73
74
impl Health {
75
fn new(value: f32) -> Self {
76
Self {
77
current: value,
78
max: value,
79
}
80
}
81
}
82
83
#[derive(Component)]
84
struct FireRate(f32);
85
86
#[derive(Component)]
87
struct FireCooldown(Timer);
88
89
#[derive(Component)]
90
struct Enemy;
91
92
#[derive(Component, Deref, DerefMut)]
93
struct Velocity(Vec2);
94
95
#[derive(Component)]
96
struct Friction;
97
98
fn setup(
99
mut commands: Commands,
100
mut meshes: ResMut<Assets<Mesh>>,
101
mut materials: ResMut<Assets<ColorMaterial>>,
102
) {
103
commands.spawn(Camera2d);
104
commands.spawn((
105
Enemy,
106
Mesh2d(meshes.add(Triangle2d::default())),
107
MeshMaterial2d(materials.add(ENEMY_COLOR)),
108
Transform::from_translation(ENEMY_STARTING_POSITION)
109
.with_scale(Vec2::splat(ENEMY_WIDTH).extend(1.))
110
.with_rotation(Quat::from_rotation_z(ENEMY_STARTING_ANGLE_R - PI / 2.)),
111
Velocity(Vec2::from_angle(ENEMY_STARTING_ANGLE_R) * ENEMY_STARTING_SPEED),
112
Friction,
113
ScreenEdge(EdgeBehaviour::Wraps),
114
FireRate(ENEMY_STARTING_FIRE_RATE_PS),
115
FireCooldown(Timer::from_seconds(10., TimerMode::Once)),
116
Health::new(500.),
117
));
118
}
119
120
fn apply_velocity(mut query: Query<(&mut Transform, &Velocity)>, time: Res<Time>) {
121
for (mut transform, velocity) in &mut query {
122
transform.translation.x += velocity.x * time.delta_secs();
123
transform.translation.y += velocity.y * time.delta_secs();
124
}
125
}
126
127
fn apply_friction(mut query: Query<&mut Velocity, With<Friction>>, time: Res<Time>) {
128
for mut velocity in &mut query {
129
velocity.x -= velocity.x * UNIVERSAL_FRICTION * time.delta_secs();
130
velocity.y -= velocity.y * UNIVERSAL_FRICTION * time.delta_secs();
131
}
132
}
133
134
fn check_collisions(
135
mut commands: Commands,
136
player_bullet_query: Query<(&player::PlayerBullet, Entity, &Transform)>,
137
mut player_query: Query<(Entity, &player::Player, &Transform, &mut Health)>,
138
enemy_query: Query<(Entity, &Transform), With<Enemy>>,
139
mut sound_events: EventWriter<sound::SoundEvent>,
140
) {
141
for (player_bullet, player_bullet_entity, player_bullet_transform) in &player_bullet_query {
142
for (enemy_entity, enemy_transform) in &enemy_query {
143
// TODO: rapier
144
if player_bullet_transform
145
.translation
146
.distance(enemy_transform.translation)
147
< 50.
148
{
149
commands.entity(player_bullet_entity).despawn();
150
commands.entity(enemy_entity).despawn();
151
sound_events.write(sound::SoundEvent(sound::SoundKind::Explosion));
152
}
153
}
154
155
for (other_player_entity, other_player, other_player_transform, mut other_player_health) in
156
&mut player_query
157
{
158
if player_bullet.shooter == other_player.0 {
159
continue;
160
}
161
162
// TODO: rapier
163
if player_bullet_transform
164
.translation
165
.distance(other_player_transform.translation)
166
< 100.
167
{
168
commands.entity(player_bullet_entity).despawn();
169
other_player_health.current -= player_bullet.damage;
170
if other_player_health.current <= 0. {
171
commands.entity(other_player_entity).despawn();
172
sound_events.write(sound::SoundEvent(sound::SoundKind::Explosion));
173
} else {
174
sound_events.write(sound::SoundEvent(sound::SoundKind::ShipDamage));
175
}
176
}
177
}
178
}
179
}
180