Skip to content

Commit

Permalink
smarter enemies
Browse files Browse the repository at this point in the history
  • Loading branch information
tsukinoko-kun committed Jul 13, 2024
1 parent 0d9bfe6 commit 9e97847
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 20 deletions.
73 changes: 72 additions & 1 deletion src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,42 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use bevy::prelude::{Vec2, Vec3};
use bevy::prelude::{Transform, Vec2, Vec3};
use rand_core::RngCore;

pub trait FRng {
fn next_f32(&mut self) -> f32;
fn next_f32_range(&mut self, min: f32, max: f32) -> f32 {
min + (max - min) * self.next_f32()
}
}

impl FRng for wyrand::WyRand {
fn next_f32(&mut self) -> f32 {
self.rand() as f32 / u32::MAX as f32
}
fn next_f32_range(&mut self, min: f32, max: f32) -> f32 {
min + (max - min) * self.next_f32()
}
}

impl FRng for bevy_prng::WyRand {
fn next_f32(&mut self) -> f32 {
self.next_u32() as f32 / u32::MAX as f32
}
fn next_f32_range(&mut self, min: f32, max: f32) -> f32 {
min + (max - min) * self.next_f32()
}
}

impl FRng for bevy::prelude::ResMut<'_, bevy_rand::prelude::GlobalEntropy<bevy_prng::WyRand>> {
fn next_f32(&mut self) -> f32 {
self.next_u32() as f32 / u32::MAX as f32
}
fn next_f32_range(&mut self, min: f32, max: f32) -> f32 {
min + (max - min) * self.next_f32()
}
}

pub trait IntoVec3 {
fn xyz(self) -> Vec3;
Expand All @@ -27,3 +62,39 @@ impl IntoVec3 for Vec2 {
Vec3::new(self.x, self.y, 0.0)
}
}

pub trait RandomAround {
fn random_around(self, rng: &mut impl FRng, min_radius: f32, max_radius: f32) -> Self;
}

impl RandomAround for Vec2 {
fn random_around(self, rng: &mut impl FRng, min_radius: f32, max_radius: f32) -> Self {
let radius = rng.next_f32() * 2.0 * std::f32::consts::PI;
let distance = rng.next_f32_range(min_radius, max_radius);
Vec2::new(
self.x + distance * radius.cos(),
self.y + distance * radius.sin(),
)
}
}

impl RandomAround for Vec3 {
fn random_around(self, rng: &mut impl FRng, min_radius: f32, max_radius: f32) -> Self {
let radius = rng.next_f32() * 2.0 * std::f32::consts::PI;
let distance = rng.next_f32_range(min_radius, max_radius);
Vec3::new(
self.x + distance * radius.cos(),
self.y + distance * radius.sin(),
0.0,
)
}
}

impl RandomAround for Transform {
fn random_around(self, rng: &mut impl FRng, min_radius: f32, max_radius: f32) -> Self {
Transform {
translation: self.translation.random_around(rng, min_radius, max_radius),
..self
}
}
}
78 changes: 60 additions & 18 deletions src/gameplay/enemy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use bevy::audio::PlaybackMode;
use bevy::prelude::*;
#[cfg(feature = "storage")]
Expand All @@ -24,6 +23,7 @@ use bevy_prng::WyRand;
use bevy_rand::prelude::GlobalEntropy;
use rand_core::RngCore;

use crate::ext::{IntoVec3, RandomAround};
use crate::gameplay::anim::*;
use crate::gameplay::health::*;
use crate::gameplay::movement::*;
Expand All @@ -34,15 +34,20 @@ use crate::state::{AppState, ON_ENTER_GAMEPLAY, ON_EXIT_GAMEPLAY};

const ENEMY_THRESHOLD: f32 = 64.0;

pub struct EnemyPlugin;

#[derive(Debug)]
enum EnemyState {
Hunting,
ReadyBlade,
SwingBlade,
}

#[derive(Debug)]
enum EnemyTarget {
Player,
PlayerFuture,
Location(Vec2),
}

#[derive(Debug)]
enum Face {
Left,
Expand All @@ -54,14 +59,18 @@ pub struct Enemy {
animation_state: EnemyState,
face: Face,
sword_hit_timer: Timer,
target_switch_timer: Timer,
target: EnemyTarget,
}

impl Default for Enemy {
fn default() -> Self {
Self {
animation_state: EnemyState::Hunting,
face: Face::Left,
sword_hit_timer: Timer::from_seconds(0.5, TimerMode::Repeating),
sword_hit_timer: Timer::from_seconds(0.3, TimerMode::Repeating),
target_switch_timer: Timer::from_seconds(6.0, TimerMode::Repeating),
target: EnemyTarget::Player,
}
}
}
Expand All @@ -87,21 +96,16 @@ fn spawn_enemy(
let texture_atlas_layout = texture_atlas_layouts.add(layout);
let animation_indices = AnimationIndices::new(0, 0);

let radius = rng.next_u32() as f32 / u32::MAX as f32 * 2.0 * std::f32::consts::PI;
let distance =
rng.next_u32() as f32 / u32::MAX as f32 * difficulty.get_enemy_speed() * 15.0
+ difficulty.get_enemy_speed() * 15.0;

commands.spawn((
Enemy::default(),
Health::new(1.0),
SpriteBundle {
texture,
transform: Transform {
translation: Vec3::new(
player_transform.translation.x + distance * radius.cos(),
player_transform.translation.y + distance * radius.sin(),
0.0,
translation: player_transform.translation.random_around(
&mut rng,
difficulty.get_enemy_speed() * 15.0,
difficulty.get_enemy_speed() * 30.0,
),
scale: Vec3::splat(1.0),
..default()
Expand Down Expand Up @@ -205,13 +209,48 @@ fn enemy_attack_fx(commands: &mut Commands, asset_server: &Res<AssetServer>) {
});
}

fn switch_target(
mut enemy_q: Query<&mut Enemy>,
time: Res<Time>,
mut rng: ResMut<GlobalEntropy<WyRand>>,
player_transform_q: Query<&GlobalTransform, With<Player>>,
) {
let player_transform = match player_transform_q.get_single() {
Ok(player_transform) => player_transform,
Err(_) => return,
};
for mut enemy in enemy_q.iter_mut() {
enemy.target_switch_timer.tick(time.delta());
if enemy.target_switch_timer.just_finished() {
let target = match rng.next_u32() % 3 {
0 => EnemyTarget::Player,
1 => EnemyTarget::PlayerFuture,
_ => EnemyTarget::Location(
player_transform
.translation()
.random_around(&mut rng, 128.0, 512.0)
.xy(),
),
};
enemy.target = target;
}
}
}

fn update_position(
mut enemy_q: Query<(&Transform, &mut Velocity), With<Enemy>>,
player_transform_q: Query<&Transform, With<Player>>,
mut enemy_q: Query<(&Enemy, &Transform, &mut Velocity), Without<Player>>,
player_q: Query<(&Transform, &Velocity), With<Player>>,
) {
if let Ok(player_transform) = player_transform_q.get_single() {
for (enemy_transform, mut enemy_vel) in enemy_q.iter_mut() {
let direction = player_transform.translation - enemy_transform.translation;
if let Ok((player_transform, player_velocity)) = player_q.get_single() {
for (enemy, enemy_transform, mut enemy_vel) in enemy_q.iter_mut() {
let target_pos = match enemy.target {
EnemyTarget::Player => player_transform.translation,
EnemyTarget::PlayerFuture => {
player_transform.translation + player_velocity.direction * 128.0
}
EnemyTarget::Location(pos) => pos.xyz(),
};
let direction = target_pos - enemy_transform.translation;
if direction.length() >= ENEMY_THRESHOLD {
enemy_vel.direction = direction.normalize();
} else {
Expand Down Expand Up @@ -286,6 +325,8 @@ impl EnemyDifficulty {
}
}

pub struct EnemyPlugin;

impl Plugin for EnemyPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(EnemyDifficulty::default())
Expand All @@ -294,6 +335,7 @@ impl Plugin for EnemyPlugin {
Update,
(
spawn_enemy,
switch_target,
update_position,
enemy_attack,
update_animation,
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn main() {
title: "Mageanoid".into(),
name: Some("Mageanoid".into()),
present_mode: bevy_window::PresentMode::AutoVsync,
mode: bevy_window::WindowMode::BorderlessFullscreen,
// mode: bevy_window::WindowMode::BorderlessFullscreen,
fit_canvas_to_parent: true,
prevent_default_event_handling: false,
window_theme: Some(bevy_window::WindowTheme::Dark),
Expand Down

0 comments on commit 9e97847

Please sign in to comment.