r/o
1
use std::f32::consts::PI;
2
3
use bevy::{
4
math::ops::{atan2, hypot},
5
prelude::*,
6
};
7
8
use crate::{
9
EdgeBehaviour, FireCooldown, FireRate, Friction, GAMEPAD_STICK_DEADZONE,
10
GAMEPAD_TRIGGER_DEADZONE, Health, ScreenEdge, Velocity,
11
sound::{SoundEvent, SoundKind},
12
};
13
14
const PLAYER0_STARTING_POSITION: Vec3 = Vec3::new(-600.0, -350.0, 1.0);
15
const PLAYER0_STARTING_ANGLE_R: f32 = 0.6;
16
const PLAYER0_STARTING_SPEED: f32 = 800.;
17
const PLAYER1_STARTING_POSITION: Vec3 = Vec3::new(700.0, -450.0, 1.0);
18
const PLAYER1_STARTING_ANGLE_R: f32 = PI - 0.7;
19
const PLAYER1_STARTING_SPEED: f32 = 700.;
20
21
const PLAYER_STARTING_HEALTH: f32 = 1000.;
22
const PLAYER_STARTING_FIRE_RATE_PS: f32 = 5.;
23
pub(crate) const PLAYER_LENGTH: f32 = 40.;
24
pub(crate) const PLAYER_WIDTH: f32 = 30.;
25
const PLAYER_ACCELERATION: f32 = 800.;
26
const PLAYER_MAX_ROTATION_SPEED_RPS: f32 = PI * 1.5;
27
28
const PLAYER_BULLET_SIZE: Vec2 = Vec2::new(4.5, 18.);
29
const PLAYER_BULLET_SPEED: f32 = 700.;
30
const PLAYER_BULLET_SPREAD: f32 = PI / 16.;
31
32
const PLAYER0_COLOR: Color = Color::srgb(0.5, 0.35, 0.8);
33
const PLAYER1_COLOR: Color = Color::srgb(0.5, 0.8, 0.35);
34
35
#[derive(PartialEq, Copy, Clone)]
36
pub(crate) struct PlayerId(pub(crate) usize); // XXX: we index directly into a Vec<&Gamepad> with this.
37
38
#[derive(Component)]
39
pub(crate) struct Player(pub(crate) PlayerId);
40
41
#[derive(Component, Copy, Clone)]
42
pub(crate) struct PlayerBullet {
43
pub(crate) shooter: PlayerId,
44
pub(crate) damage: f32,
45
}
46
47
#[derive(Resource)]
48
pub(crate) struct PlayerBulletAssets {
49
mesh: Mesh2d,
50
player0_material: MeshMaterial2d<ColorMaterial>,
51
player1_material: MeshMaterial2d<ColorMaterial>,
52
}
53
54
impl FromWorld for PlayerBulletAssets {
55
fn from_world(world: &mut World) -> Self {
56
return Self {
57
mesh: Mesh2d(world.add_asset(Rectangle::from_size(PLAYER_BULLET_SIZE))),
58
player0_material: MeshMaterial2d(
59
world.add_asset(PLAYER0_COLOR.mix(&Color::WHITE, 0.3)),
60
),
61
player1_material: MeshMaterial2d(
62
world.add_asset(PLAYER1_COLOR.mix(&Color::WHITE, 0.3)),
63
),
64
};
65
}
66
}
67
68
pub(crate) fn setup(
69
mut commands: Commands,
70
mut meshes: ResMut<Assets<Mesh>>,
71
mut materials: ResMut<Assets<ColorMaterial>>,
72
) {
73
commands.spawn_batch([
74
(
75
Player(PlayerId(0)),
76
Mesh2d(meshes.add(Triangle2d::default())),
77
MeshMaterial2d(materials.add(PLAYER0_COLOR)),
78
Transform::from_translation(PLAYER0_STARTING_POSITION)
79
.with_scale(Vec2::new(PLAYER_WIDTH, PLAYER_LENGTH).extend(1.))
80
.with_rotation(Quat::from_rotation_z(PLAYER0_STARTING_ANGLE_R - PI / 2.)),
81
Velocity(Vec2::from_angle(PLAYER0_STARTING_ANGLE_R) * PLAYER0_STARTING_SPEED),
82
Friction,
83
ScreenEdge(EdgeBehaviour::Wraps),
84
FireRate(PLAYER_STARTING_FIRE_RATE_PS),
85
FireCooldown(Timer::from_seconds(0., TimerMode::Once)),
86
Health::new(PLAYER_STARTING_HEALTH),
87
),
88
(
89
Player(PlayerId(1)),
90
Mesh2d(meshes.add(Triangle2d::default())),
91
MeshMaterial2d(materials.add(PLAYER1_COLOR)),
92
Transform::from_translation(PLAYER1_STARTING_POSITION)
93
.with_scale(Vec2::new(PLAYER_WIDTH, PLAYER_LENGTH).extend(1.))
94
.with_rotation(Quat::from_rotation_z(PLAYER1_STARTING_ANGLE_R - PI / 2.)),
95
Velocity(Vec2::from_angle(PLAYER1_STARTING_ANGLE_R) * PLAYER1_STARTING_SPEED),
96
Friction,
97
ScreenEdge(EdgeBehaviour::Wraps),
98
FireRate(PLAYER_STARTING_FIRE_RATE_PS),
99
FireCooldown(Timer::from_seconds(0., TimerMode::Once)),
100
Health::new(PLAYER_STARTING_HEALTH),
101
),
102
]);
103
}
104
105
pub(crate) fn handle_player_movement(
106
gamepads: Query<&Gamepad>,
107
players: Query<(&Player, &mut Transform, &mut Velocity)>,
108
time: Res<Time>,
109
) {
110
let gamepads = gamepads.iter().collect::<Vec<_>>();
111
112
for (player, mut transform, mut velocity) in players {
113
let gamepad = &gamepads[player.0.0];
114
115
let lsx = gamepad.get(GamepadAxis::LeftStickX).unwrap();
116
let lsy = gamepad.get(GamepadAxis::LeftStickY).unwrap();
117
if lsx.abs() > GAMEPAD_STICK_DEADZONE || lsy.abs() > GAMEPAD_STICK_DEADZONE {
118
let at = atan2(lsy, lsx);
119
let dist = hypot(lsy, lsx).min(1.0);
120
121
let new_rotation = Quat::rotate_towards(
122
&transform.rotation,
123
Quat::from_rotation_z(at - PI / 2.0),
124
time.delta_secs() * PLAYER_MAX_ROTATION_SPEED_RPS * dist,
125
);
126
transform.rotation = new_rotation;
127
}
128
129
let lt2 = gamepad.get(GamepadButton::LeftTrigger2).unwrap();
130
if lt2.abs() > GAMEPAD_TRIGGER_DEADZONE {
131
let sa = transform.rotation * Vec3::Y;
132
velocity.x += sa.x * PLAYER_ACCELERATION * lt2 * time.delta_secs();
133
velocity.y += sa.y * PLAYER_ACCELERATION * lt2 * time.delta_secs();
134
}
135
}
136
}
137
138
pub(crate) fn handle_player_fire(
139
mut commands: Commands,
140
bullet_assets: Res<PlayerBulletAssets>,
141
gamepads: Query<&Gamepad>,
142
players: Query<(&Player, &Transform, &Velocity, &mut FireCooldown, &FireRate)>,
143
mut sound_events: EventWriter<SoundEvent>,
144
time: Res<Time>,
145
) {
146
let gamepads = gamepads.iter().collect::<Vec<_>>();
147
148
for (player, transform, velocity, mut fire_cooldown, fire_rate) in players {
149
fire_cooldown.0.tick(time.delta());
150
151
let gamepad = &gamepads[player.0.0];
152
let rt2 = gamepad.get(GamepadButton::RightTrigger2).unwrap();
153
if rt2.abs() > 0. {
154
if fire_cooldown.0.finished() {
155
let base_trans = Transform::from_translation(transform.translation.with_z(0.))
156
.with_rotation(transform.rotation);
157
let mut rl = base_trans.clone();
158
rl.rotate_z(PLAYER_BULLET_SPREAD);
159
let mut rr = base_trans.clone();
160
rr.rotate_z(-PLAYER_BULLET_SPREAD);
161
let material = if player.0.0 == 0 {
162
&bullet_assets.player0_material
163
} else {
164
&bullet_assets.player1_material
165
};
166
let bullet = PlayerBullet {
167
shooter: player.0,
168
damage: 100.,
169
};
170
commands.spawn_batch([
171
(
172
bullet,
173
bullet_assets.mesh.clone(),
174
material.clone(),
175
base_trans,
176
Velocity(
177
velocity.0 + (base_trans.rotation * Vec3::Y).xy() * PLAYER_BULLET_SPEED,
178
),
179
ScreenEdge(EdgeBehaviour::Despawns),
180
),
181
(
182
bullet,
183
bullet_assets.mesh.clone(),
184
material.clone(),
185
rl,
186
Velocity(velocity.0 + (rl.rotation * Vec3::Y).xy() * PLAYER_BULLET_SPEED),
187
ScreenEdge(EdgeBehaviour::Despawns),
188
),
189
(
190
bullet,
191
bullet_assets.mesh.clone(),
192
material.clone(),
193
rr,
194
Velocity(velocity.0 + (rr.rotation * Vec3::Y).xy() * PLAYER_BULLET_SPEED),
195
ScreenEdge(EdgeBehaviour::Despawns),
196
),
197
]);
198
fire_cooldown.0 = Timer::from_seconds(1. / fire_rate.0, TimerMode::Repeating);
199
sound_events.write(SoundEvent(SoundKind::PlayerFire));
200
}
201
}
202
}
203
}
204